适用运算符 <* 和 *>,类型签名含义
Applicative operators <* and *>, type signature implication
我最近看到了一个简单的例子,它使 <*
和 *>
变得清晰。
validate :: String -> Maybe String
validate s = if s=="" then Nothing else Just s
>validate "a" *> validate "b"
Just "b"
>validate "" *> validate "b"
Nothing
>validate "a" <* validate "b"
Just "a"
>validate "a" <* validate ""
Nothing
>validate "a" <* validate "b" <* validate "c"
Just "a"
>validate "a" *> validate "b" <* validate "c"
Just "b"
这表明影响很重要,即使它们产生的价值并不重要。
我的问题是关于类型签名的。
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
- 这些类型签名是否真的暗示了上面显示的行为?
我可以理解如何推理“显然我们有一个 Applicative - 所以这说明了一般行为。对于运算符 *>
,因为我们丢弃了左边的值,唯一的 possible 意味着这个函数可能会是左侧的效果如何影响整个操作。"
在 Maybe
的情况下 - 那么似乎是 - 隐含了行为。对于 Either
,同样的含义成立并且错误将传播到效果 'failure'.
注意我只能说上面的内容,因为我现在知道实现是如何工作的,我的问题与第一次看到这样的签名的经验丰富的函数式程序员有关。
我读过像 [a] -> b :: Int
这样的类型签名(那里可能不是真正的代码)几乎暗示了列表长度的实现。
我还搜索了“从类型签名暗示实现”,发现人们 have-been/are 从事此类工作 - 但通常无法完成(呃 - 没有那个新的 GitHub 东西: --)
所以也许我已经回答了我自己的问题,但如果有任何其他答案或评论,我将不胜感激。我对 Haskell 还是个新手,多年来经过多次错误的开始,它终于开始深入人心了。我只触及了表面...
谢谢
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
Do these type signatures actually imply the behavior shown above?
当然不是。这两个操作可以简单地实现为
apR :: (Applicative f) => f a -> f b -> f b
apR a b = b
apL :: (Applicative f) => f a -> f b -> f a
apL a b = a
只是忽略 f
上的 Applicative
约束。
同样,[a] -> Int
并不意味着实现是 length
。也可以
foo :: [a] -> Int
foo _ = 42
但在这两种情况下,从推断类型与给定签名不同的意义上来说,这些都不是“正确”的实现。
那么您的问题可能有点不同,例如,假设 有一个具有匹配 推断类型的实现。这是否意味着它做了我们想要的事情?
答案似乎仍然是否定的。首先,我们可以定义
apR2 :: (Applicative f) => f a -> f b -> f b
apR2 a b = pure (\a _ b -> b) <*> a <*> b <*> b
对于某些类型,例如您的示例,它不会产生任何影响。但总的来说,效果做两次和只做一次是不一样的。
另一个更有意义的“错误”实现(感谢 Daniel Wagner 的评论)是
apR2b :: (Applicative f) => f a -> f b -> f b
apR2b a b = pure (\b a -> b) <*> b <*> a
现在,即使是您的示例也不一定总是有效,因为效果的 顺序 不同——它首先“执行”b
,在 a
的。
我最近看到了一个简单的例子,它使 <*
和 *>
变得清晰。
validate :: String -> Maybe String
validate s = if s=="" then Nothing else Just s
>validate "a" *> validate "b"
Just "b"
>validate "" *> validate "b"
Nothing
>validate "a" <* validate "b"
Just "a"
>validate "a" <* validate ""
Nothing
>validate "a" <* validate "b" <* validate "c"
Just "a"
>validate "a" *> validate "b" <* validate "c"
Just "b"
这表明影响很重要,即使它们产生的价值并不重要。
我的问题是关于类型签名的。
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
- 这些类型签名是否真的暗示了上面显示的行为?
我可以理解如何推理“显然我们有一个 Applicative - 所以这说明了一般行为。对于运算符 *>
,因为我们丢弃了左边的值,唯一的 possible 意味着这个函数可能会是左侧的效果如何影响整个操作。"
在 Maybe
的情况下 - 那么似乎是 - 隐含了行为。对于 Either
,同样的含义成立并且错误将传播到效果 'failure'.
注意我只能说上面的内容,因为我现在知道实现是如何工作的,我的问题与第一次看到这样的签名的经验丰富的函数式程序员有关。
我读过像 [a] -> b :: Int
这样的类型签名(那里可能不是真正的代码)几乎暗示了列表长度的实现。
我还搜索了“从类型签名暗示实现”,发现人们 have-been/are 从事此类工作 - 但通常无法完成(呃 - 没有那个新的 GitHub 东西: --)
所以也许我已经回答了我自己的问题,但如果有任何其他答案或评论,我将不胜感激。我对 Haskell 还是个新手,多年来经过多次错误的开始,它终于开始深入人心了。我只触及了表面...
谢谢
(*>) :: f a -> f b -> f b (<*) :: f a -> f b -> f a
Do these type signatures actually imply the behavior shown above?
当然不是。这两个操作可以简单地实现为
apR :: (Applicative f) => f a -> f b -> f b
apR a b = b
apL :: (Applicative f) => f a -> f b -> f a
apL a b = a
只是忽略 f
上的 Applicative
约束。
同样,[a] -> Int
并不意味着实现是 length
。也可以
foo :: [a] -> Int
foo _ = 42
但在这两种情况下,从推断类型与给定签名不同的意义上来说,这些都不是“正确”的实现。
那么您的问题可能有点不同,例如,假设 有一个具有匹配 推断类型的实现。这是否意味着它做了我们想要的事情?
答案似乎仍然是否定的。首先,我们可以定义
apR2 :: (Applicative f) => f a -> f b -> f b
apR2 a b = pure (\a _ b -> b) <*> a <*> b <*> b
对于某些类型,例如您的示例,它不会产生任何影响。但总的来说,效果做两次和只做一次是不一样的。
另一个更有意义的“错误”实现(感谢 Daniel Wagner 的评论)是
apR2b :: (Applicative f) => f a -> f b -> f b
apR2b a b = pure (\b a -> b) <*> b <*> a
现在,即使是您的示例也不一定总是有效,因为效果的 顺序 不同——它首先“执行”b
,在 a
的。