Haskell: Understanding the pure function for Applicative functors - haskell

I am learning about Applicative Functors and the pure function has the following type declaration:
pure :: a -> f a
I understand that the pure function takes a value of any type and returns an applicative value with that value inside it. So if the applicative instance was Maybe, pure 3 would give Just 3.
However, what happens when you apply pure to a value that's already inside an applicative value? E.g. what happens if you do something like pure Just 3?

What happens when you apply pure to a value that's already inside an applicative value?
It just gets wrapped in an additional layer. The end result is an applicative value nested inside another applicative value.
E.g. what happens if you do something like pure Just 3?
This is an interesting question, although probably not for the reasons you meant. The important point here is to distinguish pure (Just 3) — which was probably what you meant — from pure Just 3 = (pure Just) 3, which is what you wrote. This gives two scenarios:
pure (Just 3) simply applies pure to the value Just 3, which — as I discussed above — gives Just (Just 3), which is a nested applicative value.
(pure Just) 3 is an interesting case. Recall the types of pure and Just:
pure :: Applicative f => a -> f a
Just :: a -> Maybe a
-- so:
pure Just :: Applicative f => f (a -> Maybe a)
In other words, pure Just takes the function Just and wraps it inside an applicative value.
Next, we want to take pure Just and apply it to a value 3. But we can’t do this, since f (a -> Maybe a) is a value, not a function! So (pure Just) 3 should result in a type error.
…except it turns out to typecheck just fine! So we’re missing something. In this case, it turns out there is an applicative instance for functions:
instance Applicative ((->) r) where
pure x = \r -> x
(<*>) = _irrelevant_here
The syntax is a bit funny, but it basically means that r -> ... is an applicative. This particular instance is known as the Reader monad, and it’s very widely used. (For more about this particular data type, see e.g. here or here.) The idea is that r -> a can compute an a given an r input; in this case, pure x creates a function which ignores its input and returns x always, and f <*> x feeds the r input into both f and x, then combines the two. In this case, we’re only using pure, so it’s easy to evaluate (pure Just) 3 by hand:
(pure Just) 3
= (\r -> Just) 3
= Just
So the idea here is that pure wraps Just in an applicative value, which in this case happens to be a function; then, we apply this function to 3, which gets rid of the wrapper to reveal the original Just.

First of all, pure has the type:
pure :: Applicative f => a -> f a
To make things simpler, think of the kind of f
:k f
f :: * -> *
and the kind of a is *
then the type of a, is just a, any a, the most polymorphic of all (but with kind * remember). So you don't really care the value of a, you just have a restriction, and that's the typeclass Applicative, and the kind of f (remember * -> *)
so in this case:
gchi> pure 3 :: Maybe Int
ghci> Just 3
here f is Maybe and a is 3
In the same way
gchi> pure $ Just 3 :: Maybe (Maybe Int)
gchi> Just (Just 3)
here f is again Maybe and a is Just 3
and you can play a little changing the type to pure:
gchi> pure 3 :: [Double]
ghci> [3.0]
here, f is [], and a is 3
same way
ghci> pure [3] :: [[Double]]
ghci> [[3.0]]
finally here, f again, is [] and a is [3]

Related

what does (<*>) :: f (a -> b) -> f a -> f b exactly do in the Functor class

