-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path03-ComplexTypes.fsx
209 lines (155 loc) · 5.38 KB
/
03-ComplexTypes.fsx
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
(*
Tuples
Tuples are a collection of values, which can be of any type themselves.
You can create a tuple using the comma operator (',')
Whenever you see a comma in F#, assume it's there to create a tuple.
*)
let coordinates = 5, 6
let names = "Ankit", "Solanki"
let address = "Hosur Road", "Bangalore", "Karnataka", 560068
(*
Tuple type signatures are something like: int * int, int * string, etc
A '*' represents that the tuple is a 'product type'.
For now, you can imagine it to be a type that represents the cartesion
product of two different types.
Explicit type declaration also works for tuples.
*)
let position : int * int = 100, 200
(*
All tuples are defined by their base types.
Any pair of ints (int * int) is of the same type as any other pair of ints.
You can alias this 'raw' type with a human friendly name though.
*)
type Position = int * int
let currentPos : Position = 1, 2
(*
Destructuring, or pulling values out of a tuple
It is possible to get a value out of a tuple using a simple assignment operation
*)
let x, y = currentPos
// You can ignore some values if you don't care about them using '_'
let x1, _ = currentPos
// You always have to match the number of items mentioned in the tuple
// This will be a compiler error
// let locality, _ = address
(*
Records
Records types have named fields.
You need to define a record before you use it (unlike typle)
*)
type CoOrdinates = { x : int ; y : int }
// You can omit the semi-colon if you separate fields by newlines
type Contact = {
name : string
address : string
pincode : int
verified : bool
}
module Contact =
let isNotverified (c : Contact) = not c.verified
// Creating instances of a record type is very similar
let a = { x = 1 ; y = 2 }
// Compiler will automatically infer the type of the record
// based on the fields you use.
// But you can give a type annotation too.
let b : CoOrdinates = { x = 1 ; y = 2 }
let me = {
name = "Ankit"
address = "Bangalore"
pincode = 560068
verified = true
}
(*
Characteristics of records
- Immutable. You can't update a field once you initialize it.
- Compiler requires you to give value for all fields on initialization.
- Automatic structural integrity comparison.
- Can be used as a key in a map, etc.
*)
// Structural equality
printfn """
a = %A
b = %A
is a == b? %A""" a b (a = b)
// If you need to modify a record, you can use a 'copy constructor'
let newCoordinates = { a with x = 5 }
// You can use records as function arguments, of course.
let incrementX (position : CoOrdinates) =
{ position with x = position.x + 1 }
incrementX a
(*
Discriminated Unions
These are similar to enum type in other languages.
But much more powerful.
*)
type State =
| Karnataka
| UttarPradesh
| Maharashtra
| TamilNadu
// Usage: you can refer to the value directly
let s = Karnataka
let s2 = Karnataka
// Or by using prefixes.
let s' = State.TamilNadu
s = s2 // Equality checking is built-in
// Now, introducing the 'match' expression
// 'match' has to be exhaustive.
let stateName (state : State) : string =
match state with
| Karnataka -> "Karnataka"
| UttarPradesh -> "Uttar Pradesh"
| Maharashtra -> "Maharashtra"
| TamilNadu -> "Tamil Nadu"
printfn "%s" (stateName TamilNadu)
// Compiler will give a warning if you do not consider all cases.
// Example: add a new state to the State type
(* Discriminated unions can have associated data *)
type Shape =
| Square of decimal
| Rectangle of decimal * decimal
| Circle of decimal
// You can create a value of this type like this:
let unitSquare : Shape = Square 1M
// One easy way to think about this is to consider 'Square'
// to be a constructor for the type 'Shape', that takes a
// decimal value and return a Shape instances.
// You can use `match` to pattern match the different cases,
// and get the associated values.
let area (shape : Shape) : decimal =
match shape with
| Square side -> side * side
| Rectangle (length , breadth) -> length * breadth
| Circle radius -> 3.14M * radius * radius
// One of the most common union types you will see is 'Option'
// Option is a built-in type.
// It represents duality: either you have a value, or no value.
// This is F#'s way to remove nulls from the language.
// Any possible function that could return null would in fact
// return an option instead, and the compiler will force you to
// handle both the cases.a
// Even though 'Option' is built-in, we can create it very easily.
// Using ' with the name to prevent conflict with the built-in type.
// type MyOption<'T> =
// | Some' of 'T
// | None'
// Example usage: load contact from db given a name.
let load (name : string) : Contact option =
// Stub implementation
let doesUserExist name =
false
// Stub implementation
let loadUser name : Contact =
{
name = "A"
address = "A"
pincode = 123456
verified = false
}
match (doesUserExist name) with
| true -> Some (loadUser name)
| false -> None
// Using option types forces you to always make sure that
// you handle the failure cases.
// You can do that at the edge of your system, ideally only
// a single time, instead of doing null checks throughout.