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 yet:) ? Or can someone explain it to me with simple words ?
Lifting is a concept which allows you to transform a function into a corresponding function within another (usually more general) setting
take a look at http://haskell.org/haskellwiki/Lifting
Let's start with an example:
> 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
transforms a function of plain types to a function of these types wrapped in an Applicative
, such as lists, IO
, etc.
Another common lift is lift
from Control.Monad.Trans
. It transforms a monadic action of one monad to an action of a transformed monad.
In general, lifts "lift" a function/action into a "wrapped" type.
The best way to understand this, and monads etc and to understand why they are useful, is probably to code and use it. If there's anything you coded previously that you suspect can benefit from this (ie this will make that code shorter etc), just try it out and you'll easily grasp the concept.
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 -> a -> a) -> Foo a -> Foo a -> Foo a
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 (+)
Paul's and yairchu's are both good explanations.
I'd like to add that the function being lifted can have an arbitrary number of arguments and that they don't have to be of the same type. For example, you could also define a liftFoo1:
liftFoo1 :: (a -> b) -> Foo a -> Foo b
In general, the lifting of functions that take 1 argument is captured in the type class Functor
, and the lifting operation is called fmap
:
fmap :: Functor f => (a -> b) -> f a -> f b
Note the similarity with liftFoo1
's type. In fact, if you have liftFoo1
, you can make Foo
an instance of Functor
:
instance Functor Foo where
fmap = liftFoo1
Furthermore, the generalization of lifting to an arbitrary number of arguments is called applicative style. Don't bother diving into this until you grasp the lifting of functions with a fixed number of arguments. But when you do, Learn you a Haskell has a good chapter on this. The Typeclassopedia is another good document that describes Functor and Applicative (as well as other type classes; scroll down to the right chapter in that document).
Hope this helps!