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
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
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.
Constructors may contain parameters:
In addition, the type itself can be parameterized by the type used. For example a list:
In the description of the constructor, you can use the following sugar:
We define useful functions above our list:
A sample can be arbitrarily complex:
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:
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
Define some of the standard functions for our list, similar to the corresponding standard
you can write
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:
To find out the priority of the operator, in the interpreter you can use the command: i
Since our operator is similar
We define auxiliary functions for convenience
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
Here, a lambda is used as a convolution function. Lambda is an anonymous function, written as
Converts all elements of the list into strings.
Between the elements, inserts a comma,
joins all the lines into one, and from the edges we add brackets. We check:
Next time I’ll talk about type classes and some standard ones.
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 functionslistHead :: List a -> a
listTail :: List a -> List a
which are elementarily implementedFunction 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 ( Null
and Cons _ xs
) and the appropriate option is selected. _
means any value, but it doesn’t matter to us. 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 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 functionlistHead 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 infixl
and infixr
accordingly. 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 priorityinfixr 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 x
with
Cons 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.