我曾经写过一个函数,它接受一个值作为输入,调用该输入的函数,如果结果是Just x
,它应该返回x
;否则,它应该返回原始输入。
换句话说,这个功能(我不知道该怎么称呼):
foo :: (a -> Maybe a) -> a -> a
foo f x = fromMaybe x (f x)
由于它似乎是一个通用功能,我想知道它是否已经定义,所以I asked on Twitter和Chris Allen replied它是ap fromMaybe
。
这听起来很有希望,所以我解雇了GHCI并开始尝试:
Prelude Control.Monad Data.Maybe> :type ap
ap :: Monad m => m (a -> b) -> m a -> m b
Prelude Control.Monad Data.Maybe> :type fromMaybe
fromMaybe :: a -> Maybe a -> a
Prelude Control.Monad Data.Maybe> :type ap fromMaybe
ap fromMaybe :: (b -> Maybe b) -> b -> b
ap fromMaybe
的类型看起来肯定是正确的,并且一些实验似乎表明它也具有所需的行为。
但它是如何运作的?
fromMaybe
函数对我来说似乎很清楚,我认为我理解ap
的作用 - 至少在Maybe
的背景下。当m
为Maybe
时,其类型为Maybe (a -> b) -> Maybe a -> Maybe b
。
我不明白ap fromMaybe
甚至编译的方式。对我来说,这个表达看起来像是部分应用,但我可能会弄错。但是,如果是这种情况,我不明白这些类型是如何匹配的。
ap
的第一个参数是m (a -> b)
,但fromMaybe
的类型为a -> Maybe a -> a
。那怎么样?编译器推断Monad
是哪个m
实例?带有两个(curried)参数的fromMaybe
如何变成一个只带一个参数的函数?
有人可以帮我连接点吗?
答案 0 :(得分:20)
但在ap
的上下文中使用Maybe
不是。我们将其与函数fromMaybe
一起使用,因此它在函数的上下文中,其中
ap f g x = f x (g x)
我们拥有的各种Monad
个实例
instance Monad ((->) r)
所以它是
ap :: Monad m => m (a -> b) -> m a -> m b
fromMaybe :: r -> (Maybe r -> r)
ap :: (r -> (a -> b)) -> (r -> a) -> (r -> b)
ap f g x :: b
ap fromMaybe :: (r -> a) -> (r -> b) , a ~ Maybe r , b ~ r
因为类型中的->
与右侧相关联:a -> b -> c ~ a -> (b -> c)
。试图将类型插入到一起,我们最终只能得到上面的定义。
使用(<*>) :: Applicative f => f (a -> b) -> f a -> f b
,我们可以将其写为(fromMaybe <*>)
,如果你喜欢这种涂鸦:
#> :t (fromMaybe <*>)
(fromMaybe <*>) :: (r -> Maybe r) -> r -> r
正如在这里的另一个答案中正确地指出的那样,当与函数一起使用时,<*>
只是你的好人。 S combinator。我们不能在Haskell中使用名为S
的函数,因此<*>
只是无点编码风格的标准集的一部分。 Monadic bind(更多的是,翻转),=<<
,可能更加神秘,但是一个无点编程人员并不关心,并乐意用它来编码另一个类似的模式,
(f =<< g) x = f (g x) x
在combinatory函数调用中,神秘或无神秘(zipWith (-) =<< drop 1
浮现在脑海中)。
答案 1 :(得分:16)
为简洁和机械的答案道歉。我不喜欢樱桃挑选像Applicative或Monad这样的东西,但我不知道你在哪里。这是not my usual approach to teaching Haskell。
首先,ap
真的是(<*>)
。
Prelude> import Control.Monad
Prelude> import Data.Maybe
Prelude> import Control.Applicative
Prelude> :t ap
ap :: Monad m => m (a -> b) -> m a -> m b
Prelude> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
这是什么意思?这意味着我们不需要像Monad那样强大的东西来描述我们正在做的事情。适用就足够了。但是,Functor没有。
Prelude> :info Applicative
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Prelude> :info Functor
class Functor (f :: * -> *) where
fmap :: (a -> b) -> f a -> f b
以下是ap
/ (<*>)
与Maybe Monad / Applicative:
Prelude> ap (Just (+1)) (Just 1)
Just 2
Prelude> (<*>) (Just (+1)) (Just 1)
Just 2
要弄清楚的第一件事是,我们正在讨论类型类Applicative的哪个实例?
Prelude> :t fromMaybe
fromMaybe :: a -> Maybe a -> a
从Maye的类型中汲取一些东西给我们:
(->) a (Maybe a -> a)
所以我们在这里关注的类型构造函数是(->)
。 GHCi告诉我们有关(->)
也称为函数类型的内容是什么?
Prelude> :info (->)
data (->) a b -- Defined in ‘GHC.Prim’
instance Monad ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Applicative ((->) a) -- Defined in ‘GHC.Base’
槽糕。那可能呢?
Prelude> :info Maybe
data Maybe a = Nothing | Just a -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Applicative Maybe -- Defined in ‘GHC.Base’
使用(<*>)
对于Maybe的行为是:
Prelude> (+1) 1
2
Prelude> (+1) `fmap` Just 1
Just 2
Prelude> Just (+1) <*> Just 1
Just 2
Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
Prelude> let mFmap = fmap :: (a -> b) -> Maybe a -> Maybe b
Prelude> (+1) `mFmap` Just 1
Just 2
Prelude> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Prelude> let mAp = (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
Prelude> :t (+1)
(+1) :: Num a => a -> a
Prelude> :t Just (+1)
Just (+1) :: Num a => Maybe (a -> a)
Prelude> Just (+1) `mAp` Just 1
Just 2
好的,函数类型的Functor和Applicative怎么样?这里一个棘手的部分是(->)
部分应用类型作为Functor / Applicative / Monad。因此,f
成为整体(->) a
的{{1}},其中(->) a b
是参数类型,结果为a
。
b
不保证有用。您可以从类型中了解Prelude> (fmap (+1) (+2)) 0
3
Prelude> (fmap (+1) (+2)) 0
3
Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
Prelude> let funcMap = fmap :: (a -> b) -> (c -> a) -> c -> b
Prelude> -- f ~ (->) c
Prelude> (funcMap (+1) (+2)) 0
3
Prelude> :t (<*>)
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
Prelude> let funcAp = (<*>) :: (c -> a -> b) -> (c -> a) -> (c -> b)
Prelude> :t fromMaybe
fromMaybe :: a -> Maybe a -> a
Prelude> :t funcAp fromMaybe
funcAp fromMaybe :: (b -> Maybe b) -> b -> b
Prelude> :t const
const :: a -> b -> a
Prelude> :t funcAp const
funcAp const :: (b -> b1) -> b -> b
并不感兴趣,并了解参数化的工作原理。
编辑:说到撰写,funcAp const
的Functor只是(->) a
。适用的是,但有一个额外的论点。 Monad是应用程序,但有论点翻转。
进一步的问题:(.)
的适用<*>
是S,而(->) a
是SKI组合子微积分的K. (您可以从K和S派生出来。实际上,您可以从K和S派生出任何程序。)
pure
答案 2 :(得分:8)
为了清楚起见,我将重新标记类型参数。
ap :: Monad m => m (a -> b) -> m a -> m b
fromMaybe :: c -> Maybe c -> c
编译器推断m是哪个Monad实例?
((->) r)
是Monad
。对于某些特定的 r
,这是所有以r
类型为参数的函数。
所以在类型中:
ap :: Monad m => m (a -> b) -> m a -> m b
m
〜(c ->)
,a
〜Maybe c
和b
〜c
。
返回类型m a -> m b
扩展为(c -> Maybe c) -> c -> c
- 这是ap fromMaybe
的类型。
答案 3 :(得分:6)
如果您更喜欢中缀语法,那么您要查找的monad是(->) r
或r -> _
。
然后ap
的签名扩展为:
m (a -> b) -> m a -> m b =
(r -> (a -> b)) -> (r -> a) -> r -> b = -- now we use the signature of fromMaybe
(b -> (Maybe b -> b)) -> (b -> Maybe b) -> b -> b
现在,如果您将ap fromMaybe
视为部分应用的函数,那么您将获得所需的结果。