什么是Haskell的“提升”?

时间:2010-03-07 08:58:09

标签: haskell functional-programming

我不明白“提升”是什么。在了解“电梯”是什么之前我应该​​先了解单子吗? (我对monads也完全无知:)或者有人可以用简单的词语向我解释一下吗?

5 个答案:

答案 0 :(得分:164)

提升更多的是一种设计模式,而不是一种数学概念(虽然我希望周围的人现在可以通过展示升降机是一类什么类型来反驳我。)

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

这样的东西
data Foo a = Foo { ...stuff here ...}

假设您发现Foo的大量使用采用数字类型(IntDouble等)并且您不得不编写解包这些数字的代码,添加或相乘它们,然后把它们包起来。您可以通过编写一次unwrap-and-wrap代码来使其短路。此功能传统上称为“提升”,因为它看起来像这样:

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 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中的新函数。这反过来可以使用(<*>)应用于下一个参数,依此类推。因此,现在不是为每个arity提供升力功能,而是只有一个菊花链应用程序。

答案 1 :(得分:36)

保罗和yairchu都是很好的解释。

我想补充说,被解除的函数可以有任意数量的参数,并且它们不必是相同的类型。例如,您还可以定义liftFoo1:

liftFoo1 :: (a -> b) -> Foo a -> Foo b

通常,在类型类Functor中捕获取1个参数的函数,并将提升操作称为fmap

fmap :: Functor f => (a -> b) -> f a -> f b

注意与liftFoo1类型的相似性。事实上,如果您有liftFoo1,则可以Foo Functor的实例:

instance Functor Foo where
  fmap = liftFoo1

此外,提升到任意数量的参数的概括称为 applicative style 。在掌握具有固定数量参数的函数解除之前,不要费心去做。但是,当你这样做时,Learn you a Haskell有一个很好的章节。 Typeclassopedia是另一个描述 Functor Applicative 的良好文档(以及其他类型类;向下滚动到该文档的右侧章节)。

希望这有帮助!

答案 2 :(得分:22)

让我们从一个例子开始:

> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> a -> [a]
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f a -> f [a]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)

liftA2将普通类型的函数转换为包含在Applicative中的这些类型的函数,例如列表,IO等。

来自lift的另一个常见提升是Control.Monad.Trans。它将一个monad的monadic动作转换为变形monad的动作。

一般情况下,升降机将一个功能/动作“提升”为“包裹”类型。

了解这一点的最佳方法,monad等以及了解它们有用的原因,可能是编码和使用它。如果您之前编写过任何您怀疑可以从中受益的内容(即这会使代码更短等),只需尝试一下,您就可以轻松掌握这一概念。

答案 3 :(得分:11)

提升是一个概念,它允许您将一个函数转换为另一个(通常是更一般的)设置中的相应函数

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

答案 4 :(得分:0)

根据this shiny tutorial,仿函数是一些容器(如Maybe<a>List<a>Tree<a>,可以存储其他类型的元素a) 。我已经将Java泛型表示法<a>用于元素类型a,并将这些元素视为树Tree<a>上的浆果。有一个函数fmap,它带有元素转换函数a->b和容器functor<a>。它将a->b应用于容器的每个元素,有效地将其转换为functor<b>。当仅提供第一个参数时,a->bfmap等待functor<a>。也就是说,仅提供a->b会将此元素级别的函数转换为对容器进行操作的函数functor<a> -> functor<b>。这称为函数的提升。因为容器也称为仿函数,所以Functors而不是Monads是提升的先决条件。 Monads与提升有点“平行”。两者都依赖于Functor概念而做f<a> -> f<b>。不同之处在于,提升使用a->b进行转换,而Monad要求用户定义a -> f<b>