我刚刚意识到,函数具有Monad,Functor和Applicative的实例。类型实例函数
我最常做的,当我看到一些类型类实例我不明白,是写一些良好的输入表达式,看看它返回:
有人可以解释这些情况?你通常会听到关于List和Maybe的实例,这些实例现在对我来说很自然,但我不明白函数如何成为函子或Monad。
编辑: 好吧,这是一个有效的良好的输入表达式无法编译:
fmap (+) (+) 1 (+1) 1
我刚刚意识到,函数具有Monad,Functor和Applicative的实例。类型实例函数
我最常做的,当我看到一些类型类实例我不明白,是写一些良好的输入表达式,看看它返回:
有人可以解释这些情况?你通常会听到关于List和Maybe的实例,这些实例现在对我来说很自然,但我不明白函数如何成为函子或Monad。
编辑: 好吧,这是一个有效的良好的输入表达式无法编译:
fmap (+) (+) 1 (+1) 1
首先,我同意你的观点:功能不如仿函数非常直观,的确有时我真希望这些情况并不存在。这并不是说他们有时候并没有用处,但是他们经常以不必要和混乱的方式使用它们。这些方法总是可以替换为更具体的组合子(特别是Control.Arrow
),或者用等效的但更具描述性的reader monad替代。
这就是说...了解功能仿函数,我建议你先考虑Map
。在某种程度上,Map Int
与数组非常类似:它包含一些可以转换的元素(即fmap
),并且您可以通过索引使用整数访问各个元素。 Map
只允许“数组”在其中存在空位,并且从整数索引推广到任何可以排序的索引。
尽管如此,Map
只是函数的具体实现:它将参数(键)与结果(值)相关联。这应该很清楚地说明函数函子如何工作:它覆盖函数的所有可能结果†。
这个解释很遗憾没有太多解释Monad
实例,因为Map
实际上并没有一个monad(甚至是Applicative
)实例。列表/数组实现的一个直接的适应确实会是不可能的......回顾:上表中,我们有
pure x ≡ [x]
(,) <$> [a,b] <*> [x,y,z] ≡ [(a,x),(a,y),(a,z),(b,x),(b,y),(b,z)]
所以合并之后,指数都不同。对于我们想要支持通用密钥的Map
,这不起作用。
然而,有一个为列表的替代单子例如,拉链列表:
pure x ≡ repeat x
(,) <$> [a,b] <*> [x,y,z] ≡ [(a,x),(b,y)]
注意,元素的索引将被保留。
现在这个实例实际上可以适用于Map
,如果只有repeat :: a -> Map k a
发生器。这是不存在的,因为一般情况下有无数个密钥,我们不能枚举它们,也不能平衡这样一个Map
需要的无限树。但是,如果我们限制密钥类型只有有限个可能的值(如Bool
),那么,我们是很好的:
instance Applicative (Map Bool) where
pure x = Map.fromList [(False, x), (True, x)]
<*> = Map.intersectWith ($)
现在,这也正是该功能单子作品,只是不像Map
怎么也没有问题如果无限多个不同的参数是可能的,因为你永远不会试图存储所有的与关联的值;相反,你总是只能当场计算值。
†,如果它不是懒洋洋地做这将是不可行的 - 这在Haskell是很难的一个问题,其实如果你FMAP在Map
也恰好懒洋洋地。对于功能仿函数,fmap
实际上不只是懒惰,但其结果也立即被遗忘,需要重新计算。
你迭代每一个可能的输入到一个函数? – hgiesel
@hgiesel语义上,是的。在运作上,当然不是;您只需输出一个等待用户查询特定输入的函数,然后修改该特定输入的输出。使用暗示的“数组”类比作为命名建议:'fmap f arrayLikeFunction = \ userRequest - > f(arrayLikeFunction userRequest)';或者使用'Map'类比:'fmap f mapLikeFunction = \ key - > f(mapLikeFunction key)'。 –
fmap
为功能作用在由函数产生的结果:
GHCi> :set -XTypeApplications
GHCi> :t fmap @((->) _)
fmap @((->) _) :: (a -> b) -> (t -> a) -> t -> b
的t -> a
函数的a
结果通过一个a -> b
功能修改。如果这听起来很像函数组合,这是因为它正是:
GHCi> fmap (3 *) (1 +) 4
15
GHCi> ((3 *) <$> (1 +)) 4
15
GHCi> ((3 *) . (1 +)) 4
15
(<*>)
是有点麻烦:
GHCi> :t (<*>) @((->) _)
(<*>) @((->) _) :: (t -> a -> b) -> (t -> a) -> t -> b
的Applicative f => f (a -> b)
争论变得t -> (a -> b)
。
GHCi> :t \k f -> \x -> k x (f x)
\k f -> \x -> k x (f x) :: (t2 -> t -> t1) -> (t2 -> t) -> t2 -> t1
这里:(<*>)
通过使用辅助功能(t -> a
类型的),以产生从所述第一,第二个参数的两个参数(t -> a -> b
类型的)的函数转换成(的t -> b
型)一个参数的函数的是一个示例写在应用性风格,采用的函数的Functor
和Applicative
实例:
GHCi> ((&&) <$> (> 0) <*> (< 4)) 2
True
一个读取它是“喂2
到(> 0)
和(< 4)
方式,和COMBI ne结果(&&)
“。它可以用更简洁的方式写入liftA2
Control.Applicative
,但我相信拼写更有意义 - 揭示。
的Applicative
,pure
另一种方法...
GHCi> :t pure @((->) _)
pure @((->) _) :: a -> t -> a
...使得t -> a
功能出a
,别无其他的。常数函数是这样做的唯一方法:
GHCi> pure 2 "foo"
2
GHCi> pure 2 42
2
请注意pure 2
在上面的每个例子不同的类型。
鉴于上述所有情况,Monad
实例令人惊讶地没有趣味。对于额外的清晰度,让我们来看看而不是在(>>=)
:
GHCi> :t (=<<) @((->) _)
(=<<) @((->) _) :: (a -> t -> b) -> (t -> a) -> t -> b
如果你比较这类型与(<*>)
的一个,你会看到他们是相同的,不同之处在于第一个参数已经翻转。函数实例是一个例外情况,其中Applicative
和Monad
做了基本相同的事情。
值得一提的是,从join
Control.Monad
可用于使用值作为两个参数的函数的两个参数:
GHCi> :t join @((->) _)
join @((->) _) :: (t -> t -> a) -> t -> a
GHCi> join (*) 5
25
那是你确切的代码?第一个问题是存在解析错误:括号不匹配。 –
是的,我修正了它 – hgiesel
在那个代码片段中,play的'Functor'实例仅仅用于列表,因为'fmap'的第二个参数是一个列表。你有效地试图将'(+1)'作为第一个参数给'+',即添加一个函数。函数没有标准的'+'超载。 – pigworker