You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

是否存在支持任意Applicative的Review与Setter复合Optic?

Can we create an Optic that acts as both a Review and a Setter for arbitrary Applicatives?

Great question! Let's unpack what you're asking and walk through how to solve it.

First, let's clarify your core requirements:

  • As a Review: You want theOptic # 5 to produce an IO Int (so you can chain it with >>= print). This means review theOptic 5 should evaluate to return 5 :: IO Int.
  • As a Setter: You want [2, 3] & theOptic .~ 6 to modify all elements in the list, resulting in [6, 6]—this matches the behavior of a traversal-style setter.

You're right that there's no built-in Optic type in the lens library that combines both Review and Setter constraints directly. Here's why:

  • Setter relies on covariant profunctors, focused on transforming values from a to b within a structure s to produce t.
  • Review relies on contravariant profunctors, focused on constructing a structure s from a value a.
  • The only Optic that combines both directions is an Iso, which requires a full isomorphism between s and a—something that doesn't apply here (since IO a isn't isomorphic to a, and [a] isn't isomorphic to a).

But that doesn't mean we can't create a polymorphic Optic that behaves as either a Review or Setter depending on the context. Here's how to build it:

Implementing the Polymorphic Optic

We can leverage Haskell's type system and lens's flexibility to define a single theOptic that adapts to different use cases.

Option 1: Using Custom Type Classes

Define a type class to encapsulate the dual behavior, then provide instances for the Review and Setter scenarios:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}

import Control.Lens

class MyOptic o where
  theOptic :: o

-- Instance for Setter: acts like `traversed` to modify list elements
instance (Settable p, Applicative f) => MyOptic (Setter [a] [b] a b) where
  theOptic = traversed

-- Instance for Review: constructs an IO a from a value a
instance MyOptic (Review (IO a) a) where
  theOptic = unto return

Now you can use it exactly as you wanted:

-- Use as a Review
main :: IO ()
main = (theOptic # 5) >>= print  -- Prints 5 to the console

-- Use as a Setter
modifyList :: [Int] -> [Int]
modifyList xs = xs & theOptic .~ 6  -- Turns [2,3] into [6,6]

Option 2: Overloaded Labels (Concise Alternative)

If you're using OverloadedLabels, you can define a more streamlined version by associating the label with both Optic behaviors:

{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RankNTypes #-}

import Control.Lens

instance IsLabel "theOptic" (Setter [a] [b] a b) where
  fromLabel = traversed

instance IsLabel "theOptic" (Review (IO a) a) where
  fromLabel = unto return

Usage stays intuitive, with a small syntax change:

main = (#theOptic # 5) >>= print
modifyList xs = xs & #theOptic .~ 6

Key Notes

  • This works because Haskell's type inference picks the correct instance based on context: when you use the # review operator, it resolves to the Review instance; when you use the .~ setter operator, it resolves to the Setter instance.
  • This isn't a "single" Optic type in the lens hierarchy, but a polymorphic value that can act as either Optic type. It's a valid solution to your problem, even if it doesn't fit neatly into pre-defined Optic categories.

内容的提问来源于stack exchange,提问作者yairchu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:02:37