是否存在支持任意Applicative的Review与Setter复合Optic?
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 # 5to produce anIO Int(so you can chain it with>>= print). This meansreview theOptic 5should evaluate toreturn 5 :: IO Int. - As a Setter: You want
[2, 3] & theOptic .~ 6to 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:
Setterrelies on covariant profunctors, focused on transforming values fromatobwithin a structuresto producet.Reviewrelies on contravariant profunctors, focused on constructing a structuresfrom a valuea.- The only Optic that combines both directions is an
Iso, which requires a full isomorphism betweensanda—something that doesn't apply here (sinceIO aisn't isomorphic toa, and[a]isn't isomorphic toa).
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 theReviewinstance; when you use the.~setter operator, it resolves to theSetterinstance. - This isn't a "single" Optic type in the
lenshierarchy, 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

