📜 ⬆️ ⬇️

Data Types, Pattern Matching and Functions

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

Previous articles:
The basics
Subsequent articles:
Classes of types, 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 when working with it: 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 with changes, but I use just an editor with backlight.
')

Data types


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

Designers can 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 the 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 designer, you can use this sugar:
data List a = Null | Cons { listHead :: a , listTail :: List a }
that automatically detects two functions
listHead :: List a -> a
listTail :: List a -> List a
which are elementary implemented

Feature Definition and Pattern Matching (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 order of determination is important) compared with the samples ( Null and Cons _ xs ) and the appropriate option is selected. _ means any value, but we do not care. Those. Cons _ xs passes for any non-empty list.
A sample can be arbitrarily complex:
someFunc ( Cons 5 ( Cons _ ( Cons 3 ( Cons _ Null ) ) ) ) = True
someFunc _ = False
But it can only use constructors (and embedded literals). 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 simultaneously with the sample “disassembled” in parts, we need the function parameter itself (so as not to collect everything back, if we suddenly need it), then we 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 would come to hand with real examples, and the syntax should be shown):
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 sample (in this case, in general, it does not have to be the last one and pattern guards can be written for each sample), then the one that is chosen is True which is selected. 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 the Bool type, 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 be thrown.
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 in fact 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

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

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

listFoldr fv Null = v
listFoldr fv ( Cons x xs ) = fx $ listFoldr fv 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" ) )
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 enclosed in parentheses.
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 the keywords infixl and infixr respectively.
To find out the operator's priority, 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 to ( ++ ) , we will set the same priority to it.
infixr 5 @++
Recall that a function can be called infixed, for this its name is surrounded by quotes. In fact, it is also possible to determine it, i.e. The following is a completely legal definition of a function:
lst `atIndex` n = lst !! n

Define helper functions for convenience.
toList Null = []
toList ( Cons x xs ) = x : ( toList xs )
fromList [] = Null
fromList ( x : xs ) = Cons x ( fromList xs )

Finally, we will write a function that converts our list into a string in the same way that it works for a regular list. To do this, first write a function that inserts an additional element between the elements of the list, i.e. from [ 1 , 2 , 3 ] makes [ 1 , 5 , 2 , 5 , 3 ] (if the inserted element is 5) (for some reason <font> 0 </ font> turns into emptiness, here: ( 0 ) 5 change :)) :
listIntersperse with Null = Null
listIntersperse with ( Cons x xs ) = Cons x $ listFoldr ( \ x -> Cons with . Cons x ) Null xs

Here, lambda is used as a convolution function. Lambda is an anonymous function, written as \ arg1 arg2 argN -> expr . It can also use pattern matching, but with only one, i.e. it is not possible to write several options for several samples, but if necessary, you can always use case ... of .
Consider the lambda \ x -> Cons with . Cons x \ x -> Cons with . Cons x , it takes a certain value, and returns a function that attaches the element itself to the list, and then the with element, as a result we get the list Cons with ( Cons x ... )
Those. each element, except the first, is preceded by a element with .
Well, now just define the function to convert the list to a string:
listShow lst = "[" ++ ( listFoldr ( ++ ) "" $ listIntersperse "," $ listMap show lst ) ++ "]"

listMap show lst
Converts all list items to strings.
listInterpserse ","
Insert comma between elements
listFoldr ( ++ ) ""
joins all strings into one and at the edges we add parentheses. Checking:
ghci> show [ 1 , 2 , 3 ] == listShow ( fromList [ 1 , 2 , 3 ] )
True


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

Source: https://habr.com/ru/post/50600/


All Articles