summaryrefslogtreecommitdiff
path: root/lec08/notes.md
diff options
context:
space:
mode:
Diffstat (limited to 'lec08/notes.md')
-rw-r--r--lec08/notes.md88
1 files changed, 88 insertions, 0 deletions
diff --git a/lec08/notes.md b/lec08/notes.md
new file mode 100644
index 0000000..99360c8
--- /dev/null
+++ b/lec08/notes.md
@@ -0,0 +1,88 @@
+# Lecture 8: `IO`
+
+## The problem with purity
+
+The main problem with purity is that functions cannot have side effects, therefore they cannot "talk" with the outside world, neither read from nor write to it.
+
+Purity is good, but sometimes we do want to be able to talk with the outside world. If Haskell functions could only be evaluated at a prompt, Haskell would be practically useless.
+
+It is possible, in fact, to do this kind of thing with Haskell, but in a very different way compared to other languages.
+
+## The `IO` type
+
+`IO` is a special type. Values of type `IO a` are *descriptions* of a computation and `a` is the type of the final value produced by the computation.
+
+It is important to understand that a value of the `IO` type is not a final value, but a description of how to get it. A good analogy is cakes and their recipes: `IO Cake` is not a `Cake` itself, but a recipe that describes how to cook one.
+
+The computations in `IO` *can* describe I/O operations without doing them explicitly. When a Haskell program is run, the value of the `IO` type returned by the `main` function is passed to the Haskell runtime, which then executes all the computation defined by such value, including I/O operations. In the analogy of the cake and its recipe, the Haskell runtime is a master chef who can cook everything, while our Haskell programs merely build recipes.
+
+Values of the `IO` type behave like values of any other type: they can be passed as arguments, returned from functions, stored in data structures, or combined with other `IO` values.
+
+### There is no `String` inside of an `IO String`
+
+As written above, there is no `String` in an `IO String`. The reason for that is pretty much the same as why there is no cake in the recipe for a cake.
+
+### Combining `IO`
+
+The simplest way is to use the `(>>)` operator, which is called "*and then*". It has the following type.
+
+ (>>) :: IO a -> IO b -> IO b
+
+All this operator does is creating an `IO` computation which consists of running two given computations in sequence. It's important to know that this operator discards all values produced by the first computation (if any) and only uses such computation for its side-effects, such as printing on a screen.
+
+ main = putStrLn "Hello" >> putStrLn "world!"
+
+
+While this is useful for programs that do not take any input or check any result, it's generally insufficient. For computations that not only execute simple successive instructions but also depend on what happened in prior instructions, such as printing an integer number that is the successive of another one that has been read as input, there is another operator called **bind** `(>>=)`.
+
+ (>>=) :: IO a -> (a -> IO b) -> IO b
+
+All this operator does is:
+
+1. It takes an `IO` computation that produces a value of type `a`.
+2. It takes a function that can produce a second `IO b` computation based on the value (of type `a`) produced by the first.
+3. It returns an `IO b` computation that includes: the first computation (`IO a`), a function that produces the next computation (based on the final value of the first), and the produced computation `IO b`.
+
+Informally, the computation produced by the bind operator can be described like this. First, we do the input computation `IO a`. Then, we decide what to do with the `a` value using the function passed as input. Finally, we do what the function told us to do.
+
+For example, to read a number and print its successor, we can write:
+
+ main :: IO ()
+ main = putStrLn "Number: " >> (readLn >>= (\n -> putStrLn (show (n+1))))
+
+We'll see that there are better ways to write such things.
+
+## Record syntax
+
+Record syntax allows us to name the fields of a constructor. The following two lines of Haskell code declare the same data type, first without and then with record syntax.
+
+ data D = C T1 T2 T3
+ data D = C { field1 :: T1, field2 :: T2, field3 :: T3 }
+
+Data types declared with record syntax can be used in the same way as those without it. However, record syntax allows us to implicitly declare *projection functions* for each field and to use a special syntax for constructing, modifying, and pattern-matching their data type.
+
+### Projection functions
+
+Implicit projection functions are functions named after each field and, given an instance of their data type, they return the value of the field they are named after. For example, the projection function for `field2` from the `D` type defined earlier has this type
+
+ field2 :: D -> T2
+
+and its definition is equivalent to this
+
+ field2 (C _ f _) = f
+
+This is particularly useful to get rid of boilerplate code, especially with data types that have many fields.
+
+### Special syntax
+
+The record syntax allows us to *construct* data types with a new syntax. With this syntax we can define values for each field in a custom order.
+
+ C { field3 = ..., field1 = ..., field2 = ... }
+
+In addition to that, we can *modify* a value by specifying just some of its fields, instead of all of them. Of course this doesn't actually modify the value itself, it just creates a new value whose fields are the same, except for those that have changed.
+
+ d { field3 = ... }
+
+Finally, we can *pattern-match* fields using their names. The example below only matches `field1` from an instance of `D` and uses `x` as a pattern (which, in this case, just renames the field) and ignores the other field values. We could have used any other pattern instead of `x`.
+
+ foo (C { field1 = x }) = ... x ...