如何将镜头(或任何其他光学镜头)同时用作吸气剂和吸气剂? [英] How to treat a lens (or any other optic) as a getter and setter simultaneously?

查看:155
本文介绍了如何将镜头(或任何其他光学镜头)同时用作吸气剂和吸气剂?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图编写一个通用记录更新程序,它允许用户轻松更新现有记录中的字段,字段类似于形状为传入记录。这是我到现在为止:

  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 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 the Functor 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 of Lens, 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 with ALens, but it requires cloneLens 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