适用运算符 <* 和 *>,类型签名含义

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的。