class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
From my understanding, it takes a function f, where another function (a -> b) as its argument, returns a function f. Applying f to a then returns a function f and apply f to b.
Here is an example:
Prelude> (+) <$> Just 2 <*> Just 3
Just 5
But I don't quite understand how it works.
I guess (+) should be f, Just 2 and Just 3 should be a and b respectively. Then what is (a -> b)?
From my understanding, it takes a function f...
Unfortunately this is incorrect. In this case, f is a type, not a function. Specifically, f is a "higher-kinded type" with kind * -> *. The type f is the functor.
In this case, f is Maybe. So we can rewrite the function types, specializing them for Maybe:
pure :: a -> Maybe a
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
It starts to become a bit clearer once you get this far. There are a couple different possible definitions for pure, but only one that makes sense:
pure = Just
The operator x <$> y is the same as pure x <*> y, so if you write out:
(+) <$> Just 2 <*> Just 3
Then we can rewrite it as:
pure (+) <*> pure 2 <*> pure 3
Although this technically has a more general type. Working with the functor laws, we know that pure x <*> pure y is the same as pure (x y), so we get
pure ((+) 2) <*> pure 3
pure ((+) 2 3)
pure (2 + 3)
In this case, we a and b are the types but since <*> appears twice they actually have different types in each case.
In the first <*>, a is Int and b is Int -> Int.
In the second <*>, both a and b are Int. (Technically you get generic versions of Int but that's not really important to the question.)
Applicative functors were introduced to Haskell as applicative style programming "Idioms". Unpacking this phrase, we have "applicative style programming"; which is just application of functions to arguments. We also have "idioms", or phrases in a language which have a special meaning. For example "raining cats and dogs" is an idiom for raining very heavily. Putting them together, applicative functors are function applications with special meaning.
Take for example, following Dietrich Epp's lead, anApplication defined by a function,
anApplication = f a
where
f = (+2)
a = 3
and, anIdiomaticApplication, defined with idiomatic application,
anIdiomaticApplication = f <*> a
where
f = Just (+2)
a = Just 3
The top level structure of these definitions are similar. The difference? The first has a space--normal function application--and the second has <*>--idiomatic function application. This illustrates how <*> facilitates applicative style: just use <*> in place of a space.
The application, <*>, is idiomatic because it carries a meaning other than just pure function application. By way of exposition, in anIdiomaticApplication we have something like this:
f <*> a :: Maybe (Int -> Int) <*> Maybe Int
Here, the <*> in the type is used to represent a type level function* that corresponds to the signature of the real <*>. To the type-<*> we apply the type arguments for f and a (respectively Maybe (Int -> Int) and Maybe Int). After application we have
f <*> a :: Maybe Int
As an intermediate step, we can imagine something like
f <*> a :: Maybe ((Int -> Int) _ Int)
With _ being the type level stand-in for regular function application.
At this point we can finally see the idiom-ness called out. f <*> a is like a normal function application, (Int -> Int) _ Int, in the Maybe context/idiom. So, <*> is just function application that happens within a certain context.
In parting, I'll emphasize that understanding <*> is only partially understanding its use. We can understand that f <*> a is just function application which some extra idiomatic meaning. Due to the Applicative laws, we can also assume that idiomatic application will be somehow sensible.
Don't be surprised, however, if you look at <*> and get confused since there is so little there. We must also be versed in the various Haskell Idioms. For instance, in the Maybe idiom either the function or value may not be present, in which case the output will be Nothing. There are of course, many others, but getting familiar with just Either a and State s should model a wide variety of the different kinds.
*Something like this could actually be made with a closed type family (untested)
type family IdmApp f a where
IdmApp (f (a->b)) a = f b

Multiplying the value within two "Maybe" monads?

I'm currently in the process of trying to learn Haskell, and ran into an odd issue regarding the Maybe monad which I can't seem to figure out.
As an experiment, I'm currently trying to take a string, convert each letter to an arbitrary number, and multiply/combine them together. Here's what I have so far:
lookupTable :: [(Char, Int)]
lookupTable = [('A', 1), ('B', 4), ('C', -6)]
strToInts :: String -> [Maybe Int]
strToInts = map lookupChar
where
lookupChar :: Char -> Maybe Int
lookupChar c = lookup c lookupTable
-- Currently fails
test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
test seq = [ x * y | (x, y) <- zip seq $ tail seq, x < y ]
main :: IO ()
main = do
putStrLn $ show $ test $ strToInts "ABC"
When I try running this, it returns the following error:
test.hs:13:16:
Could not deduce (Num (Maybe n)) arising from a use of `*'
from the context (Num n, Ord n)
bound by the type signature for
test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
at test.hs:12:9-48
Possible fix: add an instance declaration for (Num (Maybe n))
In the expression: x * y
In the expression: [x * y | (x, y) <- zip seq $ tail seq]
In an equation for `test':
test seq = [x * y | (x, y) <- zip seq $ tail seq]
I'm not 100% sure why this error is occurring, or what it exactly means, though I suspect it might be because I'm trying to multiply two Maybe monads together -- if I change the definition of test to the following, the program compiles and runs fine:
test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
test seq = [ x | (x, y) <- zip seq $ tail seq, x < y ]
I also tried changing the type declaration to the below, but that didn't work either.
test :: (Num n, Ord n) => [Maybe n] -> [Num (Maybe n)]
I'm not really sure how to go about fixing this error. I'm fairly new to Haskell, so it might just be something really simple that I'm missing, or that I've structured everything completely wrong, but this is stumping me. What am I doing wrong?
Maybe does not have a num instance so you cannot multiply them together directly. You need to somehow apply the pure function to the values inside the context. This is exactly what applicative functors are for!
Applicative functors live in Control.Applicative:
import Control.Applicative
So you have this function and you want to apply it to 2 arguments in a context:
(*) :: Num a => a -> a -> a
You probably learned about fmap, it takes a function and applies it to a value in a context. <$> is an alias for fmap. When we fmap the pure function over the maybe value we get the following result:
(*) <$> Just 5 :: Num a => Maybe (a -> a)
So now we have maybe a function and we need to apply it to maybe a value, this is exactly what the applicative functor does. Its main operator is <*> which has the signature:
(<*>) :: f (a -> b) -> f a -> f b
When we specialize it we get the function we need:
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
We apply it and the output is the number you expect.
(*) <$> Just 5 <*> Just 5 :: Num a => Maybe a
So to make your code compile you need to change your test function to use <$> and <*>, see if you can figure out how.
Reite's answer is correct, and it's generally how I'd normally recommend handling it - however, it seems to me that you don't quite understand how to work with Maybe values; if so there is little sense looking at applicative functors right now.
The definition of Maybe is basically just
data Maybe a = Nothing | Just a
Which basically means exactly what it sounds like when you read it in plain english "A value of type Maybe a is either the value Nothing for that type, or a value of the form Just a".
Now you can use pattern matching to work with that, with the example of lists:
maybeReverse :: Maybe [a] -> Maybe [a]
maybeReverse Nothing = Nothing
maybeReverse (Just xs) = Just $ reverse xs
Which basically means "If the value is Nothing, then there's nothing to reverse, so the result is Nothing again. If the value is Just xs then we can reverse xs and wrap it with Just again to turn it into a Maybe [a] value).
Of course writing functions like this for every single function we ever want to use with a Maybe value would be tedious; So higher order functions to the rescue! The observation here is that in maybeReverse we didn't do all that much with reverse, we just applied it to the contained value and wrapped the result of that in Just.
So we can write a function called liftToMaybe that does this for us:
liftToMaybe :: (a->b) -> Maybe a -> Maybe b
liftToMaybe f Nothing = Nothing
liftToMaybe f (Just a) = Just $ f a
A further observation we can make is that because functions are values, we can also have Maybe values of functions. To do anything useful with those we could again unwrap them... or notice we're in the same situation as in the last paragraph, and immediately notice that we don't really care what function exactly is in that Maybe value and just write the abstraction directly:
maybeApply :: Maybe (a->b) -> Maybe a -> Maybe b
maybeApply Nothing _ = Nothing
maybeApply _ Nothing = Nothing
maybeApply (Just f) (Just a) = Just $ f a
Which, using our liftToMaybe function above, we can simplify a bit:
maybeApply :: Maybe (a->b) -> Maybe a -> Maybe b
maybeApply Nothing _ = Nothing
maybeApply (Just f) x = liftToMaybe f x
The <$> and <*> operators in Reite's answer are basically just infix names for liftToMaybe (which is also known as fmap) and maybeApply respectively; They have the types
(<$>) :: Functor f => (a->b) -> f a -> f b
(<*>) :: Applicative f => f (a->b) -> f a -> f b
You don't really need to know what the Functor and Applicative things are right now (although you should look into them at some point; They're basically generalizations of the above Maybe functions for other kinds of "context") - basically, just replace f with Maybe and you'll see that these are basically the same functions we've talked about earlier.
Now, I leave applying this to your original problem of multiplication to you (although the other answers kinda spoil it).
You are correct, the problem is that you are trying to multiply two Maybe values together, but (*) only works in instances of Num.
As it turn out, Maybe is an instance of the Applicative typeclass. This means that you can "lift" funcions that work with a type a to functions that work with a type Maybe a.
import Control.Applicative
Two functions provided by Applicative are:
pure :: a -> f a Puts a pure value in a "neutral context". For Maybe, this is Just.
(<*>) :: f (a -> b) -> f a -> f b Lets you apply a "function in a context" to two "values in a context".
So, suppose we have this pure computation:
(*) 2 3
Here are some analogous computations in a Maybe context:
Just (*) <*> Just 2 <*> Just 3
-- result is Just 6
pure (*) <*> pure 2 <*> pure 3
-- result is Just 6 -- equivalent to the above
pure (*) <*> pure 2 <*> Nothing
-- Nothing
Nothing <*> pure 2 <*> Just 3
-- Nothing
Nothing <*> Nothing <*> Nothing
-- Nothing
If the function or one of the argument is "missing", we return Nothing.
(<*>) is the explicit application operator when working with applicatives (instead of using a blank space, as when we work with pure values).
It's instructive to explore what (<*>) does with other instances of Applicative, like []:
ghci> [succ,pred] <*> pure 3
[4,2]
ghci> [succ,pred] <*> [3]
[4,2]
ghci> pure succ <*> [2,5]
[3,6]
ghci> [succ] <*> [2,5]
[3,6]
ghci> [(+),(*)] <*> pure 2 <*> pure 3
[5,6]
ghci> [(+),(*)] <*> [2,1] <*> pure 3
[5,4,6,3]
ghci> [(+),(*)] <*> [2,1] <*> [3,7]
[5,9,4,8,6,14,3,7]

