length, head, teal, curry, uncurry, map, filter, ...
What unites all these functions? Let's look at the implementation of some of them: length :: [a] -> Int length [] = [] length (_:xs) = 1 + length xs head :: [a] -> a head (x:_) = x tail :: [a] -> [a] tail (_:xs) = xs curry :: ((a,b) -> c) -> (a -> b -> c) curry fxy = f (x,y) uncurry :: (a -> b -> c) -> ((a,b) -> c) uncurry g (x,y) = gxy
a
, and curry
and uncurry
have b
and c
. Instead of a specific data type ( Int, Bool, Char,
...), typing is used. Java example: public class Test<A> {...}
a, abc, aA101
), and all specific types (constructors) begin with a capital letter (for example, String, Int, Node
).a
parameter can take any type, be it Int, String, or even functions (for example, length [f, g, h]
, where f, g, h are functions that have the same type). It is worth noting that type b
can also accept any type, including the type of parameter a
.:t
, for example: Main>:t length length :: [a] -> Int
(+) 2 3 ->> 5
(+) 2.5 3.85 ->> 6.35
(+) True False
(+) [1,2,3] [3,2,1]
(+) :: a -> a -> a
(+) :: Num a => a -> a -> a
. That is, a restriction is imposed on the type of input (and output) data.a
: Num a says that type a
must be an element of class Num. Such type classes are very important in Haskell, as they add additional protection against programming errors, and can also reduce the amount of written code at times. We will see this in the following example. data Tree1 a = Nil | Node1 a (Tree1 a) (Tree1 a)
data Tree2 ab = Leaf2 b | Node2 ab (Tree2 ab) (Tree2 ab)
data Tree3 = Leaf3 String | Node3 String Tree3 Tree3
type Lst a = [a]
data Rope ab = Nil | Twisted b (Rope ba)
size
function, which, regardless of the data structure, would output either its depth (for trees), or length (for lists), or size - in one word. A naive solution would be to write for each its own function: sizeT1 :: Tree1 a -> Int -- - sizeT1 Nil = 0 sizeT1 (Node1 _ lr) = 1 + sizeT1 l + sizeT1 r sizeT2 :: (Tree2 ab) -> Int -- - sizeT2 (Leaf 2 _) = 1 sizeT2 (Node2 _ _ lr) = 2 + sizeT2 l + sizeT2 r sizeT3 :: Tree3 -> Int -- String sizeT3 (Leaf3 m)= length m sizeT3 (Node mlr) = length m + sizeT3 l +sizeT3 r -- : sizeLst :: [a] -> Int sizeLst = length sizeRope :: (Rope ab) -> Int sizeRope Nil = 0 sizeRope (Twisted _ ls) = 1 + sizeRope ls
class Size a where size :: a -> Int
a
(= for specific data types): instance Size (Tree1 a) where size Nil = 0 size (Node1 _ lr) = 1 + size l + size r instance Size (Tree2 ab) where size (Leaf2 _) = 1 size (Node2 _ _ lr) = 2 + size l + size r instance Size Tree3 where size (Leaf3 m) = length m size (Node3 mlr) = length m + size l + size r instance Size [a] where size = length instance Size (Rope ab) where size Nil = 0 size (Twisted _ ls) = 1 + size ls
:t size
, we will see size :: Size a => a -> Int
. We got what we wanted: size Nil ->> 0 size (Node1 "foo" (Node1 "bar" Nil Nil) Nil) ->> 2 size (Leaf 2 "foo") ->> 1 size (Node3 "foo" (Node3 "bar" (Leaf3 "abc") (Leaf "cba")) (Leaf "tst")) ->> 15 --3*5 size [1..5] ->> 5 size (Rope 2 (Rope 'a' (Rope 5 Nil))) ->> 3
Size
class implemented the size function, which is applicable only to values of a certain type (= data structures). In this case, this function, like other predefined operators (+), (*), (-), is overloaded. size [(1,2), (3,4)] ->> 4 size [('a','b'), ('c','d'), ('e','f')] ->> 6
instance Size [(a,b)] where size = (*2) . length
instance Size [a] where
), we can no longer use another definition, because, as mentioned earlier, type a
can be any type, including (b,c)
, that is. [a] == [(b,c)]
To solve this problem, you can use the Overlapping Instances (English overlapping ekzeplyary class). This solution has its drawbacks (importing one of the modules can change the value of the program, can cause confusing errors, and so on).Size
class, but the incomplete class declaration in Haskell: (tv = type variable): class Name tv where tv
tv
can also be entered in the class declaration (for example, tv
must be an element of the Ord
class). Signatures can be any number, but each of them must include (as an input and / or output parameter) the variable tv.deriving
section). There is also a relationship between classes. For example, if we know how to compare two elements (class Ord), then we should be able to determine in advance whether one element is equal to another (class Eq). After all, to implement the operator ( >=
) you need to have an implemented operator (==). Therefore, it can be said that the class Ord depends (inherits) on the class Eq. Here is a more detailed class dependency diagram:Eq
more detail. As previously mentioned, this class should have two functions (==) and (/ =): class Eq a where (==), (/=) :: a -> a -> Bool -- (==) (/=) , 1 x /= y = not (x == y) x == y = not (x /= y)
instance Eq Bool where (==) True True = True (==) False False = True (==) _ _ = False
instance (Eq a) => Eq (Tree2 a) where -- a, (==) (Leaf2 s) (Leaf2 t) = (s==t) (==) (Node2 s t1 t2) (Node2 t u1 u2)= (s==t) && (t1 == u1) && (t2 == u2) (==) _ _ = False
(Tree3 ab)
we should already impose a condition on both a
and b
, that is,instance (Eq a, Eq b) => Eq (Tree3 ab)
=>
is called a context, all that to the right of this symbol must be either a base type (Int, Bool, ...), or type constructors (Tree a, [...], ...). The operator (==) is an overloaded function (ad-hoc polymorphism). (==) fac fib ->> False -- fac - , fib - (==) (\x -> x+x) (\x -> 2*x) ->> True (==) (+2) (2+) ->> True
instace Eq (Int -> Int) where (==) fg = ... -- f g -
deriving
. That is, we could quite calmly write: data Tree a = Nil | Node a (Tree a) (Tree a) deriving Eq
instance Eq a => Eq (Tree a) where ...
). But for any function, then you have to impose a condition on the variable a
, that this variable is an element of the class Eq. Sometimes, you still have to write these functions yourself, since the “automatic” comparison does not always work the way we would like. For example for a binary tree data Tree3 ab = Leaf3 bl | Node3 ab (Tree3 ab) (Tree3 ab) deriving Eq
instance (Eq a, Eq b) => Eq (Tree3 ab) where (==) (Leaf3 q) (Leaf3 s) = (q==s) (==) (Node3 _ q t1 t2) (Node3 _ s u1 u2) = (q==s)&&(t1==u1)&&(t2==u2) (==) _ _ = False
Source: https://habr.com/ru/post/166353/
All Articles