为什么棱镜集函数 return 和 Option/Maybe 没有
Why doesn't a prism set function return an Option/Maybe
在功能光学中,一个性能良好的棱镜(我相信在 scala 中称为部分透镜)应该具有类型为 'subpart -> 'parent -> 'parent
的 set 函数,如果棱镜 "succeeds" 并且在结构上与给定的 'parent
参数兼容,那么它 return 是 'parent
给定的适当子部分修改为具有 'subpart
给出的值。如果棱镜 "fails" 并且在结构上与 'parent
参数不兼容,那么它 return 是未修改的 'parent
。
我想知道为什么棱镜不 return 一个 'parent option
(Haskell 人的 Maybe
)来表示集合函数的 pass/fail 性质?程序员不应该能够从 return 类型判断集合是否为 "successful" 吗?
我知道在功能光学领域已经进行了大量的研究和思考,所以我确信一定有一个我似乎找不到的明确答案。
(我有 F# 背景,如果我使用的语法对 Haskell 或 Scala 程序员来说有点不透明,我深表歉意)。
我怀疑没有一个明确的答案,所以我在这里给你两个。
起源
我相信棱镜首先被想象(如果我模糊的记忆是正确的话,是 Dan Doel 提出的)"co-lenses"。而从 s
到 a
的镜头提供
get :: s -> a
set :: (s, a) -> s
从 s
到 a
的棱镜提供
coget :: a -> s
coset :: s -> Either s a
所有箭头都反转,乘积 (,)
被余积 Either
替换。所以类型和功能类别中的棱镜是对偶类别中的透镜。
对于简单的棱镜,s -> Either s a
看起来有点奇怪。你为什么要恢复原来的价值?但是 lens
包还提供 类型改变 光学元件。所以我们最终得到
get :: s -> a
set :: (s, b) -> t
coget :: a -> s
coset :: t -> Either s b
突然之间,我们在不匹配的情况下得到的结果实际上可能有点不同!那是什么?这是一个例子:
cogetLeft :: a -> Either a x
cogetLeft = Left
cosetLeft :: Either b x -> Either (Either a x) b
cosetLeft (Left b) = Right b
cosetLeft (Right x) = Left (Right x)
第二种(非匹配)情况,我们得到的value是一样的,但是它的type变了.
良好的层次结构
对于 Van Laarhoven(如 lens
)和 profunctor 风格的框架,透镜和棱镜也可以代表遍历。为此,它们需要具有相似的形式,而这种设计实现了这一点。 leftaroundabout 的回答给出了这方面的更多细节。
回答“为什么”——镜头等非常严格地来自范畴论,所以这实际上是非常明确的——你描述的行为只是从数学中消失了,这不是任何人为任何定义的东西目的,但遵循更一般的想法。
好吧,这不是很令人满意。
不确定其他语言的类型系统是否强大到足以表达这一点,但原则上和在 Haskell 中,棱镜是 遍历 的特例.
遍历是一种“访问”某些“容器”中所有“元素”出现的方法。经典的例子是
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
这通常用作
Prelude> mapM print [1..4]
1
2
3
4
[(),(),(),()]
这里的重点是:对 actions/side-effects 进行排序,并将结果收集到与我们开始时结构相同的容器中。
棱镜的特别之处在于容器仅限于包含一个或零个元素†(而一般遍历可以遍历任意数量的元素)。但是 set
运算符不知道这一点,因为它更通用。好消息是您因此可以在透镜、棱镜或 mapM
上使用它,并且始终获得明智的行为。但它不是“只向结构中插入一次,否则告诉我是否失败”的行为。
并不是说这不是一个明智的操作,只是这不是镜头库所说的“设置”。您可以通过显式匹配和重新构建来完成:
set₁ :: Prism s a -> a -> s -> Maybe s
set₁ p x = case matching p x of
Left _ -> Nothing
Right a -> Just $ a ^. re p
†更准确地说:棱镜将箱子分开:一个容器可能包含一个元素,除此之外别无其他,或者它可能没有元素,但可能不相关。
在功能光学中,一个性能良好的棱镜(我相信在 scala 中称为部分透镜)应该具有类型为 'subpart -> 'parent -> 'parent
的 set 函数,如果棱镜 "succeeds" 并且在结构上与给定的 'parent
参数兼容,那么它 return 是 'parent
给定的适当子部分修改为具有 'subpart
给出的值。如果棱镜 "fails" 并且在结构上与 'parent
参数不兼容,那么它 return 是未修改的 'parent
。
我想知道为什么棱镜不 return 一个 'parent option
(Haskell 人的 Maybe
)来表示集合函数的 pass/fail 性质?程序员不应该能够从 return 类型判断集合是否为 "successful" 吗?
我知道在功能光学领域已经进行了大量的研究和思考,所以我确信一定有一个我似乎找不到的明确答案。
(我有 F# 背景,如果我使用的语法对 Haskell 或 Scala 程序员来说有点不透明,我深表歉意)。
我怀疑没有一个明确的答案,所以我在这里给你两个。
起源
我相信棱镜首先被想象(如果我模糊的记忆是正确的话,是 Dan Doel 提出的)"co-lenses"。而从 s
到 a
的镜头提供
get :: s -> a
set :: (s, a) -> s
从 s
到 a
的棱镜提供
coget :: a -> s
coset :: s -> Either s a
所有箭头都反转,乘积 (,)
被余积 Either
替换。所以类型和功能类别中的棱镜是对偶类别中的透镜。
对于简单的棱镜,s -> Either s a
看起来有点奇怪。你为什么要恢复原来的价值?但是 lens
包还提供 类型改变 光学元件。所以我们最终得到
get :: s -> a
set :: (s, b) -> t
coget :: a -> s
coset :: t -> Either s b
突然之间,我们在不匹配的情况下得到的结果实际上可能有点不同!那是什么?这是一个例子:
cogetLeft :: a -> Either a x
cogetLeft = Left
cosetLeft :: Either b x -> Either (Either a x) b
cosetLeft (Left b) = Right b
cosetLeft (Right x) = Left (Right x)
第二种(非匹配)情况,我们得到的value是一样的,但是它的type变了.
良好的层次结构
对于 Van Laarhoven(如 lens
)和 profunctor 风格的框架,透镜和棱镜也可以代表遍历。为此,它们需要具有相似的形式,而这种设计实现了这一点。 leftaroundabout 的回答给出了这方面的更多细节。
回答“为什么”——镜头等非常严格地来自范畴论,所以这实际上是非常明确的——你描述的行为只是从数学中消失了,这不是任何人为任何定义的东西目的,但遵循更一般的想法。
好吧,这不是很令人满意。
不确定其他语言的类型系统是否强大到足以表达这一点,但原则上和在 Haskell 中,棱镜是 遍历 的特例. 遍历是一种“访问”某些“容器”中所有“元素”出现的方法。经典的例子是
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
这通常用作
Prelude> mapM print [1..4]
1
2
3
4
[(),(),(),()]
这里的重点是:对 actions/side-effects 进行排序,并将结果收集到与我们开始时结构相同的容器中。
棱镜的特别之处在于容器仅限于包含一个或零个元素†(而一般遍历可以遍历任意数量的元素)。但是 set
运算符不知道这一点,因为它更通用。好消息是您因此可以在透镜、棱镜或 mapM
上使用它,并且始终获得明智的行为。但它不是“只向结构中插入一次,否则告诉我是否失败”的行为。
并不是说这不是一个明智的操作,只是这不是镜头库所说的“设置”。您可以通过显式匹配和重新构建来完成:
set₁ :: Prism s a -> a -> s -> Maybe s
set₁ p x = case matching p x of
Left _ -> Nothing
Right a -> Just $ a ^. re p
†更准确地说:棱镜将箱子分开:一个容器可能包含一个元素,除此之外别无其他,或者它可能没有元素,但可能不相关。