gpt4 book ai didi

haskell - 如何同时将透镜(或任何其他光学器件)视为 setter/getter 和设置剂?

转载 作者:行者123 更新时间:2023-12-02 18:22:28 25 4
gpt4 key购买 nike

我正在尝试编写一个通用记录更新程序,它允许人们轻松更新现有记录中的字段,以及类似形状的传入记录中的字段。这是我到目前为止所拥有的:

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

我希望按以下方式使用它:

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}

这不起作用,可能是因为 Haskell 为 applyUpdater 推断出一个非常奇怪的类型签名,这意味着它没有做我期望它做的事情:

applyUpdater :: [ASetter t1 t1 a t] -> t1 -> Getting t (ASetter t1 t1 a t) t -> t1

这是代码示例和编译错误:

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

编译错误:

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)

最佳答案

首先,请注意 (^.) 将镜头作为其 right 参数,因此您真正想要的实际上是 getters = DL.map (flip (^.)) 字段,又名 DL.map View 字段

但更有趣的问题是:光学需要更高阶的多态性,因此 GHC 只能猜测类型。因此,始终从类型签名开始!

天真地,你可能会写

applyUpdater :: [Lens' s a] -> s -> s -> s

嗯,这实际上不起作用,因为 Lens' 包含一个 量词,因此将其放入列表 would require impredicative polymorphism, which GHC isn't really capable of 。常见问题,因此镜头库有两种方法来解决这个问题:

  • ALens只是 Functor 约束的一个特定实例,选择它是为了保留完整的通用性。但是,您需要使用不同的组合器来应用它。

    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

    因为 ALens 严格来说是 Lens 的实例,因此您可以完全按照您想要的方式使用它。

  • ReifiedLens保留原始的多态性,但将其包装在新型中,以便镜片可以存储在例如一个列表。然后可以像往常一样使用包裹的镜头,但是您需要显式地包裹它们以传递到您的函数中;对于您的应用程序来说,这可能不值得。当您想以不太直接的方式重复使用存储的镜头时,此方法更有用。 (这也可以使用ALens来完成,但它需要cloneLens,我认为这对性能不利。)

applyUpdater 现在将按照我用 ALens' 解释的方式工作,但是它只能与镜头列表一起使用,所有镜头都聚焦在相同的区域类型。将镜头聚焦在列表中不同类型的字段上显然是一种类型错误。要实现这一点,您必须将镜头包装在某种新类型中以隐藏类型参数 - 没有办法解决这个问题,根本不可能统一 email 的类型>notificationEnabled 为您可以填充在一个列表中的内容。

但在经历这个麻烦之前,我强烈考虑在列表中存储任何镜头:基本上就是组成所有访问共享引用的更新函数。好吧,直接这样做——方便地,“所有访问共享引用”正是函数 monad 为您提供的功能,因此编写起来很简单

applyUpdater :: [s -> r -> s] -> s -> r -> s
applyUpdater = foldr (>=>) pure

要将镜头转换为单独的更新器功能,请编写

mkUpd :: ALens' s a -> s -> s -> s
mkUpd l exi inc = storing l (inc^#l) exi

像这样使用

applyUpdater 
[mkUpd email, mkUpd notificationEnabled]
User{name="saurabh", email="blah@blah.com", notificationEnabled=True}
User{name="saurabh", email="foo@bar.com", notificationEnabled=False}

关于haskell - 如何同时将透镜(或任何其他光学器件)视为 setter/getter 和设置剂?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45618101/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com