What is “lifting” in Haskell?

后端 未结 5 697
南方客
南方客 2020-12-22 16:07

I don\'t understand what \"lifting\" is. Should I first understand monads before understanding what a \"lift\" is? (I\'m completely ignorant about monads, too :) Or can some

5条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-12-22 16:18

    Lifting is more of a design pattern than a mathematical concept (although I expect someone around here will now refute me by showing how lifts are a category or something).

    Typically you have some data type with a parameter. Something like

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

    Suppose you find that a lot of uses of Foo take numeric types (Int, Double etc) and you keep having to write code that unwraps these numbers, adds or multiplies them, and then wraps them back up. You can short-circuit this by writing the unwrap-and-wrap code once. This function is traditionally called a "lift" because it looks like this:

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

    In other words you have a function which takes a two-argument function (such as the (+) operator) and turns it into the equivalent function for Foos.

    So now you can write

    addFoo = liftFoo2 (+)
    

    Edit: more information

    You can of course have liftFoo3, liftFoo4 and so on. However this is often not necessary.

    Start with the observation

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

    But that is exactly the same as fmap. So rather than liftFoo1 you would write

    instance Functor Foo where
       fmap f foo = ...
    

    If you really want complete regularity you can then say

    liftFoo1 = fmap
    

    If you can make Foo into a functor, perhaps you can make it an applicative functor. In fact, if you can write liftFoo2 then the applicative instance looks like this:

    import Control.Applicative
    
    instance Applicative Foo where
       pure x = Foo $ ...   -- Wrap 'x' inside a Foo.
       (<*>) = liftFoo2 ($)
    

    The (<*>) operator for Foo has the type

    (<*>) :: Foo (a -> b) -> Foo a -> Foo b
    

    It applies the wrapped function to the wrapped value. So if you can implement liftFoo2 then you can write this in terms of it. Or you can implement it directly and not bother with liftFoo2, because the Control.Applicative module includes

    liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
    

    and likewise there are liftA and liftA3. But you don't actually use them very often because there is another operator

    (<$>) = fmap
    

    This lets you write:

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

    The term myFunction <$> arg1 returns a new function wrapped in Foo. This in turn can be applied to the next argument using (<*>), and so on. So now instead of having a lift function for every arity, you just have a daisy chain of applicatives.

提交回复
热议问题