Lecture notes "Haskell as the first programming language." Part 2

  • Tutorial
Hi Habr! Today we’ll talk about function polymorphism, operators, and currying. I remind you that the lectures are designed for beginners, and the compendium suggests a concise presentation. If still interested ...

Part I

Polymorphism

A function is polymorphic if it is capable of handling different types of data. The simplest polymorphic function is the identity function:
id     :: a -> a
id x   =  x

Most often, polymorphism is used to work with lists (which is understandable):
length :: [a] -> Int
length []        =  0
length (_:l)     =  1 + length l

The scope of this function (obviously calculating the length of the list) is a list of variables of type a. In common parlance, such a design is simply called: "a list of ashoks." The function will accept any list as an input. Most of the functions in the prelude are polymorphic. Do not be lazy - take a look. In the meantime, here's another example:
filter :: (a -> Bool) -> [a] -> [a]
filter p []                 = []
filter p (x:xs) | p x       = x : filter p xs
                | otherwise = filter p xs

The filter function leaves in the list only elements that satisfy a certain condition.

As you can see, a polymorphic type can also be a domain of a function:
error            :: String -> a

In this case, the error function takes an input string about the error and returns a variable of type a.
A polymorphic function without parameters is defined very interestingly - undefined:
undefined        :: a
undefined        =  error "Prelude.undefined"

This constant function is used to denote an indefinite quantity of any type.

Operators

A function that can be placed between arguments is called infix or, simply, an operator. (The word arguments is used conditionally, we remember that in Haskell there are no functions of two arguments).
The Haskell syntax allows the following characters to be used to construct statements:
": # $% * + - =. / \ <> ?! @ ^ | "
Operators can be composed as you like, with the exception of the following combinations:
" :: = ... - @ \ | <- -> ~ => ”
Besides the fact that the names of the operators are enclosed in parentheses, the function definition is the same as in the prefix notation. Example:
(%%) :: Int -> Int -> (Int, Int)
(%%) x y = (div x y, rem x y)

In addition to the definition of a function in infix notation, it is often necessary to indicate the priority of the operator and the type of associativity. In Haskell, priority is given by an integer, the higher the number, the higher the priority and the earlier the statement will execute.

By associativity, the operators are divided into associative, associative on the right, associative on the left and nonassociative.
Some conditional operator "\ /":
  • associative on the right if the expression a \ / b \ / c is calculated as a \ / (b \ / c)
  • associative on the left if the expression a \ / b \ / c is calculated as (a \ / b) \ / c
  • associative if the expression a \ / b \ / c can be calculated in any order
  • nonassociative if the expression a \ / b \ / c is forbidden to be written without brackets

In Haskell:
  • infixr - associativity on the right
  • infixl - associativity on the left
  • infix - non-associative operator

So, as we write the operator, taking into account priority and associativity:
infixr 7 --ассоциативность справа, приоритет 7
(\/) :: Bool -> Bool -> Bool
(\/) x y = ...


Priorities and associativity of standard prelude operators:
infixr 9  .
infixl 9  !!
infixr 8  ^, ^^, **
infixl 7  *, /, `quot`, `rem`, `div`, `mod`, :%, %
infixl 6  +, -
infixr 5  :, ++
infix  4  ==, /=, <, <=, >=, >, `elem`, `notElem`
infixr 3  &&
infixr 2  ||
infixl 1  >>, >>=
infixr 1  =<<
infixr 0  $, $!, `seq`

Carring

So, why isn’t there a function of two or more arguments in Haskell? Consider the function:
plus :: (Integer, Integer) -> Integer
plus (x, y) = x + y

The plus function has one argument. This argument is a pair of numbers.
Usually, the functions in Haskell are written as:
plus :: Integer -> Integer -> Integer
plus x  y  = x + y

And this function also has one argument (and not two, as one might think), this argument is a number of type Integer. Thus, it becomes possible to apply the arguments one at a time. This is the principle of currying (by the name of the American logic Haskell B. Curry). If we “feed” such a function a number, then we get another function. This is easy to verify with a simple example:
successor :: Integer -> Integer
successor = plus 1

This technique is called partial application of the function.

The prelude defines special functions curry and uncurry, leading functions to currying form and vice versa:
curry            :: ((a, b) -> c) -> a -> b -> c
curry f x y      =  f (x, y)
uncurry          :: (a -> b -> c) -> ((a, b) -> c)
uncurry f p      =  f (fst p) (snd p)

What function we would not write, the argument turns out only one. However, currying, with rare exceptions, is preferable. Currying functions, in addition to reducing the number of brackets, bring a lot of goodies when using. We will be convinced of it in the subsequent lectures. And that's all for today.
When writing the text, I relied on the abstracts of lectures by Sergei Mikhailovich Abramov.
Thanks for attention!

Also popular now: