Haskell中的“解除”是什么?


Answers:


179

升降机更是一种设计模式,而不是数学概念(尽管我希望周围的人现在会通过显示升降机是类别还是某种东西来反驳我)。

通常,您有一些带有参数的数据类型。就像是

data Foo a = Foo { ...stuff here ...}

假设您发现有很多使用Foo数字类型(IntDouble等)的方法,并且您一直在编写代码来解开这些数字,将它们相加或相乘,然后将其包装起来。您可以通过编写一次“拆封”代码来缩短此时间。传统上将此功能称为“提升”,因为它看起来像这样:

liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c

换句话说,您有一个使用两个参数的函数(例如(+)运算符)并将其转换为Foos的等效函数的函数。

所以现在你可以写

addFoo = liftFoo2 (+)

编辑:更多信息

您当然可以拥有liftFoo3liftFoo4依此类推。但是,这通常不是必需的。

从观察开始

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

同样有liftAliftA3。但是您实际上并不经常使用它们,因为还有另一个运算符

(<$>) = fmap

这使您可以编写:

result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4

该术语myFunction <$> arg1返回包装在Foo中的新函数。依次使用可以将其应用于下一个参数(<*>),依此类推。因此,现在,您不必拥有对每个辅助对象都具有的提升功能,而只需拥有一系列雏菊式的应用程序即可。


26
值得提醒的是,升降机应遵守标准法律lift id == id和规定lift (f . g) == (lift f) . (lift g)
卡洛斯·谢德格

13
升降机确实是“一个类别或某物”。卡洛斯(Carlos)刚刚列出了Functor定律,其中id.分别是某些类别的标识箭头和箭头组成。通常哈斯克尔说话的时候,有问题的类别是“Hask”,其箭头是Haskell函数(换句话说,id.指Haskell函数你熟悉和喜爱)。
丹·伯顿

3
这应该显示为instance Functor Foo,不是instance Foo Functor,对不对?我会自行编辑,但不确定100%。
合金2014年

2
没有应用程序的举升是=函子。我的意思是您有2个选择:函子或应用函子。第一个提升单参数功能,第二个提升多参数功能。就是这样。对?这不是火箭科学:)听起来像是。谢谢您的好回答!
jhegedus 2015年

2
@atc:这是部分应用程序。参见wiki.haskell.org/Partial_application
Paul Johnson

41

保罗和叶尔楚都是很好的解释。

我想补充一点,被提升的函数可以具有任意数量的参数,并且它们不必具有相同的类型。例如,您还可以定义一个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是描述另一个很好的文件函子应用型(以及其他类型的类;向下滚动到文档中的正确章)。

希望这可以帮助!


25

让我们从一个示例开始(添加了一些空白以使表述更加清晰):

> 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等。

另一个常见的电梯是liftControl.Monad.Trans。它将一个monad的monadic动作转换为一个已转换monad的动作。

一般而言,“升降机” 升降机的功能/动作成“包装”类型(所以原函数获取到工作“的包裹下”)。

理解这一点,monads等以及理解它们为何有用的最好方法可能是编写代码并使用它。如果您以前编码过的任何东西都怀疑可以从中受益(即,这会使该代码更短等),只需尝试一下,您就可以轻松地理解这个概念。


13

提升是一个概念,可让您在另一个(通常是更一般的)设置中将功能转换为相应的功能

看看http://haskell.org/haskellwiki/Lifting


40
是的,但是该页面开始为“我们通常以(协变)函子...开始”。并非完全适合新手。
保罗·约翰逊

3
但是“ functor”已链接,因此新手只需单击即可查看Functor是什么。诚然,链接页面不是很好。我需要获得一个帐户并解决该问题。
jrockway 2010年

10
这是我在其他函数式编程站点上看到的一个问题。每个概念都将根据其他(陌生的)概念进行解释,直到新手进入完整的圈子(并绕弯道)。必须与喜欢递归有关。
DNA

2
为此链接投票。电梯使一个世界和另一个世界之间建立联系。
eccstartup

3
仅当您已经了解该主题时,此类答案才是好的。
doubleOrt

-2

根据这本精妙的教程,函子是某个容器(例如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>


5
我给你打了个分数,因为“函子是一些容器”是巨魔味的火焰诱饵。示例:从某些功能到某种r类型的功能(让我们使用c各种功能)都是Functors。他们不“包含”任何c一个。在这种情况下,fmap是函数组合,它包含一个a -> b函数和r -> a一个,为您提供一个新的r -> b函数。仍然没有容器。如果可以的话,我会再次将其标记为最后一句话。
BMeph 2013年

1
另外,它fmap是一个函数,不会“等待”任何东西;作为容器的“容器”是整个提升的重点。同样,Monads(如果有的话)是提起的双重想法:Monad允许您使用已经被提起了一些正数的东西,就好像它只被提起过一次一样-这就是平展化
BMeph 2013年

1
@BMeph To waitto expectto anticipate是同义词。说“功能等待”是指“功能预期”。
2014年

@BMeph我要说的是,不要将函数视为函子是容器的反例,而应该将函数的理智的函子实例视为函数不是容器的反例。函数是从域到共域的映射,域是所有参数的叉积,共域是函数的输出类型。同样,列表是从Naturals到列表内部类型的映射(域->共域)。如果您记住该功能或不保留列表,它们将变得更加相似。
分号

@BMeph被认为更像是容器的唯一原因之一是,在许多语言中它们都可以被突变,而传统上函数则不能。但是在Haskell中,这也不是一个公平的声明,因为它们都不能被突变,并且都可以被复制-突变:b = 5 : af 0 = 55 f n = g n,都涉及到对“容器”进行伪突变。同样,列表通常完全存储在内存中,而功能通常作为计算存储的事实。但是,在两次通话之间不存储的备忘录/单行列表都打破了这种想法。
分号
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.