如何将镜头(或任何其他光学镜头)同时用作吸气剂和吸气剂? [英] How to treat a lens (or any other optic) as a getter and setter simultaneously?
问题描述
我试图编写一个通用记录更新程序,它允许用户轻松更新现有
记录中的字段,字段类似于形状为传入
记录。这是我到现在为止:
applyUpdater fields exists existing =
let getters = DL.map(^ )字段
setters = DL.map set fields
updaters = DL.zipWith(,)getters setters $ b $ in DL.foldl'(\updated(getter,setter) - > setter( getter incoming)更新)现有更新程序
我希望以下列方式使用它:
applyUpdater
[email,notificationEnabled] - 要从incoming =>复制的字段现有的(这显然假设`name`和`email`镜头已经被设置
用户{name =saurabh,email =blah@blah.com,notificationEnabled = True}
用户{ name =saurabh,email =foo@bar.com,notificationEnabled = False}
不起作用,可能是因为Haskell为 applyUpdater
推断出一个非常奇怪的类型签名,这意味着它不会执行我期望的操作:
applyUpdater :: [ASetter t1 t1 at] - > t1 - >获取t(ASetter t1 t1 at)t - > t1
下面是代码示例和编译错误:
模块TryUpdater其中
导入Control.Lens
导入GHC.Generics
导入Data.List作为DL
data用户=用户{_name :: String,_email :: String,_notificationEnabled :: Bool}派生(Eq,Show,Generic)
makeLensesWith classUnderscoreNoPrefixFields''User
- applyUpdater :: [ASetter t1 t1 at] - > t1 - >获得t(ASetter t1 t1 a t)t - > t1
applyUpdater字段现有传入=
让getters = DL.map(^。)字段
setters = DL.map设置字段
updaters = DL.zipWith(,)getters setters (更新(getter,setter) - > setter(getter incoming)更新)现有更新程序
testUpdater :: User - >
in DL.foldl'用户 - > User
testUpdater existingUser incomingUser = applyUpdater [email,notificationEnabled] existingUser incomingUser
编译错误:
18 62错误错误:
•无法将类型'Bool'与'[Char]'
由于使用'notificationEnabled'
实例'HasNotificationEnabled User Bool'
在/ Users / saurabhnanda / projects / vl-haskell / .stack-work / intero / intero54587Sfx.hs:8:1-51
•在表达式中:notificationEnabled
在'applyUpdater'的第一个参数中,即
'[email,notificationEnabled]'
在表达式中:
applyUpdater [email,notificationEnabled] existingUser incomingUser(intero)
18 96错误错误:
•无法匹配类型'用户'
机智h'(字符串 - >常量字符串字符串)
- > ASetter用户用户字符串字符串
- > Const String(ASetter User User String String)'
预期类型:获得
字符串(ASetter用户字符串字符串)字符串
实际类型:用户
•在' applyUpdater',即'incomingUser'
在表达式中:
applyUpdater [email,notificationEnabled] existingUser incomingUser
在'testUpdater'的等式中:
testUpdater existingUser incomingUser
= applyUpdater
[email,notificationEnabled] existingUser incomingUser(intero)
(^。)
将镜头作为它的 right 参数,所以你真正想要的实际上是 getters = DL.map(flip(^。))字段
,又名 DL.map视图字段
。 但是这里更有趣的问题是:光学需要更高阶的多态性,因此GHC只能猜测类型。出于这个原因,始终 带着类型签名! 您可能会写出 好吧,这实际上并不奏效,因为 由于
applyUpdater :: [Lens'sa] - > s - > s - > s
Lens'
包含一个∀
量词,因此将它放在列表中将需要不确定的多态性,而GHC并不真正有能力。常见问题,所以镜头库有两种方法来解决这个问题:
ALens
仅仅是 Functor
约束的具体实例,所以选择这样就保留了完整的一般性。然而,您需要使用不同的组合器来应用它。
applyUpdater :: [ALens's a] - > s - > s - > s
applyUpdater字段存在传入=
让getters = DL.map(flip(^#))字段
setters = DL.map存储字段
updaters = DL.zipWith(, )在DL.foldl中获得
的获得者(\upd(γ,σ) - >σ(γincoming)upd)现有更新者
ALens
严格来说是一个 Lens
的实例,您可以使用这正是你想要的。 ReifiedLens
保留了原始的多态性,但是将其包装为新类型,镜头可以存储在例如一个列表。被包裹的镜头可以照常使用,但是您需要明确地包装它们以传递到您的函数;这可能不值得您的应用程序的麻烦。当您想以较不直接的方式重新使用存储的镜头时,此方法更有用。 (这也可以用 ALens
来完成,但它需要 cloneLens
,我认为这对性能不利。)
$ p $ applyUpdater 现在将以我用<$ c解释的方式工作$ c> ALens',但它只能用于所有镜头列表,所有焦点都是同一类型的字段。将镜头聚焦在列表中不同类型的字段显然是一种类型错误。为了达到这个目的,你必须 email $ c的类型$ c>和
notificationEnabled
,以便您可以在单个列表中添加内容。
但在经历这种麻烦之前,我强烈认为 完全不会将任何镜头存储在列表中:基本上,构成更新函数的所有内容都可以访问共享参考。那么直接做 - 访问一个共享引用就是monad为你提供的函数的正确方法,所以编写这个函数很简单。
applyUpdater :: [s - > r - > s] - > s - > r - > s
applyUpdater = foldr(> =>)纯
一个单独的更新函数,写入
mkUpd :: ALens'sa - > s - > s - > s
mkUpd l exi inc =存储l(inc ^#l)exi
成为用于像
applyUpdater
[mkUpd电子邮件,mkUpd notificationEnabled]
用户{name =saurabh, email =blah@blah.com,notificationEnabled = True}
用户{name =saurabh,email =foo@bar.com,notificationEnabled = False}
I'm trying to write a generic record updater which will allow one to easily update fields in an existing
record, with fields in a similarly shaped incoming
record. Here is what I have till now:
applyUpdater fields existing incoming =
let getters = DL.map (^.) fields
setters = DL.map set fields
updaters = DL.zipWith (,) getters setters
in DL.foldl' (\updated (getter, setter) -> setter (getter incoming) updated) existing updaters
And I wish to use it in the following manner:
applyUpdater
[email, notificationEnabled] -- the fields to be copied from incoming => existing (this obviously assumed that `name` and `email` lenses have already been setup
User{name="saurabh", email="blah@blah.com", notificationEnabled=True}
User{name="saurabh", email="foo@bar.com", notificationEnabled=False}
This doesn't work, probably because Haskell infers a very weird type signature for applyUpdater
which means it it not doing what I'm expecting it to do:
applyUpdater :: [ASetter t1 t1 a t] -> t1 -> Getting t (ASetter t1 t1 a t) t -> t1
Here's a code-sample and the compile error:
module TryUpdater where
import Control.Lens
import GHC.Generics
import Data.List as DL
data User = User {_name::String, _email::String, _notificationEnabled::Bool} deriving (Eq, Show, Generic)
makeLensesWith classUnderscoreNoPrefixFields ''User
-- applyUpdater :: [ASetter t1 t1 a t] -> t1 -> Getting t (ASetter t1 t1 a t) t -> t1
applyUpdater fields existing incoming =
let getters = DL.map (^.) fields
setters = DL.map set fields
updaters = DL.zipWith (,) getters setters
in DL.foldl' (\updated (getter, setter) -> setter (getter incoming) updated) existing updaters
testUpdater :: User -> User -> User
testUpdater existingUser incomingUser = applyUpdater [email, notificationEnabled] existingUser incomingUser
Compile error:
18 62 error error:
• Couldn't match type ‘Bool’ with ‘[Char]’
arising from a functional dependency between:
constraint ‘HasNotificationEnabled User String’
arising from a use of ‘notificationEnabled’
instance ‘HasNotificationEnabled User Bool’
at /Users/saurabhnanda/projects/vl-haskell/.stack-work/intero/intero54587Sfx.hs:8:1-51
• In the expression: notificationEnabled
In the first argument of ‘applyUpdater’, namely
‘[email, notificationEnabled]’
In the expression:
applyUpdater [email, notificationEnabled] existingUser incomingUser (intero)
18 96 error error:
• Couldn't match type ‘User’
with ‘(String -> Const String String)
-> ASetter User User String String
-> Const String (ASetter User User String String)’
Expected type: Getting
String (ASetter User User String String) String
Actual type: User
• In the third argument of ‘applyUpdater’, namely ‘incomingUser’
In the expression:
applyUpdater [email, notificationEnabled] existingUser incomingUser
In an equation for ‘testUpdater’:
testUpdater existingUser incomingUser
= applyUpdater
[email, notificationEnabled] existingUser incomingUser (intero)
First, note that (^.)
takes the lens as its right argument, so what you really want is actually getters = DL.map (flip (^.)) fields
, aka DL.map view field
.
But the more interesting problem here: optics require higher-rank polymorphism, so GHC can only guess types. Always start out with the type signature for this reason!
Naïvely, you would probably write
applyUpdater :: [Lens' s a] -> s -> s -> s
Well, that doesn't actually work, because Lens'
includes a ∀
quantifier so putting it in a list would require impredicative polymorphism, which GHC isn't really capable of. Common problem, so the lens library has two ways of getting around that:
ALens
is just a specific instantiation of theFunctor
constraint, chosen so you retain the full generality. You need to use different combinators for applying it, however.applyUpdater :: [ALens' s a] -> s -> s -> s applyUpdater fields existing incoming = let getters = DL.map (flip (^#)) fields setters = DL.map storing fields updaters = DL.zipWith (,) getters setters in DL.foldl' (\upd (γ, σ) -> σ (γ incoming) upd) existing updaters
Because
ALens
is strictly an instantiation ofLens
, you can use that exactly the way you intended.ReifiedLens
keeps the original polymorphism, but wraps it in a newtype so the lenses can be stored in e.g. a list. The wrapped lens can then be used as usual, but you'll need to explicitly wrap them to pass into your function; this is probably not worth the hassle for your application. This approach is more useful when you want to re-use the stored lenses in a less direct manner. (This can also be done withALens
, but it requirescloneLens
which I reckon is bad for performance.)
applyUpdater
will now work the way I explained with ALens'
, however it can only be used with a lists of lenses all focusing of fields of the same type. Putting lenses focusing on fields of different type in a list is quite clearly a type error. To accomplish that, you must wrap the lenses in some newtype to hide the type parameter – no way around it, it's simply not possible to unify the types of email
and notificationEnabled
to something you can stuff in one single list.
But before going through that trouble, I would strongly consider not storing any lenses in a list at all: basically what is just composing update-functions that all access a shared reference. Well, do that directly – "all accessing a shared reference" is, conveniently, exactly what the function monad offers you, so it's trivial to write
applyUpdater :: [s -> r -> s] -> s -> r -> s
applyUpdater = foldr (>=>) pure
To convert a lens to an individual updater-function, write
mkUpd :: ALens' s a -> s -> s -> s
mkUpd l exi inc = storing l (inc^#l) exi
to be used like
applyUpdater
[mkUpd email, mkUpd notificationEnabled]
User{name="saurabh", email="blah@blah.com", notificationEnabled=True}
User{name="saurabh", email="foo@bar.com", notificationEnabled=False}
这篇关于如何将镜头(或任何其他光学镜头)同时用作吸气剂和吸气剂?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!