Data Types, Pattern Matching, and Features

    Today, as promised, I will briefly talk about user data types, function definitions, and pattern matching.

    Previous articles:
    Fundamentals
    Subsequent articles:
    Type classes, monads


    Foreword


    Files have the extension .hs or .lhs (for Literate Haskell, with the reverse way of writing comments and code).
    To load them into the interpreter, you can either call it ghci file.hs, or use the commands: cd and: l
    gchi> :cd c:/some/haskell
    ghci> :l file.hs
    [1 of 1] Compiling Main ( file.hs, interpreted )
    Ok, modules loaded: Main.
    ghci>
    Surely there is a convenient IDE that automatically makes a reload when changes are made, but I just use a backlit editor.

    Data types


    The data type is determined using the data keyword, the type name, and then listing the constructors through |
    Data types and constructors must begin with a capital letter, function names with a small one.
    data Color = Red | Green | Blue

    Constructors may contain parameters:
    import Data.Word (Word8) -- импортируем Word8 из модуля Data.Word
    data Color = Red | Green | Blue | Custom Word8 Word8 Word8

    In addition, the type itself can be parameterized by the type used. For example a list:
    data List a = Null | Cons a (List a)
    Those. the list of elements a is either empty or consists of (head) a and (tail) List a
    In the description of the constructor, you can use the following sugar:
    data List a = Null | Cons { listHead :: a, listTail :: List a }
    which automatically determines two functions
    listHead :: List a -> a
    listTail :: List a -> List a
    which are elementarily implemented

    Function definition and matching pattern (pattern matching)


    We define useful functions above our list:
    length Null = 0
    length (Cons _ xs) = 1 + length xs
    Here I used pattern matching. The function parameter is sequentially (the determination order is important) are compared with the samples ( Nulland Cons _ xs) and the appropriate option is selected. _means any value, but it doesn’t matter to us. Those. Cons _ xspasses for any non-empty list.
    A sample can be arbitrarily complex:
    someFunc (Cons 5 (Cons _ (Cons 3 (Cons _ Null)))) = True
    someFunc _ = False
    But you can only use constructors (and embedded literals) in it. Those. in this example:
    x = 4
    wrong x = True
    wrong _ = False
    the first option will always work, since x is a free name, and not the value that is defined as 4.
    If, at the same time as the “disassembled” parts of the sample, we need the function parameter itself (so as not to collect everything back if we suddenly need it need), then you can write it like this:
    someFunc v@(Cons 5 (Cons _ (Cons 3 (Cons _ Null)))) = -- v - параметр функции, сопоставленный с образцом
    Pattern matching can also be used inside a function. Here is the most general case (sorry for the senselessness of the example, but I don’t remember when such a general case came in handy with real examples, and you need to show the syntax):
    someFunc2 n = case n of
      Null -> "Null"
      Cons _ Null -> "One"
      Cons _ (Cons x _)
        | x == 0 -> "0"
        | x == 1 -> "1"
        | otherwise -> "otherwise" -- otherwise определена как True
    The last three lines in the example above are the so-called pattern guards. When the value falls under the last pattern (in this case, in general, it does not have to be the last and pattern guards can be written for each pattern), then the one that gives is chosen next True. The same mechanism works for functions:
    someFunc3 (x:xs)
        | isSomePredicate x = xs
        | x == 0 = []
        | otherwise = (x:xs)
    In addition, there is an additional non-standard feature. Instead of writing an expression of type Bool, you can write a certain pattern and check any expression for coincidence with it, for example:
    someFunc4 (x:xs)
        | (2:ys) <- filter even xs = ys -- подсписок чётных начинается с 2 и непуст
        | (4:y:[]) <- xs = [y] -- xs состоит из 2-х элементов, первый - 4
        | otherwise = (x:xs)

    If the samples are not ways to cover all possible values, but it does appear there, an exception will fly.
    In particular, listHead (and the standard head too) is not able to process an empty list
    ghci> listHead Null
    *** Exception: No match in record selector Main.listHead
    ghci> head []
    *** Exception: Prelude.head: empty list
    The second option gives more information, because actually the head for an empty list is defined, but it throws an exception. For such cases, you can use the standard error function
    listHead Null = error "listHead: empty list"
    listHead (Cons x _) = x
    ghci> listHead Null
    *** Exception: listHead: empty list

    Define some of the standard functions for our list, similar to the corresponding standard
    listMap f Null = Null
    listMap f (Cons x xs) = Cons (f x) (listMap f xs)

    listFilter p Null = Null
    listFilter p (Cons x xs)
      | p x = Cons x (listFilter p xs)
      | otherwise = listFilter p xs

    listFoldr f v Null = v
    listFoldr f v (Cons x xs) = f x $ listFoldr f v xs
    ($)Is an application operator, it takes a function and an argument. Its essence is that it eliminates the need to put extra brackets, i.e. instead,
    foo (bar 3 (baz 56 "x"))
    you can write
    foo $ bar 3 $ baz 56 "x"

    Operators are defined in the same way as functions, but if they are used in prefix form, they must be surrounded by brackets.
    In this example, the entries are correct:
    Null @++ right = right
    (@++) left Null = left
    (Cons l ls) @++ right = Cons l (ls @++ right)
    Additionally, you can assign priority and left or right associativity to the operator. using keywords infixland infixraccordingly.
    To find out the priority of the operator, in the interpreter you can use the command: i
    ghci> :i (++)
    (++) :: [a] -> [a] -> [a] -- Defined in GHC.Base
    infixr 5 ++
    5 is a priority from 1 to 9, the higher it is, the higher the priority.
    Since our operator is similar (++), then we will give it the same priority
    infixr 5 @++
    Recall that a function can be called infographically; for this, its name is surrounded by quotation marks. In fact, it can also be defined in this way, i.e. The following is a completely legal definition of a function:
    lst `atIndex` n = lst !! n

    We define auxiliary functions for convenience
    toList Null = []
    toList (Cons x xs) = x:(toList xs)
    fromList [] = Null
    fromList (x:xs) = Cons x (fromList xs)

    Finally, we write a function that converts our list to a string in the same way as it does for a regular list. To do this, first write a function that inserts an additional element between the list items, i.e. of [1,2,3]doing [1,5,2,5,3](if the plug-in element 5) (for some reason, 0 is converted into the void, that: (0) fell to 5 to change :)):
    listIntersperse with Null = Null
    listIntersperse with (Cons x xs) = Cons x $ listFoldr (\x -> Cons with.Cons x) Null xs

    Here, a lambda is used as a convolution function. Lambda is an anonymous function, written as \arg1 arg2 argN -> expr. In it, you can also use pattern matching, but only with one, i.e. writing several options for several samples will not work, but if necessary, you can always use it . Consider the lambda written here in more detail , it takes a certain value, and returns a function that attaches this element to the list, and then the element , as a result we get a list i.e. each element, except the first, is preceded by an element . Well, now it’s easy to define a function to convert a list to a string:case ... of
    \x -> Cons with.Cons xwithCons with (Cons x ...)
    with

    listShow lst = "[" ++ (listFoldr (++) "" $ listIntersperse "," $ listMap show lst) ++ "]"

    listMap show lst
    Converts all elements of the list into strings.
    listInterpserse ","
    Between the elements, inserts a comma,
    listFoldr (++) ""
    joins all the lines into one, and from the edges we add brackets. We check:
    ghci> show [1,2,3] == listShow (fromList [1,2,3])
    True


    Next time I’ll talk about type classes and some standard ones.

    Also popular now: