Answers:
升降机更是一种设计模式,而不是数学概念(尽管我希望周围的人现在会通过显示升降机是类别还是某种东西来反驳我)。
通常,您有一些带有参数的数据类型。就像是
data Foo a = Foo { ...stuff here ...}
假设您发现有很多使用Foo
数字类型(Int
,Double
等)的方法,并且您一直在编写代码来解开这些数字,将它们相加或相乘,然后将其包装起来。您可以通过编写一次“拆封”代码来缩短此时间。传统上将此功能称为“提升”,因为它看起来像这样:
liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c
换句话说,您有一个使用两个参数的函数(例如(+)
运算符)并将其转换为Foos的等效函数的函数。
所以现在你可以写
addFoo = liftFoo2 (+)
编辑:更多信息
您当然可以拥有liftFoo3
,liftFoo4
依此类推。但是,这通常不是必需的。
从观察开始
liftFoo1 :: (a -> b) -> Foo a -> Foo b
但这与完全相同fmap
。所以,而不是liftFoo1
你会写
instance Functor Foo where
fmap f foo = ...
如果您真的想要完整的规律性,可以说
liftFoo1 = fmap
如果可以制成Foo
函子,也许可以使其成为可应用的函子。实际上,如果可以编写,liftFoo2
则应用实例如下所示:
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap 'x' inside a Foo.
(<*>) = liftFoo2 ($)
(<*>)
Foo 的运算符具有以下类型
(<*>) :: Foo (a -> b) -> Foo a -> Foo b
它将包装的函数应用于包装的值。因此,如果您可以实施,liftFoo2
则可以按照它来编写。或者,您可以直接实现它,而不必为烦恼liftFoo2
,因为该Control.Applicative
模块包括
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
同样有liftA
和liftA3
。但是您实际上并不经常使用它们,因为还有另一个运算符
(<$>) = fmap
这使您可以编写:
result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4
该术语myFunction <$> arg1
返回包装在Foo中的新函数。依次使用可以将其应用于下一个参数(<*>)
,依此类推。因此,现在,您不必拥有对每个辅助对象都具有的提升功能,而只需拥有一系列雏菊式的应用程序即可。
lift id == id
和规定lift (f . g) == (lift f) . (lift g)
。
id
和.
分别是某些类别的标识箭头和箭头组成。通常哈斯克尔说话的时候,有问题的类别是“Hask”,其箭头是Haskell函数(换句话说,id
并.
指Haskell函数你熟悉和喜爱)。
instance Functor Foo
,不是instance Foo Functor
,对不对?我会自行编辑,但不确定100%。
保罗和叶尔楚都是很好的解释。
我想补充一点,被提升的函数可以具有任意数量的参数,并且它们不必具有相同的类型。例如,您还可以定义一个liftFoo1:
liftFoo1 :: (a -> b) -> Foo a -> Foo b
通常,在类class中捕获带有1个参数的函数Functor
的提升,该提升操作称为fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
请注意与liftFoo1
的类型相似。实际上,如果您拥有liftFoo1
,则可以创建Foo
以下实例Functor
:
instance Functor Foo where
fmap = liftFoo1
此外,将提升到任意数量的参数的一般化称为应用样式。在您掌握有固定数量的参数的函数提升之前,请不要花时间进行深入研究。但是,当您这样做时,向您了解Haskell的内容就很好。该Typeclassopedia是描述另一个很好的文件函子和应用型(以及其他类型的类;向下滚动到文档中的正确章)。
希望这可以帮助!
让我们从一个示例开始(添加了一些空白以使表述更加清晰):
> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"
liftA2
将普通类型的函数转换为封装在中的相同类型Applicative
的函数,例如列表IO
等。
另一个常见的电梯是lift
从Control.Monad.Trans
。它将一个monad的monadic动作转换为一个已转换monad的动作。
一般而言,“升降机” 升降机的功能/动作成“包装”类型(所以原函数获取到工作“的包裹下”)。
理解这一点,monads等以及理解它们为何有用的最好方法可能是编写代码并使用它。如果您以前编码过的任何东西都怀疑可以从中受益(即,这会使该代码更短等),只需尝试一下,您就可以轻松地理解这个概念。
提升是一个概念,可让您在另一个(通常是更一般的)设置中将功能转换为相应的功能
根据这本精妙的教程,函子是某个容器(例如Maybe<a>
,List<a>
或者Tree<a>
可以存储其他类型的元素a
)。我使用Java泛型符号表示<a>
元素类型,a
并将这些元素视为树上的浆果Tree<a>
。有一个函数fmap
,它带有元素转换函数,a->b
还有container functor<a>
。它适用a->b
于容器的每个元素,有效地将其转换为functor<b>
。如果仅提供第一个参数a->b
,则fmap
等待functor<a>
。也就是说,a->b
仅提供就可以将此元素级别的功能转换为functor<a> -> functor<b>
对容器进行操作的功能。这叫做起重功能的 因为容器也称为函子,所以函子而不是Monad是提升的先决条件。Monad有点类似于提升。两者都依赖于Functor概念并且这样做f<a> -> f<b>
。区别在于a->b
转换使用提升功能,而Monad需要用户定义a -> f<b>
。
r
类型的功能(让我们使用c
各种功能)都是Functors。他们不“包含”任何c
一个。在这种情况下,fmap是函数组合,它包含一个a -> b
函数和r -> a
一个,为您提供一个新的r -> b
函数。仍然没有容器。如果可以的话,我会再次将其标记为最后一句话。
fmap
是一个函数,不会“等待”任何东西;作为容器的“容器”是整个提升的重点。同样,Monads(如果有的话)是提起的双重想法:Monad允许您使用已经被提起了一些正数的东西,就好像它只被提起过一次一样-这就是平展化。
To wait
,to expect
,to anticipate
是同义词。说“功能等待”是指“功能预期”。
b = 5 : a
和f 0 = 55
f n = g n
,都涉及到对“容器”进行伪突变。同样,列表通常完全存储在内存中,而功能通常作为计算存储的事实。但是,在两次通话之间不存储的备忘录/单行列表都打破了这种想法。