Simple examples to illustrate Category, Monoid and Monad?

I am getting very confused with these three concepts.
Is there any simple examples to illustrate the differences between
Category, Monoid and Monad ?
It would be very helpful if there is a illustration of these abstract concepts.
This probably isn't the answer you're looking for, but here you go anyways:
A really crooked way of looking at monads & co.
One way of looking at abstract concepts like these is to link them with basic concepts, such as ordinary list processing operations. Then, you could say that,
A category generalizes the (.) operation.
A monoid generalizes the (++) operation.
A functor generalises the map operation.
An applicative functor generalizes the zip (or zipWith) operation.
A monad generalizes the concat operation.
A Category
A category consists of a set (or a class) of objects and bunch of arrows that each connect two of the objects. In addition, for each object, there should be an identity arrow connecting this object to itself. Further, if there is one arrow (f) that ends on an object, and another (g) that starts from the same object, there should then also be a composite arrow called g . f.
In Haskell this is modelled as a typeclass that represents the category of Haskell types as objects.
class Category cat where
id :: cat a a
(.) :: cat b c -> cat a b -> cat a c
Basic examples of a category are functions. Each function connects two types, for all types, there is the function id :: a -> a that connects the type (and the value) to itself. The composition of functions is the ordinary function composition.
In short, categories in Haskell base are things that behave like functions, i.e. you can put one after another with a generalized version of (.).
A Monoid
A monoid is a set with an unit element and an associative operation. This is modelled in Haskell as:
class Monoid a where
mempty :: a
mappend :: a -> a -> a
Common examples of monoids include:
set of integers, the element 0, and the operation (+).
set of positive integers, the element 1, and the operation (*).
set of all lists, the empty list [], and the operation (++).
These are modelled in Haskell as
newtype Sum a = Sum {getSum :: a}
instance (Num a) => Monoid (Sum a) where
mempty = Sum 0
mappend (Sum a) (Sum b) = Sum (a + b)
instance Monoid [a] where
mempty = []
mappend = (++)
Monoids are used to 'combine' and accumulate things. For example, the function mconcat :: Monoid a => [a] -> a, can be used to reduce a list of sums to single sum, or a nested list into a flat list. Consider this as a kind of generalization of (++) or (+) operations that in a way 'merge' two things.
A Functor
A functor in Haskell is a thing that quite directly generalizes the operation map :: (a->b) -> [a] -> [b]. Instead of mapping over a list, it maps over some structure, such as a list, binary tree, or even an IO operation. Functors are modelled like this:
class Functor f where
fmap :: (a->b) -> f a -> f b
Contrast this to the definition of the normal map function.
An Applicative Functor
Applicative functors can be seen as things with a generalized zipWith operation. Functors map over general structures one at the time, but with an Applicative functor you can zip together two or more structures. For the simplest example, you can use applicatives to zip together two integers inside the Maybe type:
pure (+) <*> Just 1 <*> Just 2 -- gives Just 3
Notice that the structure can affect the result, for example:
pure (+) <*> Nothing <*> Just 2 -- gives Nothing
Contrast this to the usual zipWith function:
zipWith (+) [1] [2]
Instead of of just lists, the applicative works for all kinds of structures. Additionally, the clever trickery with pure and (<*>) generalizes the zipping to work with any number of arguments. To see how this works, inspect the following types while keeping the concept of partially applied functions at hand:
instance (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Notice also the similarity between fmap and (<*>).
A Monad
Monads are often used to model different computational contexts, such as non-deterministic, or side-effectful computations. Since there are already far too many monad tutorials, I will just recommend The best one, instead of writing yet another.
Relating to the ordinary list processing functions, monads generalize the function concat :: [[a]] -> [a] to work with many other sorts of structures besides lists. As a simple example, the monadic operation join can be used to flatten nested Maybe values:
join (Just (Just 42)) -- gives Just 42
join (Just (Nothing)) -- gives Nothing
How is this related to the use of Monads as a means of structuring computations? Consider a toy example where you do two consecutive queries from some database. The first query returns you some key value, with which you wish to do another lookup. The problem here is that the first value is wrapped inside Maybe, so you can't query with that directly. Instead, as maybe is a Functor, you could instead fmap the return value with the new query. This would give you two nested Maybe values like above. Another query would result in three layers of Maybes. This would be quite difficult to program with, but a monadic join gives you a way to flatten this structure, and work with just a single level of Maybes.
(I think I'll be editing this post a lot before it makes any sense..)
I think to understanding monads one needs to play with bind operator (>>=).
Heavilty influenced by [http://dev.stephendiehl.com/hask/#eightfold-path-to-monad-satori](Don't read the monad tutorials.)
My little play is the following:
1. Concat getLine and putStrLn
Adapted from http://www.haskellforall.com/2014/10/how-to-desugar-haskell-code.html
Prelude> f = getLine >>= \a -> putStrLn a
Prelude> f
abc
abc
Prelude>
and the signatures:
Prelude> :t getLine
getLine :: IO String
Prelude> :t (\a -> putStrLn a)
(\a -> putStrLn a) :: String -> IO ()
Prelude> :t f
f :: IO ()
Result: one can see parts of (>>=) :: Monad m => m a -> (a -> m b) -> m b signature.
2 . concat Maybe's
Adaptation from https://wiki.haskell.org/Simple_monad_examples
Prelude> g x = if (x == 0) then Nothing else Just (x + 1)
Prelude> Just 0 >>= g
Nothing
Prelude> Just 1 >>= g
Just 2
Result: fail "zero" is Nothing
3. Think of bind as joining the computations
... as described in https://www.slideshare.net/ScottWlaschin/functional-design-patterns-devternity2018

Put two monadic values into a pair and return it

I am playing with Parsec and I want to combine two parsers into one with the result put in a pair, and then feed it another function to operate on the parse result to write something like this:
try (pFactor <&> (char '*' *> pTerm) `using` (*))
So I wrote this:
(<&>) :: (Monad m) => m a -> m b -> m (a, b)
pa <&> pb = do
a <- pa
b <- pb
return (a, b)
And
using :: (Functor f) => f (a, b) -> (a -> b -> c) -> f c
p `using` f = (uncurry f) <$> p
Is there anything similar to (<&>) which has been implemented somewhere? Or could this be written pointfree? I tried fmap (,) but it seems hard to match the type.
Better than <&> or liftM2 would be
(,) <$> a <*> b
since Applicative style seems to be gaining popularity and is very succinct. Using applicative style for things like this will eliminate the need for <&> itself, since it is much clearer than (,) <$> a <*> b.
Also this doesn't even require a monad - it will work for Applicatives too.
Is there anything similar to (<&>) which has been implemented somewhere? Or could this be written pointfreely? I tried fmap (,) but it seems hard to match the type.
I don't now if it's implemented anywhere, but <&> should be the same as liftM2 (,). The difference to fmap is, that liftM2 lifts a binary function into the monad.
Using applicative style, there is no need to put the intermediate results into a tuple just to immediately apply an uncurried function. Just apply the function "directly" using <$> and <*>.
try ((*) <$> pFactor <*> (char '*' *> pTerm))
In general, assuming sane instances of Monad and Applicative,
do x0 <- m0
x1 <- m1
...
return $ f x0 x1 ...
is equivalent to
f <$> m0 <*> m1 <*> ...
except that the latter form is more general and only requires an Applicative instance. (All monads should also be applicative functors, although the language does not enforce this).
Note, that if you go the opposite direction from Applicative you'll see that the way you want to combine parsers fits nicely into Arrow paradigm and Arrow parsers implementation.
E.g.:
import Control.Arrow
(<&>) = (&&&)
p `using` f = p >>^ uncurry f
Yes, you could use applicative style but I don't believe that answers either of your questions.
Is there some already defined combinator that takes two arbitrary monads and sticks their value types in a pair and then sticks that pair in the context?
Not in any of the standard packages.
Can you do that last bit point free?
I'm not sure if there is a way to make a curried function with an arity greater than 1 point free. I don't think there is.
Hope that answers your questions.

What is "lifting" in Haskell?

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 someone explain it to me with simple words?
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:
ghci> :type myFunction
a -> b -> c -> d
ghci> :type myFunction <$> Foo 3
Foo (b -> c -> d)
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, like this:
ghci> :type myFunction <$> Foo 3 <*> Foo 4
Foo (c -> d)
ghci: :type myFunction <$> Foo 3 <*> Foo 4 <*> Foo 5
Foo d
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!
Let's start with an example (some white space is added for clearer presentation):
> 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 transforms a function of plain types to a function of same 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, "lift" lifts a function/action into a "wrapped" type (so the original function gets to work "under the wraps").
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 (i.e. this will make that code shorter, etc.), just try it out and you'll easily grasp the concept.
I wanted to write an answer because I wanted to write it from a different point of view.
Let's say you have a functor like for example Just 4 and you wanted to apply a function on that functor for example (*2). So you try something like this:
main = print $ (*2) (Just 4)
And you'll get an error:
No instance for (Num (Maybe a0)) arising from an operator section
• In the expression: * 2
Ok that fails. To make (*2) work with Just 4 you can lift it with the help of fmap.
The type signature of fmap:
fmap :: (a -> b) -> f a -> f b
Link:
https://hackage.haskell.org/package/base-4.16.0.0/docs/Prelude.html#v:fmap
The fmap function takes an a -> b function, and a functor. It applies the a -> b function to the functor value to produce f b.
In other words, the a -> b function is being made compatible with the functor. The act of transforming a function to make it compatible is lifting.
In o.o.p. terms this is called an adapter.
So fmap is a lifting function.
main = print $ fmap (*2) (Just 4)
This produces:
Just 8
According to this shiny tutorial, a functor is some container (like Maybe<a>, List<a> or Tree<a> that can store elements of some another type, a). I have used Java generics notation, <a>, for element type a and think of the elements as berries on the tree Tree<a>. There is a function fmap, which takes an element conversion function, a->b and container functor<a>. It applies a->b to every element of the container effectively converting it into functor<b>. When only first argument is supplied, a->b, fmap waits for the functor<a>. That is, supplying a->b alone turns this element-level function into the function functor<a> -> functor<b> that operates over containers. This is called lifting of the function. Because the container is also called a functor, the Functors rather than Monads are a prerequisite for the lifting. Monads are sort of "parallel" to lifting. Both rely on the Functor notion and do f<a> -> f<b>. The difference is that lifting uses a->b for the conversion whereas Monad requires the user to define a -> f<b>.

Resources