第一个很酷的地方就是a -> b
可以支持map
。是的,函数就是函子!
让我们考虑以下类型map
:
map :: Functor f => (b -> c) -> f b -> f c
让我们替换Functor f => f
为Array
给我们一个具体的类型:
map :: (b -> c) -> Array b -> Array c
让我们Functor f => f
用Maybe
这个时间替换:
map :: (b -> c) -> Maybe b -> Maybe c
相关性很明显。让我们Functor f => f
用Either a
, 替换来测试二进制类型:
map :: (b -> c) -> Either a b -> Either a c
我们经常将函数的类型从a
to表示b
为 as a -> b
,但这实际上只是 for 的糖Function a b
。让我们使用长格式并Either
在上面的签名中替换为Function
:
map :: (b -> c) -> Function a b -> Function a c
因此,对函数的映射为我们提供了一个函数,它将将该b -> c
函数应用于原始函数的返回值。a -> b
我们可以使用糖重写签名:
map :: (b -> c) -> (a -> b) -> (a -> c)
注意到什么了吗?是什么类型的compose
?
compose :: (b -> c) -> (a -> b) -> a -> c
所以compose
只是map
专门针对函数类型!
第二个很酷的地方是a -> b
可以支持ap
。函数也是应用函子!这些在 Fantasy Land 规范中称为Apply 。
让我们考虑以下类型ap
:
ap :: Apply f => f (b -> c) -> f b -> f c
让我们替换Apply f => f
为Array
:
ap :: Array (b -> c) -> Array b -> Array c
现在,与Either a
:
ap :: Either a (b -> c) -> Either a b -> Either a c
现在,与Function a
:
ap :: Function a (b -> c) -> Function a b -> Function a c
是什么Function a (b -> c)
?这有点令人困惑,因为我们混合了两种样式,但它是一个函数,它接受一个类型的值a
并返回一个函数 from b
to c
。a -> b
让我们使用样式重写:
ap :: (a -> b -> c) -> (a -> b) -> (a -> c)
任何支持map
并ap
可以“提升”的类型。让我们来看看lift2
:
lift2 :: Apply f => (b -> c -> d) -> f b -> f c -> f d
请记住,它Function a
满足 Apply 的要求,因此我们可以替换Apply f => f
为Function a
:
lift2 :: (b -> c -> d) -> Function a b -> Function a c -> Function a d
哪个写得更清楚:
lift2 :: (b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d)
让我们重新审视您的初始表达式:
// average :: Number -> Number
const average = lift2(divide, sum, length);
做什么average([6, 7, 8])
?( ) 被赋予函数 ( a
) ,产生( )。也赋予函数 ( ) ,产生( )。现在我们有了 a和 a,我们可以将它们提供给函数 ( ) 以生成( ),这是最终结果。[6, 7, 8]
a -> b
sum
b
21
a
a -> c
length
c
3
b
c
b -> c -> d
divide
d
7
所以,因为 Function 类型可以支持map
and ap
,所以我们可以converge
免费获得(通过lift
、lift2
和lift3
)。我实际上想converge
从 Ramda 中删除,因为它没有必要。
请注意,我故意避免R.lift
在此答案中使用。由于决定支持任何数量的功能,它具有无意义的类型签名和复杂的实现。另一方面,Sanctuary 的特定于 arity 的提升函数具有清晰的类型签名和简单的实现。