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


    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- Trueor 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 strings
    ghci> :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 aand 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 ( letneeded 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.5
    ghci> :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 elements
    drop :: Int -> [a] -> [a]
    discards the first n elements and returns the remaining
    null :: [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 list
    ghci> 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 it
    foldl :: (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 others
    product :: 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, reverseit 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.

    Also popular now: