第一个很酷的地方就是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
我们经常将函数的类型从ato表示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 bto 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 -> bsumb21aa -> clengthc3bcb -> c -> ddivided7
所以,因为 Function 类型可以支持mapand ap,所以我们可以converge免费获得(通过lift、lift2和lift3)。我实际上想converge从 Ramda 中删除,因为它没有必要。
请注意,我故意避免R.lift在此答案中使用。由于决定支持任何数量的功能,它具有无意义的类型签名和复杂的实现。另一方面,Sanctuary 的特定于 arity 的提升函数具有清晰的类型签名和简单的实现。