forked from Kaleb47/Haskell-Plutus-journal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
morecustomtypeclasses.hs
450 lines (304 loc) · 14.3 KB
/
morecustomtypeclasses.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
{-# OPTIONS_GHC -Wno-incomplete-patterns #-}
--Algebraic Data Types
data Bool = False | True
-- the part to the left of the equal sign denotes the type, Bool
-- remember | represents or
--data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647
--this data type reprresents the minimum and maximum values of Int
--data Shape = Circle Float Float Float | Rectangle Float Float Float Float
--the above data type represents circles and rectangles. We will eventually use them in are functions
--the circle having three fields and the rectanglw having four
--and yes constructors are functions
{-
surface :: Shape -> Float
surface (Circle _ _ r) = pi * r ^ 2
surface (Rectangle x1 y1 x2 y2) = (abs $ x2 - x1) * (abs $ y2 - y1) -}
--this function takes a shape and returns a surface
--remember, circle and rectangle are not types
--they are simply parameters of Shape
--you can't just type in circle 10 3 1 into the ghci
--the ghci does not know how display our type as a string yet
--so we use this...
--data Shape = Circle Float Float Float | Rectangle Float Float Float Float deriving (Show)
--Deriving (Show) makes it easier for you to type the parameter into the ghci
--since value constructors, like the one above, are functions, we can apply maps to them
data Point = Point Float Float deriving (Show)
data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
--now we've made an intermediate datatype that defines a point in two dimensional space
--this makes it a lot easier for documentation
surface :: Shape -> Float
surface (Circle _ r) = pi * r ^ 2
surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
--lets make a function that can move shapes on the x and y axis
nudge :: Shape -> Float -> Float -> Shape
nudge (Circle (Point x y) r) a b = Circle (Point (x+a) (y+b)) r
nudge (Rectangle (Point x1 y1) (Point x2 y2)) a b = Rectangle (Point (x1+a) (y1+b)) (Point (x2+a) (y2+b))
{-
in the ghci, we can add nudge amounts like so
ghci> nudge (Circle (Point 34 34) 10) 5 10
Circle (Point 39.0 44.0) 10.0
-}
baseCircle :: Float -> Shape
baseCircle r = Circle (Point 0 0) r
baseRect :: Float -> Float -> Shape
baseRect width height = Rectangle (Point 0 0) (Point width height)
{-
module Shapes
( Point(..)
, Shape(..)
, surface
, nudge
, baseCircle
, baseRect
) where
we can export the types we defined in a module
That means you can make your own libraries in Haskell, cool!
-}
-- RECORRD SYNTAX --
{-
data Person = Person String String Int Float String String deriving (Show)
firstName :: Person -> String
firstName (Person firstname _ _ _ _ _) = firstname
lastName :: Person -> String
lastName (Person _ lastname _ _ _ _) = lastname
age :: Person -> Int
age (Person _ _ age _ _ _) = age
height :: Person -> Float
height (Person _ _ _ height _ _) = height
phoneNumber :: Person -> String
phoneNumber (Person _ _ _ _ number _) = number
flavor :: Person -> String
flavor (Person _ _ _ _ _ flavor) = flavor
-}
{-
data Person = Person { firstName :: String
, lastName :: String
, age :: Int
, height :: Float
, phoneNumber :: String
, flavor :: String
} deriving (Show) -}
--write the instance then :: and specify the type
-- this helps us create the look up fields in the data type
-- TYPE PARAMETERS --
--type constructors can take types as parameters to produce new types.
--Maybe Int, Maybe Car, Maybe String
--Maybe by itself is not a type
--we use type parameters when we know our data type would work regardless
{-
For example, let's say we have a car data type...
data Car = Car { company :: String
, model :: String
, year :: Int
} deriving (Show)
why do that when you can distinguish each parameter like so
data Car a b c = Car { company :: a
, model :: b
, year :: c
} deriving (Show)
tellCar :: (Show a) => Car String String a -> String
tellCar (Car {company = c, model = m, year = y}) = "This " ++ c ++ " " ++ m ++ " was made in " ++ show y
This makes the function easier to read for your colleagues!
-}
--Having maps parameterized enables us to have mappings from any type to any other type, as long as the type of the key is part of the Ord typeclass
-- NEVER ADD TYPECLASS CONSTRAINTS IN DATA DECLARATIONS
--you'll end up writing class constraints that you don't need
data Vector a = Vector a a a deriving (Show)
vplus :: (Num t) => Vector t -> Vector t -> Vector t
(Vector i j k) `vplus` (Vector l m n) = Vector (i+l) (j+m) (k+n)
vectMult :: (Num t) => Vector t -> t -> Vector t
(Vector i j k) `vectMult` m = Vector (i*m) (j*m) (k*m)
scalarMult :: (Num t) => Vector t -> Vector t -> t
(Vector i j k) `scalarMult` (Vector l m n) = i*l + j*m + k*n
--We'll be using a parameterized type because even though it will usually contain numeric types, it will still support several of them.
-- as you can see vplus is adding two vectors together
--vectMult is for multiplying a vector with scalar
-- When declaring a data type, the part before the = is the type constructor
-- the constructors after it (possibly separated by |'s) are value constructors.
-- DERIVED INSTANCES --
--remember that a typeclass is an interface of some behavior
--Haskell can derive the behavior of each typeclass in our datatype using the derived keyword
{-
data Person = Person { firstName :: String
, lastName :: String
, age :: Int
} deriving (Eq)
we can try to equate two "person" data types by deriving the Equality type class
Haskell will now try to see if the value constructors match
Now, if the type has fields that we want to see, we need to derive from Show or Read like so,
data Person = Person { firstName :: String
, lastName :: String
, age :: Int
} deriving (Eq, Show, Read)
-}
--Remember show is for converting values of our type to a string
--Read converts strings into values
--Deriving instances from the Ord type class when the datatype has values that can be ordered
--Enum and Bounded typeclasses help us use algebrraic data to enumerate the data type likeso,
data Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday
deriving (Eq, Ord, Show, Read, Bounded, Enum)
--Enums are good for predecessors and successors
--Eq and Ord would help us compare and equate days
--Bounded helps you rank lowest to highest
--Enum. We can get predecessors and successors of days and we can make list ranges from them!
-- TYPE SYNONYMS --
-- [Char] and String are interchangeable
-- remember that the type keyword is not used to make anything new but is used to make type synonyms
type String = [Char]
--this improves the readability of functions
{-
type PhoneNumber = String
type Name = String
type PhoneBook = [(Name,PhoneNumber)]
inPhoneBook :: Name -> PhoneNumber -> PhoneBook -> Bool
inPhoneBook name pnumber pbook = (name,pnumber) `elem` pbook
Doesn't that make the type signature so much easier to read!
Fonzie says: Aaay! When I talk about concrete types I mean like fully applied types like Map Int String
or if we're dealin' with one of them polymorphic functions, [a] or (Ord a) => Maybe a and stuff.
And like, sometimes me and the boys say that Maybe is a type, but we don't mean that, cause every idiot knows Maybe is a type constructor.
When I apply an extra type to Maybe, like Maybe String, then I have a concrete type. You know, values can only have types that are concrete types!
So in conclusion, live fast, love hard and don't let anybody else use your comb!
IntMap type constructor takes one parameter and that is the type of what the integers will point to.
Oh yeah. If you're going to try and implement this,
you'll probably going to do a qualified import of Data.Map.
When you do a qualified import, type constructors also have to be preceeded with a module name.
So you'd write type IntMap = Map.Map Int.
-}
--The Data type Either a b takes two parameters
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
--We can pattern match on both the Left and Right
--Let's see this in action
{-
import qualified Data.Map as Map
data LockerState = Taken | Free deriving (Show, Eq)
type Code = String
type LockerMap = Map.Map Int (LockerState, Code)
lockerLookup :: Int -> LockerMap -> Either String Code
lockerLookup lockerNumber map =
case Map.lookup lockerNumber map of
Nothing -> Left $ "Locker number " ++ show lockerNumber ++ " doesn't exist!"
Just (state, code) -> if state /= Taken
then Right code
else Left $ "Locker " ++ show lockerNumber ++ " is already taken!"
-}
-- RECURSIVE DATA STRUCTURES --
--We can make types whose constructors have field that are the same type
--Same goes for a list like 3:(4:(5:6:[])),
--which could be written either like that or like 3:4:5:6:[]
--(because : is right-associative) or [3,4,5,6].
--We can use algebraic datatypes to implement our own list
-- data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
-- data List a = Empty | Cons { listHead :: a, listTail :: List a} deriving (Show, Read, Eq, Ord)
--the Cons constructor above is simply : which takes a value and another list and returns another list
-- We called our Cons constructor in an infix manner so you can see how it's just like :.
--Empty is like [] and 4 `Cons` (5 `Cons` Empty) is like 4:(5:[])
--We can define functions to be automatically infix by making them comprised of only special characters.
--We can also do the same with constructors, since they're just functions that return a data type
infixr 5 :-:
data List a = Empty | a :-: (List a) deriving (Show, Read, Eq, Ord)
--fixity declarations are used for making your own operator
--operators are used inm infix notation by default
--infixr right associative
--the number represents how it binds to the operands, you are picking your order of operrations
{-
infixr 5 .++
(.++) :: List a -> List a -> List a
Empty .++ ys = ys
(x :-: xs) .++ ys = x :-: (xs .++ ys)
ghci> let a = 3 :-: 4 :-: 5 :-: Empty
ghci> let b = 6 :-: 7 :-: Empty
ghci> a .++ b
(:-:) 3 ((:-:) 4 ((:-:) 5 ((:-:) 6 ((:-:) 7 Empty))))
-}
infixr 5 .++
(.++) :: List a -> List a -> List a
Empty .++ ys = ys
(x :-: xs) .++ ys = x :-: (xs .++ ys)
--we can pattern match :-: because pattern matching is about matching constructors
--What the heck is binary search tree?
--https://en.wikipedia.org/wiki/Binary_search_tree
--according to the wikipedia page, it is data stored in such a way where each node has more data than the last
data Tree a = EmptyTree | Node a (Tree a) (Tree a) deriving (Show, Read, Eq)
--now lets make a function that takes a tree and returns an element
--in haskell, we can't modify our tree
--the first function below is a singleton function for making a single node
singleton :: a -> Tree a
singleton x = Node x EmptyTree EmptyTree
-- the following function inserts an element into a tree
treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = singleton x
treeInsert x (Node a left right)
| x == a = Node x left right
| x < a = Node a (treeInsert x left) right
| x > a = Node a left (treeInsert x right)
-- TYPECLASSES 102 --
--we've learned how to automatically take our own type instances of the standard type classes by asking Haskell to derive the instances for us
--remember, typeclasses are like interfaces with specific behaviors
--This is how the Eq type class is defined in the standard Prelude
{-
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
We're defining a new typeclass that is called Eq
Some people might understand this better if
we wrote class Eq equatable where and then specified
the type declarations like (==) :: equatable -> equatable -> Bool
If we have say class Eq a where and then define a type declaration within that class like
(==) :: a -> -a -> Bool, then when we examine the type of that function later on,
it will have the type of (Eq a) => a -> a -> Bool.
data TrafficLight = Red | Yellow | Green
Now let's derive an instance from Eq
instance Eq TrafficLight where
Red == Red = True
Green == Green = True
Yellow == Yellow = True
_ == _ = False
this is possible through the instance keyword
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
we implemented == by simply doing pattern matching as you can see above
instance Show TrafficLight where
show Red = "Red light"
show Yellow = "Yellow light"
show Green = "Green light"
--------REVIEW THIS LATER----------
--- A YES-NO TYPECLASS ---
class YesNo a where
yesno :: a -> Bool
normally a bool function is preferable but this is a java like implementation
now we can implement instances
instance YesNo Int where
yesno 0 = False
yesno _ = True
epmty strings represent no and non-empty strings represent yes
instance YesNo [a] where
yesno [] = False
yesno _ = True
instance YesNo (Tree a) where
yesno EmptyTree = False
yesno _ = True
yesnoIf :: (YesNo y) => y -> a -> a -> a
yesnoIf yesnoVal yesResult noResult = if yesno yesnoVal then yesResult else noResult
-}
-- FUNCTORS --
--is like a map accept it applies functions to elements that are lists
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- this is how to implement it
--It takes a function from one type to another and a list of one type and returns a list of another type
{-
instance Functor [] where
fmap = map
instance Functor Tree where
fmap f EmptyTree = EmptyTree
fmap f (Node x leftsub rightsub) = Node (f x) (fmap f leftsub) (fmap f rightsub)
-}
--The Functor typeclass wants a type constructor that takes only one type parameter but Either takes two.
{-
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap f (Left x) = Left x
-}