The basics
Today I will try to tell the very basics, such as basic data types, function types, FVP, lists (including infinite ones).
Subsequent articles:
Data types, pattern matching, and functions
Type classes, monads
First you need to download and install the Glasgow Haskell Compiler (GHC) . At the moment, you can take the latest version, but wxHaskell, unfortunately, provides binaries for each platform for different versions of GHC. In addition, some of it is raw (wxHaskell), so I personally have GHC both version 6.10.1 and 6.8.3.
GHC has three main programs: ghc - the compiler, ghci - the interpreter, and runghc - allows you to run programs as scripts without preliminary compilation.
I use HippoEDIT to edit the source , although any editor is suitable where there is highlighting and you can configure a third-party program call for the file.
By default, the ghci prompt contains a list of loaded modules. In order not to clutter it can be changed:
Haskell is a pure and lazy functional language; this means that the result of the function depends only on the arguments, i.e. being called with one argument, the function will always return the same result. Therefore, there are no variables, there are functions and values, but this does not interfere. Laziness means that the value will be calculated only when it is really needed, which allows you to work with endless lists and data structures.
It seems that I / O is not possible in such a language, but the functions that perform I / O simply return a description of the action. The description itself is the same, i.e. functions also get clean. I will consider this mechanism in more detail later.
You can check the type of expression using the command
Specify a specific type (not necessarily a separate value, it is possible for an entire expression) as follows:
Of course, there are lists and tuples. The list can contain elements of the same type and have an arbitrary length, while a tuple is different, and the number of elements is fixed in its type.
To apply a function, you need to write its arguments after the function name:
Let's see the type of function
But the type of function can be represented as follows:
Any function can be represented as taking one argument and returning another function.
However, if you look at the type
Higher-order functions (FWPs) can take other functions and return them. For example, we can define a function that takes two and applies both to some argument:
Such a function is already defined in the standard library and is called it (.)
Another such useful function
Consider the basic list operations: The
list consists of a head and a tail and is created by the constructor (:)
There are many more useful functions, a complete list can be found here .
In relation to functions, the terms “accepts” and “returns” are not entirely accurate, since the function is not calculated immediately because of laziness, but I did not find more suitable words.
A convenient syntactic sugar is defined for the list:
You can work with this list as usual. For example, you can square each element, take only even ones.
An infinite list can also be defined through core recursion — an operation similar to recursion, but not collapsing the data structure (decreasing the value of the argument can also be called collapsing), but a “expanding” result ( Total FP ) based on the original arguments:
Where can this come in handy in practice? For example, in strict languages, it is necessary to know in advance the number of list elements, which makes it necessary to know the superfluous implementation detail in the place of the list creation. Or you have to create some kind of generator that will be called up already. Here we can simply create an endless list, and the necessary part will be taken from it when it is needed.
I hope that in an attempt to squeeze out the water I did not miss anything important. And the writer from me is unimportant. Therefore, if something is not clear, write, I will try to fix it.
Well, now charging for the mind:
Check:
Of course, this does not have practical significance, but in my opinion it is rather funny.
Next time I’ll try to talk about the definition of functions, lambdas, data types, and about pattern matching.
Subsequent articles:
Data types, pattern matching, and functions
Type classes, monads
Compiler and interpreter
First you need to download and install the Glasgow Haskell Compiler (GHC) . At the moment, you can take the latest version, but wxHaskell, unfortunately, provides binaries for each platform for different versions of GHC. In addition, some of it is raw (wxHaskell), so I personally have GHC both version 6.10.1 and 6.8.3.
GHC has three main programs: ghc - the compiler, ghci - the interpreter, and runghc - allows you to run programs as scripts without preliminary compilation.
I use HippoEDIT to edit the source , although any editor is suitable where there is highlighting and you can configure a third-party program call for the file.
By default, the ghci prompt contains a list of loaded modules. In order not to clutter it can be changed:
Prelude> :s prompt "ghci> "
ghci>
Haskell is a pure and lazy functional language; this means that the result of the function depends only on the arguments, i.e. being called with one argument, the function will always return the same result. Therefore, there are no variables, there are functions and values, but this does not interfere. Laziness means that the value will be calculated only when it is really needed, which allows you to work with endless lists and data structures.
It seems that I / O is not possible in such a language, but the functions that perform I / O simply return a description of the action. The description itself is the same, i.e. functions also get clean. I will consider this mechanism in more detail later.
Main types
Char
- character type Bool
- True
or False
Int
- integer. Its size can be 32 bits or 64 on the appropriate architecture. Integer
- "infinite" integer Double
- fractional (floating point) You can check the type of expression using the command
:t
ghci> :t 'x'
'x' :: Char
gchi> :t True
True :: Bool
ghci> :t 5
5 :: (Num t) => t
It turns out that 5 is of type not Int. This entry means approximately “5 can have any type t if it belongs to the class Num”. I will write about classes later, until it can be concluded that 5 may take on a different type depending on the situation (including the user type). Specify a specific type (not necessarily a separate value, it is possible for an entire expression) as follows:
ghci> :t 5::Int
5::Int :: Int
ghci> :t (5 + 6 * 3)::Int
(5 + 6 * 3)::Int :: Int
Of course, there are lists and tuples. The list can contain elements of the same type and have an arbitrary length, while a tuple is different, and the number of elements is fixed in its type.
ghci> :t (True, 'f')
(True, 'f') :: (Bool, Char)
ghci> :t (False, 5::Int, 'q')
(False, 5, 'q') :: (Bool, Int, Char)
ghci> :t ['h','e','l','l','o']
['h','e','l','l','o'] :: [Char]
ghci> :t "hello"
"hello" :: [Char]
From the latter we can conclude that a string is a list of characters and all the same operations are defined on it as on lists.Functions
To apply a function, you need to write its arguments after the function name:
ghci> odd 5
True
ghci> max 4 5
5
ghci> lines "first line\nsecond line"
["first line","second line"]
ghci> "left" ++ "right"
"leftright"
++ is an operator, but it is also a regular function that can be used like this:ghci> (++) "left" "right"
"leftright"
Let's see the type of function
gchi> :t lines
lines :: String -> [String]
It is written through the arrow and means that lines takes a string and returns a list of stringsghci> :t max
max :: (Ord a) => a -> a -> a
Thus, max is defined for any a (belonging to the Ord class), takes two arguments of the same type, and returns a result of the same type. But the type of function can be represented as follows:
max :: (Ord a) => a -> (a -> a)
And here we meet currying. It turns out that max takes one argument and returns a type function (a -> a)
. Any function can be represented as taking one argument and returning another function.
ghci> (max 2) 4
4
The same is true for operators:ghci> ((++) "left") "right"
"leftright"
but in the case of an operator, you can write it like this:ghci> ("left" ++) "right"
"leftright"
ghci> (++ "right") "left"
"leftright"
Those. here the position of the argument is important and the second can be passed first. This is just syntactic sugar, but quite convenient. The most interesting thing is that the same can be done with ordinary functions, if you enclose it in quotation marks ( `
):ghci> 2 `max` 4
4
ghci> (2 `max`) 4
4
Define your function:ghci> let max2 = max 2
Despite the fact that the language is strict, often type annotations can be omitted, since Haskell can deduce types. However, if you look at the type
max2
, we will see something strange:ghci> :t max2
max2 :: Integer -> Integer
Somewhere gone a
and the type became fixed. Honestly, I won’t say why such a solution was chosen, but this can be avoided either by explicitly specifying a polymorphic type or by defining a function like this ( let
needed only for the interpreter, it is not needed in the source code to determine the function):ghci> let max2 x = max 2 x
ghci> :t max2
max2 :: (Ord t, Num t) -> t -> t
ghci> max2 3.6
3.6
Higher-order functions (FWPs) can take other functions and return them. For example, we can define a function that takes two and applies both to some argument:
ghci> let applyFunc f1 f2 x = f1 (f2 x)
ghci> applyFunc (*3) (/2) 5
7.5
As the first function, we pass (* 3), i.e. a function that takes a number and multiplies it by 3; as a second, a function dividing by two. As a result of applying them sequentially to 5, we get 7.5ghci> :t applyFunc
applyFunc :: (t1 -> t2) -> (t -> t1) -> t -> t2
By type, you can even guess what this function does. It takes two functions: from t1 to t2, from t to t1, and returns a function from t to t2. It is clear that as a result, we get a function that must apply both of these functions in sequence (since the type itself is unknown and nothing else can be done). Such a function is already defined in the standard library and is called it (.)
ghci> (not.odd) 5
False
ghci> (not.odd.(+1)) 5
True
In the first case, the odd function is applied first, and then not. Well, in the second one is initially added. In this case, this seems unnecessary, but remember that functions can take other functions, and when you need to create a complex function from primitives on the fly, such compositional operations are very useful. Another such useful function
flip
, swaps the arguments:ghci> (-) 2 3
-1
ghci> (flip (-)) 3 2
-1
List Operations
Consider the basic list operations: The
list consists of a head and a tail and is created by the constructor (:)
ghci> 5:6:[]
[5,6]
head :: [a] -> a
tail :: [a] -> [a]
allow you to get the head and tail, respectively. You cannot pass an empty list there, otherwise an exception will fly. How this is combined with cleanliness, I will also tell later.last :: [a] -> a
init :: [a] -> [a]
return the last item and list without the last item.take :: Int -> [a] -> [a]
returns the first n elementsdrop :: Int -> [a] -> [a]
discards the first n elements and returns the remainingnull :: [a] -> Bool -- проверяет список на наличие элементов
length :: [a] -> Int -- возвращает количество элементов
reverse :: [a] -> [a] -- переворачивает список
map :: (a -> b) -> [a] -> [b]
map
takes a function and applies it to each element of the list, at the output we get a new listghci> map (*2) [1, 2, 3]
[2,4,6]
Connect the module Data.Char
(there, in particular, the function is defined toUpper
)ghci> :m + Data.Char
ghci> map toUpper "hello"
"HELLO"
filter :: (a -> Bool) -> [a] -> [a]
accepts a predicate and leaves only those elements that satisfy itfoldl :: (a -> b -> a) -> a -> [b] -> a
foldr :: (a -> b -> b) -> b -> [a] -> b
The convolution is easier to show with an example.ghci> foldr (+) 0 [1, 2, 3, 4]
10
Those. we pass a function by which the list will be collapsed, the initial value and the list itself. As a result, we get:f (f (f (f 0 1) 2) 3) 4 -- foldl
f 1 (f 2 (f 3 (f 4 0))) -- foldr
In terms of this function, you can define many othersproduct :: Num a => [a] -> a
product = foldr (*) 1
maximum lst = foldr max (head lst) (tail lst)
There are many more useful functions, a complete list can be found here .
In relation to functions, the terms “accepts” and “returns” are not entirely accurate, since the function is not calculated immediately because of laziness, but I did not find more suitable words.
A convenient syntactic sugar is defined for the list:
ghci> [1..10]
[1,2,3,4,5,6,7,8,9,10]
ghci> [1,3..10]
[1,3,5,7,9]
ghci> ['a'..'d']
"abcd"
Moreover, again, user data can also act as the beginning and end. But so far it's too early about this. Interestingly, the right border can be omitted, and we get an endless list.Endless lists
ghci> [1..]
[1,2,3,4Interrupted.
To stop this, press Ctrl-C. In fact, I managed to withdraw to 622, but I did not reflect this here. You can work with this list as usual. For example, you can square each element, take only even ones.
ghci> let lst = [1..]
ghci> let filteredLst = filter even (map (^2) lst)
ghci> take 10 filteredLst
[4,16,36,64,100,144,196,256,324,400]
Of course, reverse
it will not work to calculate the length of such a list or expand it ( ), but this is usually not necessary.ghci> (last.reverse) lst
We are not lucky here either. Although you and I understand that the last element of the inverted list is the first element of the original, the compiler cannot guess before that, and laziness does not help in this case. An infinite list can also be defined through core recursion — an operation similar to recursion, but not collapsing the data structure (decreasing the value of the argument can also be called collapsing), but a “expanding” result ( Total FP ) based on the original arguments:
ghci> let ones = 1 : ones
ghci> take 5 ones
[1,1,1,1,1]
Due to laziness, the infinite data structure obtained (in this case ones is a list with 1 in the head and ones in the tail, i.e. this is an infinite list of units) is not calculated unnecessarily and can be used as usual. Where can this come in handy in practice? For example, in strict languages, it is necessary to know in advance the number of list elements, which makes it necessary to know the superfluous implementation detail in the place of the list creation. Or you have to create some kind of generator that will be called up already. Here we can simply create an endless list, and the necessary part will be taken from it when it is needed.
I hope that in an attempt to squeeze out the water I did not miss anything important. And the writer from me is unimportant. Therefore, if something is not clear, write, I will try to fix it.
Well, now charging for the mind:
ghci> let substr from len str = take len (drop from str)
ghci> substr 2 3 "01234567"
"234"
Let's make the transformations:substr from len str = (take len . drop from) str
-- Опускаем str слева и справа
substr from len = take len . drop from
substr from len = (.) (take len) (drop from)
-- Меняем местами (take len) и (drop from), чтобы len оказался справа
substr from len = flip(.) (drop from) (take len)
-- Сначала к len применяется take, потом (flip(.) (drop from))
-- запишем это с использованием (.)
substr from len = ((flip(.) (drop from)).take) len
-- Убираем len
substr from = (flip(.) (drop from)).take
-- Точку перед take выносим в начало
substr from = (.) (flip(.) (drop from)) take
-- Меняем местами аргументы, чтобы выражение,
-- содержащее from стояло справа
substr from = flip(.) take (flip(.) (drop from))
-- К from применяются последовательно: drop, flip(.), (flip(.)take),
-- так и запишем
substr from = ((flip(.) take).flip(.).drop) from
-- Убираем from
substr = (flip(.)take).flip(.).drop
Check:
ghci> let substr = (flip(.)take).flip(.).drop
ghci> substr 2 3 "01234567"
"234"
Of course, this does not have practical significance, but in my opinion it is rather funny.
Next time I’ll try to talk about the definition of functions, lambdas, data types, and about pattern matching.