2017-04-12 66 views
2

我刚刚意识到,函数具有Monad,Functor和Applicative的实例。类型实例函数

我最常做的,当我看到一些类型类实例我不明白,是写一些良好的输入表达式,看看它返回:

有人可以解释这些情况?你通常会听到关于List和Maybe的实例,这些实例现在对我来说很自然,但我不明白函数如何成为函子或Monad。

编辑: 好吧,这是一个有效的良好的输入表达式无法编译:

fmap (+) (+) 1 (+1) 1 
+1

那是你确切的代码?第一个问题是存在解析错误:括号不匹配。 –

+0

是的,我修正了它 – hgiesel

+1

在那个代码片段中,play的'Functor'实例仅仅用于列表,因为'fmap'的第二个参数是一个列表。你有效地试图将'(+1)'作为第一个参数给'+',即添加一个函数。函数没有标准的'+'超载。 – pigworker

回答

4

首先,我同意你的观点:功能不如仿函数非常直观,的确有时我真希望这些情况并不存在。这并不是说他们有时候并没有用处,但是他们经常以不必要和混乱的方式使用它们。这些方法总是可以替换为更具体的组合子(特别是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实际上不只是懒惰,但其结果也立即被遗忘,需要重新计算。

+0

你迭代每一个可能的输入到一个函数? – hgiesel

+0

@hgiesel语义上,是的。在运作上,当然不是;您只需输出一个等待用户查询特定输入的函数,然后修改该特定输入的输出。使用暗示的“数组”类比作为命名建议:'fmap f arrayLikeFunction = \ userRequest - > f(arrayLikeFunction userRequest)';或者使用'Map'类比:'fmap f mapLikeFunction = \ key - > f(mapLikeFunction key)'。 –

2

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型)一个参数的函数的是一个示例写在应用性风格,采用的函数的FunctorApplicative实例:

GHCi> ((&&) <$> (> 0) <*> (< 4)) 2 
True 

一个读取它是“喂2(> 0)(< 4)方式,和COMBI ne结果(&&)“。它可以用更简洁的方式写入liftA2Control.Applicative,但我相信拼写更有意义 - 揭示。

Applicativepure另一种方法...

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 

如果你比较这类型与(<*>)的一个,你会看到他们是相同的,不同之处在于第一个参数已经翻转。函数实例是一个例外情况,其中ApplicativeMonad做了基本相同的事情。

值得一提的是,从joinControl.Monad可用于使用值作为两个参数的函数的两个参数:

GHCi> :t join @((->) _) 
join @((->) _) :: (t -> t -> a) -> t -> a 
GHCi> join (*) 5 
25 
相关问题