Haskell: arrow priority with function arguments

I am a fairly experienced Haskell programmer with several hours of experience, so the answer may be obvious.

After looking at the taste of Haskell, I got lost when Simon explained how the append (++) function really works with its arguments.

So, here is the part where he talks about it .

First, he says that (++) :: [a] -> [a] -> [a] can be understood as a function that takes two lists as arguments and returns a list after the last arrow). However, he adds that something similar is actually happening: (++) :: [a] -> ([a] -> [a]) , the function takes only one argument and returns the function.

I'm not sure how to understand how closing the return function gets the first list, since it expects one argument as well.

On the next slide of the presentation, we implement the following implementation:

 (++) :: [a] -> [a] -> [a] [] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys) 

If I think (++) takes two arguments and returns a list, this piece of code along with recursion is pretty clear.

If we consider that (++) receives only one argument and returns a list, where does ys come from? Where is the returned function?

+4
source share
3 answers

The trick in understanding this is that all haskell functions accept only a maximum of 1 argument, simply that the implicit brackets in the type signature and syntax sake make it look like there are more arguments. To use ++ as an example, the following conversions are equivalent

 xs ++ ys = ... (++) xs ys = ... (++) xs = \ys -> ... (++) = \xs -> (\ys -> ...) (++) = \xs ys -> ... 

Another quick example:

 doubleList :: [Int] -> [Int] doubleList = map (*2) 

Here we have a single argument function doubleList without any explicit arguments. That would be equivalent to writing

 doubleList x = map (*2) x 

Or any of the following

 doubleList = \x -> map (*2) x doubleList = \x -> map (\y -> y * 2) x doubleList x = map (\y -> y * 2) x doubleList = map (\y -> y * 2) 

The first definition of doubleList is written in the so-called point notation, the so-called, because in the mathematical theory that supports it, arguments are called "points", therefore without points - "without arguments" ,.

More complex example:

 func = \xyz -> x * y + z func = \x -> \yz -> x * y + z func x = \yz -> x * y + z func x = \y -> \z -> x * y + z func xy = \z -> x * y + z func xyz = x * y + z 

Now, if we want to completely remove all references to arguments, we can use the operator . which performs the composition function:

 func xyz = (+) (x * y) z -- Make the + prefix func xy = (+) (x * y) -- Now z becomes implicit func xy = (+) ((*) xy) -- Make the * prefix func xy = ((+) . ((*) x)) y -- Rewrite using composition func x = (+) . ((*) x) -- Now y becomes implicit func x = (.) (+) ((*) x) -- Make the . prefix func x = ((.) (+)) ((*) x) -- Make implicit parens explicit func x = (((.) (+)) . (*)) x -- Rewrite using composition func = ((.) (+)) . (*) -- Now x becomes implicit func = (.) ((.) (+)) (*) -- Make the . prefix 

So, you can see that there are many ways to write a certain function with a different number of explicit “arguments”, some of which are very readable (ie func xyz = x * y + z ), and some are just a mess of characters with a small value (i.e. func = (.) ((.) (+)) (*) )

+4
source

You are confused about how the Currying Function works .

Consider the following function definitions (++) .

It takes two arguments, creates one list:

 (++) :: [a] -> [a] -> [a] [] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys) 

It takes one argument, creates a function that takes one list and creates a list:

 (++) :: [a] -> ([a] -> [a]) (++) [] = id (++) (x:xs) = (x :) . (xs ++) 

If you look closely, these functions will always produce the same result. By removing the second parameter, we changed the return type from [a] to [a] -> [a] .

  • If we specify two parameters in (++) , we get a result like [a]
  • If we specify only one parameter, we get a result of the type [a] -> [a]

This is called a currying function. We do not need to provide all the arguments to a function with multiple arguments. If we give less than the total number of arguments, instead of obtaining a “specific” result ( [a] ), we will end up with a function that can take the remaining parameters ( [a] -> [a] ).

+2
source

Perhaps this will help. First write it without the operator’s designation, which may be misleading.

 append :: [a] -> [a] -> [a] append [] ys = ys append (x:xs) ys = x : append xs ys 

We can apply one argument at a time:

 appendEmpty :: [a] -> [a] appendEmpty = append [] 

we could equivalently write that

 appendEmpty ys = ys 

from the first equation.

If we apply a non-empty first argument:

 -- Since 1 is an Int, the type gets specialized. appendOne :: [Int] -> [Int] appendOne = append (1:[]) 

we could equivalently write that

 appendOne ys = 1 : append [] ys 

from the second equation.

+2
source

All Articles