Function 函数的类型类实例
我刚刚意识到,函数有Monad、Functor和Applicative的实例Function 函数的类型类实例,function,haskell,monads,functor,applicative,Function,Haskell,Monads,Functor,Applicative,我刚刚意识到,函数有Monad、Functor和Applicative的实例 当我看到一些我没有得到的typeclass实例时,我通常会编写一些类型良好的表达式,看看它返回什么: 有人能解释一下这些例子吗?你通常会听说List和Maybe的实例,这对我来说很自然,但我不明白函数怎么可能是函子,甚至是单子 编辑: 好的,这是一个有效的类型良好的表达式,它不会编译: fmap (+) (+) 1 (+1) 1 首先,我同意你的观点:函数不像函子那样直观,事实上,我有时希望这些实例不存在。这并不是说
当我看到一些我没有得到的typeclass实例时,我通常会编写一些类型良好的表达式,看看它返回什么: 有人能解释一下这些例子吗?你通常会听说List和Maybe的实例,这对我来说很自然,但我不明白函数怎么可能是函子,甚至是单子 编辑: 好的,这是一个有效的类型良好的表达式,它不会编译:
fmap (+) (+) 1 (+1) 1
首先,我同意你的观点:函数不像函子那样直观,事实上,我有时希望这些实例不存在。这并不是说它们有时没有用处,而是它们经常被用在一种不必要和令人困惑的方式中。这些方法总是可以用更具体的组合词(特别是来自
Control.Arrow
的组合词)或等效但更具描述性的组合词替换
也就是说。。。为了理解函数函子,我建议您先考虑一下。在某种程度上,Map Int
非常类似于数组:它包含一些可以转换的元素(即fmap
over),您可以通过使用整数索引来访问单个元素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)]
请注意,元素的索引是保留的
现在,只要有一个repeat::a->mapka
生成器,这个实例实际上就可以适用于Map
。这是不存在的,因为通常有无限多个键,我们无法全部枚举它们,也无法平衡这样一个映射所需要的无限树。但是如果我们将自己限制为只有有限多个可能值的键类型(例如Bool
),那么我们就很好:
instance Applicative (Map Bool) where
pure x = Map.fromList [(False, x), (True, x)]
<*> = Map.intersectWith ($)
实例应用程序(Map Bool)其中
纯x=Map.fromList[(False,x),(True,x)]
=Map.intersectWith($)
现在,这正是函数monad的工作方式,与Map
不同的是,如果可能有无限多个不同的参数,则没有问题,因为您从未尝试将所有参数与相关值一起存储;相反,您总是只在现场计算值
†如果不是懒洋洋地完成,这将是不可行的——在哈斯克尔,这几乎不是问题,事实上,如果你在地图上fmap,它也会懒洋洋地发生。对于函数functor,fmap
实际上不仅是惰性的,而且结果也会立即被遗忘,需要重新计算。fmap
For functions作用于函数生成的结果:
GHCi> :set -XTypeApplications
GHCi> :t fmap @((->) _)
fmap @((->) _) :: (a -> b) -> (t -> a) -> t -> b
GHCi> :t join @((->) _)
join @((->) _) :: (t -> t -> a) -> t -> a
GHCi> join (*) 5
25
通过a->b
函数修改t->a
函数的a
结果。如果这听起来很像函数合成,那是因为它确实是:
GHCi> fmap (3 *) (1 +) 4
15
GHCi> ((3 *) <$> (1 +)) 4
15
GHCi> ((3 *) . (1 +)) 4
15
下面是一个以应用程序风格编写的示例,使用函数的Functor
和applicative
实例:
GHCi> ((&&) <$> (> 0) <*> (< 4)) 2
True
。。。用a
而不是其他任何东西生成t->a
函数。常量函数是实现此目的的唯一方法:
GHCi> pure 2 "foo"
2
GHCi> pure 2 42
2
请注意,pure 2
在上述每个示例中都有不同的类型
考虑到以上所有因素,Monad
实例令人惊讶地乏味。为了更清楚,让我们看一下(==)
:
那是你的密码吗?第一个问题是有一个解析错误:括号不匹配。是的,我修复了该代码片段中的Functor
实例仅用于列表,因为fmap
的第二个参数是列表。您实际上是在尝试将(+1)
作为+
的第一个参数,即添加一个函数。函数没有标准的+
重载。为什么你要尝试使用函数的头呢?“当我看到一些我没有得到的typeclass实例时,我通常做的是编写一些类型良好的表达式,看看它返回什么”——这是一个非常好的方法;只是,在这种情况下,正如其他人所指出的,根本不需要涉及列表,这可能使您的尝试偏离了轨道。您迭代了函数的所有可能输入?@hgiesel语义上是的。在操作上,当然不是;您只需输出一个函数,等待用户查询特定输入,然后修改该特定输入的输出。使用具有启发性的“数组”类比作为命名建议:fmap f arrayLikeFunction=\userRequest->f(arrayLikeFunction userRequest)
;或者使用Map
类比:fmap f mapLikeFunction=\key->f(mapLikeFunction键)
。
GHCi> ((&&) <$> (> 0) <*> (< 4)) 2
True
GHCi> :t pure @((->) _)
pure @((->) _) :: a -> t -> a
GHCi> pure 2 "foo"
2
GHCi> pure 2 42
2
GHCi> :t (=<<) @((->) _)
(=<<) @((->) _) :: (a -> t -> b) -> (t -> a) -> t -> b
GHCi> :t join @((->) _)
join @((->) _) :: (t -> t -> a) -> t -> a
GHCi> join (*) 5
25