Hi Habr! Today we will talk about the polymorphism of functions, operators and currying. I remind you that the lectures are designed for beginners, and the abstract suggests a concise presentation. If it's still interesting ...
First partPolymorphism
A function is polymorphic if it is able to operate with 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 (this is understandable):
length :: [a] -> Int length [] = 0 length (_:l) = 1 + length l
The scope of this function (calculating, obviously, the length of the list) is a list of variables of type a. Such a colloquial phrase is simply called “the list of ashek”. The function will accept any list as input. Most of the functions in the prelude are polymorphic. Do not be lazy - take a look. And, bye, here's another example:
filter :: (a -> Bool) -> [a] -> [a] filter p [] = [] filter p (x:xs) | px = 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, the polymorphic type can be a function value area:
error :: String -> a
In this case, the error function takes an error string as input and returns a variable of type a.
The polymorphic function with no 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 the arguments is called infix or, simply, an operator. (The word arguments are conditional, we remember that Haskell has no functions of two arguments).
The Haskell syntax allows the use of the following characters to construct statements:
“: # $% * + - =. / \ <> ?! @ ^ | "
Operators can be composed as you please, except for the following combinations:
“:: = ... - @ \ | <- -> ~ => "
In addition to the fact that the names of the operators are enclosed in parentheses, the definition of the function is the same as in the prefix notation. Example:
(%%) :: Int -> Int -> (Int, Int) (%%) xy = (div xy, rem xy)
In addition to the definition of the function itself in infix notation, it is often necessary to specify the operator’s priority and the type of associativity. In Haskell, the priority is set as an integer, the higher the number, the higher the priority and the earlier the statement will be executed.
By associativity, operators are divided into associative, right associative, left associative and nonassociative.
Some conditional operator "\ /":
- right associative if the expression a \ / b \ / c is calculated as a \ / (b \ / c)
- left associative 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 - right associativity
- infixl - left associativity
- infix - non-associative operator
So, how do we write an operator with priority and associativity in mind:
infixr 7
Priorities and associativity of standard operators from the prelude:
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 is there no 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, functions in Haskell are written in the form:
plus :: Integer -> Integer -> Integer plus xy = x + y
And this function also has one argument (and not two, as one might think), this argument is an integer type number. Thus, it is possible to apply the arguments one by one. This is the principle of currying (after the American logic of Haskell B. Curry). If we “feed” such a function a number, then we will get another function. This is easy to verify with a simple example:
successor :: Integer -> Integer successor = plus 1
This technique is called partial use of the function.
In the prelude, the special functions curry and uncurry are defined, which lead to the curling form and back:
curry :: ((a, b) -> c) -> a -> b -> c curry fxy = f (x, y) uncurry :: (a -> b -> c) -> ((a, b) -> c) uncurry fp = f (fst p) (snd p)
What function we would not write, the argument turns out only one. However, the carring type, with rare exceptions, is preferable. Carring functions, in addition to reducing the number of brackets, introduce a lot of buns when used. We will see this in subsequent lectures. And that's it for today.
When writing the text, I relied on lecture notes by Sergei Mikhailovich Abramov.Thanks for attention!