git.iko.soy/Haskell-HSE/haskell-lectures-2024
on main help sign in
947 lines 27 kB history raw
1# Haskell 2024 - Intro
3## What is Haskell?
5As written in Wikipedia:
7Haskell (/ˈhæskəl/) is a general-purpose, statically-typed, purely functional
8programming language with type inference and lazy evaluation. Designed for
9teaching, research, and industrial applications, Haskell has pioneered a number
10of programming language features such as type classes, which enable type-safe
11operator overloading, and monadic input/output (IO). It is named after logician
12Haskell Curry. Haskell's main implementation is the Glasgow Haskell Compiler
13(GHC).
15## Where is Haskell used?
17Haskell is used in academia and in industry:
19* The proof assistant Agda is written in Haskell.
20* Darcs is a revision control system written in Haskell, with several innovative
21 features, such as more precise control of patches to apply.
22* GHC is also often a testbed for advanced functional programming features and
23 optimizations in other programming languages.
24* Git-annex is a tool to manage (big) data files under Git version control. It
25 also provides a distributed file synchronization system (git-annex assistant).
26* Linspire Linux chose Haskell for system tools development.
27* Pandoc is a tool to convert one markup format into another.
28* Pugs is a compiler and interpreter for the Raku programming language
29 (formerly Perl 6).
30* TidalCycles is a domain special language for live coding musical patterns,
31 embedded in Haskell.
32* Xmonad is a window manager for the X Window System written fully in Haskell.
33* GarganText is a collaborative tool to map through semantic analysis texts on
34 any web browser, written fully in Haskell and PureScript, which is used for
35 instance in the research community to draw up state-of-the-art reports and
36 roadmaps.
38### Industry
40* Bluespec SystemVerilog (BSV) is a language for semiconductor design that is an
41 extension of Haskell. Also, Bluespec, Inc.'s tools are implemented in Haskell.
42 Note this is an example of a DSL embedded into Haskell.
43* Cryptol, a language and toolchain for developing and verifying cryptography
44 algorithms, is implemented in Haskell.
45* Facebook implements its anti-spam programs in Haskell, maintaining the
46 underlying data access library as open-source software.
47* The Cardano blockchain platform is implemented in Haskell.
48* GitHub implemented Semantic, an open-source library for analysis, diffing, and
49 interpretation of untrusted source code, in Haskell.
50* Standard Chartered's financial modelling language Mu is syntactic Haskell
51 running on a strict runtime.
52* seL4, the first formally verified microkernel, used Haskell as a prototyping
53 language for the OS developer. At the same time, the Haskell code defined an
54 executable specification with which to reason, for automatic translation by
55 the theorem-proving tool. The Haskell code thus served as an intermediate
56 prototype before final C refinement.
57* Target stores' supply chain optimization software is written in Haskell.
58* Co–Star
60### Some more examples from Serokell
62* Hasura is an open-source GraphQL engine that gives you instant access to a
63 GraphQL API for your data. In the GitHub repository of Hasura, Haskell is the
64 most used language, followed by languages like TypeScript, JavaScript, Python,
65 and Go. At the start of 2022, Hasura raised \$100 million.
66* Microsoft uses Haskell in Bond, a cross-platform framework for working with
67 schematized data. The Haskell part of Bond is mainly gbc, a command-line code
68 generation tool that can generate C++ and C# code using Bond. For a long time,
69 Microsoft Research also significantly supported the Glasgow Haskell Compiler
70 (GHC) by employing Simon Peyton Jones to work on it.
71* Tesla has been regularly hiring Haskell engineers and interns for some years
72 already. There isn’t a lot of information out there about the exact specifics
73 of their work. According to a comment in a job post made on Reddit, they use
74 Haskell to generate C code that is then compiled into vehicle firmware.
75* Freckle is an online learning platform that adjusts to the skills of students.
76 They use Haskell for all of their backend services and most of their tooling.
77* Galois is a company that specializes in the research and development of
78 trustworthy software systems for fields where failure is unacceptable. One of
79 their most interesting projects is Copilot, a stream-based DSL for writing and
80 monitoring embedded C programs.
81* Serokell uses Haskell to great success for most of their day-to-day software
82 development projects. They have used it to build the settlement layer of the
83 Cardano blockchain, programming languages for the Tezos blockchain,
84 cryptocurrency exchanges, a multi-currency wallet with its own DSL and more.
86## What are the benefits of using Haskell?
88Companies above list three things as the most significant benefits of Haskell:
901. Expressive type system.
912. Strong correctness guarantees.
923. Easy refactoring.
94In addition, while Haskell is a great programming language for everyday
95purposes, we can separate two popular use cases:
971. Complex systems where strong guarantees are required (such as blockchains
98 like Cardano and fintech projects like Klarna).
992. The second is compilers and DSLs. On our list, the examples for this use case
100 are projects like Bond, Semantic, Copilot.
102Both of these use cases heavily benefit from the strengths of Haskell and manage
103to dodge most of its cons. They can rightfully be called the “killer apps” of
104Haskell.
106If you are considering a project in these two areas, it’s hard to go wrong with
107picking Haskell.
109# Basic language constructs
111This file is actually a markdown file with a postprocessor run on it to extract
112haskell code blocks like these:
114```haskell
115a = 5
116```
118Normal code is written in normal source code files with extension `.hs` called
119**modules**. To write a comment, a double dash is used:
121```haskell
122-- This is a comment inside code block in a literate Haskell file.
123{- And this is a multiline comment.
124 A little useless, isn't it? -}
125```
127## Primitive Datatypes and Operators
129Of course, Haskell has numbers, booleans, characters, strings and whatnot:
131```haskell
132b = a + 1 -- 6
133c = 3 - 2 -- 1
134d = 10 * 2 -- 20
135e = 35 / 5
136```
138However, if you load this file into a Haskell interpreter called `ghci`, you'll
139see the following:
141```
142~/haskell-lectures-2024 $ ghci 1.Intro.lhs
143Loaded package environment from /home/turtle/.ghc/x86_64-linux-9.4.8/environments/default
144GHCi, version 9.4.8: https://www.haskell.org/ghc/ :? for help
145package flags have changed, resetting and loading new packages...
146Loaded GHCi configuration from /home/turtle/haskell-2024/haskell-lectures-2024/.ghci
147[1 of 2] Compiling Main ( 1.Intro.lhs, interpreted )
148Ok, one module loaded.
149ghci> e
1507.0
151```
153So division is not integer division by default. Actual integer division:
155```haskell
156this_is_eight = 35 `div` 4
157```
159Boolean operations:
161```haskell
162f = not True
163g = not False
164h = True && False
165i = True || False
166j = 1 == 1
167k = 1 /= 1
168l = 1 < 10
169```
171In the above examples, `not` is a function that takes one value. Haskell doesn't
172need parentheses for function calls --- all the arguments are just listed after
173the function. So the general pattern is: `func arg1 arg2 arg3...` See the
174section on functions for information on how to write your own.
176Strings and characters:
178```haskell
179m = "This is a string."
180n = 'a'
181```
183```haskell ignore
184o = 'You cant use single quotes for strings.' -- error!
185```
187Strings can be concatenated:
189```haskell
190p = "Hello " ++ "world!"
191```
193A string is a list of characters:
195```haskell
196q = ['H', 'e', 'l', 'l', 'o']
197```
199Lists can be indexed with the `!!` operator followed by an index
201```haskell
202s = "This is a string" !! 0
203```
205## Lists
207Every element in a list must have the same type. These two lists are equal:
209```haskell
210l1 = [1, 2, 3, 4, 5]
211l2 = [1..5]
212```
214Ranges are versatile.
216```haskell
217charRange = ['A'..'F'] -- "ABCDEF"
218orderingRange = [LT .. GT] -- [LT, EQ, GT]
219```
221You can create a step in a range.
223```haskell
224evenStep = [0,2..10] -- [0, 2, 4, 6, 8, 10]
225notStepped = [5..1] -- [] (Haskell defaults to incrementing)
226decrementStep = [5,4..1] -- [5, 4, 3, 2, 1]
227```
229Indexing into a list
231```haskell
232thirdGlyph = [1..10] !! 3 -- 4 (zero-based indexing)
233```
235You can also have infinite lists in Haskell!
237```haskell
238nats = [1..] -- a list of all the natural numbers
239```
241Infinite lists work because Haskell has "lazy evaluation". This means that
242Haskell only evaluates things when it needs to. So you can ask for the 1000th
243element of your list and Haskell will give it to you:
245```haskell
246someNat = [1..] !! 999 -- 1000
247```
249And now Haskell has evaluated elements 1 - 1000 of this list... but the rest of
250the elements of this "infinite" list don't exist yet! Haskell won't actually
251evaluate them until it needs to.
253Joining two lists:
255```haskell
256joinedLists = [1..5] ++ [6..10]
257```
259Adding to the head of a list:
261```haskell
262headCat = 0:[1..5] -- [0, 1, 2, 3, 4, 5]
263```
265More list operations
267```haskell
268someHead = head [1..5] -- 1
269someTail = tail [1..5] -- [2, 3, 4, 5]
270someInit = init [1..5] -- [1, 2, 3, 4]
271someLast = last [1..5] -- 5
272```
274Actually, you can lookup them on [hoogle](https://hoogle.haskell.org/) or, even
275better, find them there by their type signature!
277List comprehensions
279```haskell
280comp = [x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10]
281```
283With a conditional
285```haskell
286compCond = [x*2 | x <- [1..5], x*2 > 4] -- [6, 8, 10]
287```
289With several iteratees
291```haskell
292twoD = take 10 [x ^ 2 * y | x <- [1..], even x, x `mod` 3 == 0, y <- [1, -1]]
293```
295## Tuples
297Every element in a tuple can be a different type, but a tuple has a fixed
298length. A tuple:
300```haskell
301tupleExample = ("haskell", 1)
302```
304Accessing elements of a pair (i.e. a tuple of length 2)
306```haskell
307someFst = fst ("haskell", 1) -- "haskell"
308someSnd = snd ("haskell", 1) -- 1
309```
311Pair element accessing does not work on n-tuples (i.e. triple, quadruple, etc)
313```haskell ignore
314snd ("snd", "can't touch this", "da na na na") -- error! see function below
315```
317## Functions
319A simple function that takes two variables
321```haskell
322add a b = a + b
323```
325Using the function
327```haskell
328three = add 1 2 -- 3
329```
331You can also put the function name between the two arguments with backticks:
333```haskell
334alsoThree = 1 `add` 2 -- 3
335```
337You can also define functions that have no letters! This lets you define your
338own operators! Here's an operator that does integer division
340```haskell
341(//) a b = a `div` b
342eight = 35 // 4 -- 8
343```
345### Guards
347An easy way to do branching in functions
349```haskell
350fib x
351 | x < 2 = 1
352 | otherwise = fib (x - 1) + fib (x - 2)
353```
355### Pattern matching
357Pattern matching is similar. Here we have given three different equations that
358define `fib'`. Haskell will automatically use the first equation whose left hand
359side pattern matches the value.
361```haskell
362fib' 1 = 1
363fib' 2 = 2
364fib' x = fib' (x - 1) + fib' (x - 2)
365```
367### Pattern matching on tuples
369```haskell
370sndOfTriple (_, y, _) = y -- use a wild card (_) to bypass naming unused value
371```
373### Pattern matching on lists
375Here `x` is the first element in the list, and `xs` is the rest of the list. We
376can write our own map function:
378```haskell
379myMap func [] = []
380myMap func (x:xs) = func x:(myMap func xs)
381```
383Anonymous functions are created with a backslash followed by all the arguments.
385```haskell
386mapped = myMap (\x -> x + 2) [1..5] -- [3, 4, 5, 6, 7]
387```
389### Using fold (called `inject` in some languages) with an anonymous function
391`foldl1` means fold left, and use the first value in the list as the initial
392value for the accumulator.
394```haskell
395usingFold = foldl1 (\acc x -> acc + x) [1..5] -- 15
396```
398### Partial application
400If you don't pass in all the arguments to a function, it gets "partially
401applied". That means it returns a function that takes the rest of the arguments.
403```haskell
404foo = add 10 -- foo is now a function that takes a number and adds 10 to it
405fifteen = foo 5 -- 15
406```
408Another way to write the same thing. It is called a "section":
410```haskell
411foo' = (10 +)
412alsoFifteen = foo' 5 -- 15
413```
415### Function composition
417The operator `.` chains functions together. For example, here `foo` is a
418function that takes a value. It adds 10 to it, multiplies the result of that by
4194, and then returns the final value.
421```haskell
422foo'' = (4 *) . (10 +)
423sixty = foo'' 5 -- 4*(10+5) = 60
424```
426### Fixing precedence
428Haskell has an operator called `$`. This operator applies a function to a given
429parameter. In contrast to standard function application, which has highest
430possible priority of 10 and is left-associative, the `$` operator has priority
431of 0 and is right-associative. Such a low priority means that the expression on
432its right is applied as a parameter to the function on its left.
434Before:
436```haskell
437evenSeven = even (fib 7) -- false
438```
440Equivalently:
442```haskell
443evenSeven' = even $ fib 7 -- false
444```
446Composing functions:
448```haskell
449evenSeven'' = even . fib $ 7 -- false
450```
452## Type signatures
454Haskell has a very strong type system, and every valid expression has a type.
455Some basic types:
457```haskell
458five = 5 :: Integer
459hello = "hello" :: String
460truth = True :: Bool
461```
463Functions have types too. `not` takes a boolean and returns a boolean:
465```haskell
466not' = not :: Bool -> Bool
467```
469Here's a function that takes two arguments:
471```haskell
472add' = add :: Integer -> Integer -> Integer
473```
475When you define a value, it's good practice to write its type above it:
477```haskell
478double :: Integer -> Integer
479double x = x * 2
480```
482### Typed holes
484Haskell's type system allows one to use "typed holes" -- you can replace an
485expression with an underscore and a compiler would tell you what is the expected
486type of a missing expression:
488```haskell ignore
489double' x = _ * 2 :: Integer
490```
492## Control Flow and If Expressions
494if-expressions
496```haskell
497haskell = if 1 == 1 then "awesome" else "awful" -- haskell = "awesome"
498```
500if-expressions can be on multiple lines too, indentation is important
502```haskell
503haskell' = if 1 == 1
504 then "awesome"
505 else "awful"
506```
508Case expressions: Here's how you could parse command line arguments
510```haskell
511parseArgs args =
512 case args of
513 "help" -> printHelp
514 "start" -> startProgram
515 _ -> putStrLn "bad args"
517printHelp = undefined -- see Hoogle to learn what this is
518startProgram = undefined
519```
521Haskell doesn't have loops; it uses recursion instead. map applies a function
522over every element in a list
524```haskell
525someEvens = map (*2) [1..5] -- [2, 4, 6, 8, 10]
526```
528You can make a for function using map
530```haskell
531for array func = map func array
532```
534And then use it
536```haskell
537strings = for [0..5] $ \i -> show i
538```
540We could've written that like this too:
542```haskell
543strs = for [0..5] show
544```
546You can use `foldl` or `foldr` to reduce a list:
547`foldl <fn> <initial value> <list>`
549```haskell
550fortyThree = foldl (\x y -> 2*x + y) 4 [1,2,3] -- 43
551```
553(This is the same as (2 * (2 * (2 * 4 + 1) + 2) + 3))
555While `foldl` is left-handed, `foldr` is right-handed
557```haskell
558sixteen = foldr (\x y -> 2*x + y) 4 [1,2,3] -- 16
559```
561(This is now the same as (2 * 1 + (2 * 2 + (2 * 3 + 4))))
563## Data Types
565A data type is declared with a 'type constructor' on the left and one or more
566'data constructors' on the right, separated by the pipe | symbol. This is a
567sum/union type. Each data constructor is a (possibly nullary) function that
568creates an object of the type named by the type constructor.
570This is essentially an enum
572```haskell
573data Color = Red | Blue | Green -- would be an enum in other languages
574```
576Now you can use it in a function:
578```haskell
579say :: Color -> String
580say Red = "You are Red!"
581say Blue = "You are Blue!"
582say Green = "You are Green!"
583```
585Note that the type constructor is used in the type signature and the data
586constructors are used in the body of the function. Data constructors are
587primarily pattern-matched against.
589This next one is a traditional container type holding two fields. In a type
590declaration, data constructors take types as parameters. Data constructors can
591have the same name as type constructors: this is common where the type only has
592a single data constructor, although here we do not do this to reduce confusion
593for newcomers.
595```haskell
596data Point = Cartesian Float Float
597```
599This can be used in a function like:
601```haskell
602distance :: Point -> Point -> Float
603distance (Cartesian x y) (Cartesian x' y') = sqrt $ dx + dy
604 where dx = (x - x') ** 2
605 dy = (y - y') ** 2
606```
608Types can have multiple data constructors with arguments, too
610```haskell
611data Name = Mononym String
612 | FirstLastName String String
613 | FullName String String String
614```
616To make things clearer, we can use record syntax
618```haskell
619data PointRecord = CartesianPoint2D { x :: Float, y :: Float }
620 | PolarPoint2D { r :: Float, theta :: Float }
622myPoint = CartesianPoint2D { x = 7.0, y = 10.0 }
623```
625Using record syntax automatically creates accessor functions:
627```haskell
628xOfMyPoint = x myPoint -- 7.0
629```
631Record syntax also allows a simple form of update
633```haskell
634myPoint' = myPoint { x = 9.0 } -- CartesianPoint2D { x = 9.0, y = 10.0 }
635```
637Even if a type is defined with record syntax, it can be declared like a simple
638data constructor. This is fine:
640```haskell
641myPoint'' = CartesianPoint2D 3.3 4.0
642```
644It's also useful to pattern match data constructors in `case` expressions
646```haskell
647distanceFromOrigin x =
648 case x of (CartesianPoint2D x y) -> sqrt $ x ** 2 + y ** 2
649 (PolarPoint2D r _) -> r
650```
652Your data types can have type parameters too:
654```haskell ignore
655data Maybe a = Nothing | Just a
656```
658These are all `Maybe`s, but with different `a`s:
660```haskell
661justHello = Just "hello" -- of type `Maybe String`
662justOne = Just 1 -- of type `Maybe Int`
663nothing = Nothing -- of type `Maybe a` for any `a`
664```
666For convenience we can also create type synonyms with the `type` keyword
668```haskell
669type String' = [Char]
670```
672Unlike `data` types, type synonyms need no constructor, and can be used anywhere
673a synonymous data type could be used. Say we have the following type synonyms
674and items with the following type signatures:
676```haskell
677-- For demonstration purposes, these types have no constructors
678data Person
679data Circle
681-- Types in which we are interested
682type Weight = Float
683type Height = Float
684type Point2D = (Float, Float)
686getMyHeightAndWeight :: Person -> (Height, Weight)
687getMyHeightAndWeight = undefined
689findCenter :: Circle -> Point2D
690findCenter = undefined
692somePerson :: Person
693somePerson = undefined
695someCircle :: Circle
696someCircle = undefined
698distance2D :: Point2D -> Point2D -> Float
699distance2D = undefined
700```
702Then the following would compile and run without issue even though it does not
703make sense semantically:
705```haskell
706nonsense = distance2D (getMyHeightAndWeight somePerson) (findCenter someCircle)
707```
709## Typeclasses
711Typeclasses are one way Haskell does polymorphism. They are similar to
712interfaces in other languages: a typeclass defines a set of functions that must
713work on any type that is in that typeclass.
715The
716[Eq](https://hackage.haskell.org/package/base-4.19.0.0/docs/Prelude.html#t:Eq)
717typeclass is for types whose instances can be tested for equality with one
718another.
720```haskell ignore
721class Eq a where
722 (==) :: a -> a -> Bool
723 (/=) :: a -> a -> Bool
724 x == y = not (x /= y)
725 x /= y = not (x == y)
726```
728This defines a typeclass that requires two functions, `(==)` and `(/=)`. It also
729declares that one function can be declared in terms of another, so it is enough
730that __either__ the `(==)` function or the `(/=)` is defined and the other will
731be "filled in" based on the typeclass definition.
733To make a type a member of a type class, the `instance` keyword is used
735```haskell
736data TrafficLight = TLRed | TLYellow | TLGreen
738instance Eq TrafficLight where
739 TLRed == TLRed = True
740 TLGreen == TLGreen = True
741 TLYellow == TLYellow = True
742 _ == _ = False
743```
745(Note that, while declaring `TrafficLight` constructors, we had to name
746constructors differently in order for them to not clash with `Color`
747constructors we defined earlier. It is common to either prefix constructor
748names with type name abbreviation or define types in different modules
749altogether)
751Now we can use `(==)` and `(/=)` with `TrafficLight` objects:
753```haskell
754canProceedThrough :: TrafficLight -> Bool
755canProceedThrough t = t /= TLRed
756```
758You can NOT create an instance definition for a type synonym, though.
760Functions can be written to take typeclasses with type parameters, rather than
761types, assuming that the function only relies on features of the typeclass:
763```haskell
764isEqual :: Eq a => a -> a -> Bool
765isEqual x y = x == y
766```
768Note that `x` and `y` MUST be the same type, as they are both defined as being
769of type parameter `a`. A typeclass does not state that different types in the
770typeclass can be mixed together. So `isEqual Red 2` is invalid even though `2`
771is an `Int` which is an instance of `Eq`, and `Red` is a `TrafficLight` which is
772also an instance of `Eq`.
774Other common typeclasses are:
776* `Ord` for types that can be ordered, allowing you to use `>`, `<=`, etc.
777* `Read` for types that can be created from a string representation
778* `Show` for types that can be converted to a string for display
779* `Num`, `Real`, `Integral`, `Fractional` for types that can do math
780* `Enum` for types that can be stepped through
781* `Bounded` for types with a maximum and minimum
783### Literals
785We used character, string and list literals back from the very start. It is
786simple to understand what they mean and how to use them. However, numeric
787literals are not so simple. If we ask GHCi: "what is the type of 2?", we get:
789```
790ghci> :t 2
7912 :: Num a => a
792```
794So numeric literals are actually polymorphic and do not have a concrete type.
795Not that this is not the same as casts because the following is not allowed:
797```haskell
798two = 2 :: Integer
799```
800```haskell ignore
801float = two * 0.5 -- would not pass the type check
802```
804### Deriving
806Haskell can automatically make types part of `Eq`, `Ord`, `Read`, `Show`, `Enum`
807and `Bounded` with the `deriving` keyword at the end of the type declaration
809```haskell
810data PointDerives = Point Float Float deriving (Eq, Read, Show)
811```
813In this case it is NOT necessary to create an `instance` definition.
815## Haskell IO
817While IO can't be explained fully without explaining monads, it is not hard to
818explain enough to get going.
820When a Haskell program is executed, `main` is called. It must return a value of
821type `IO a` for some type `a`. For example:
823```haskell
824main :: IO ()
825main = putStrLn $ "Hello, sky! " ++ (say Blue)
826```
828(`putStrLn` has type `String -> IO ()`)
830It is easiest to do IO if you can implement your program as a function from
831String to String. The function
833```haskell
834interact' = interact :: (String -> String) -> IO ()
835```
837Inputs some text, runs a function on it, and prints out the output.
839```haskell
840countLines :: String -> String
841countLines = show . length . lines
843main' = interact countLines
844```
846You can think of a value of type `IO ()` as representing a sequence of actions
847for the computer to do, much like a computer program written in an imperative
848language. We can use the `do` notation to chain actions together. For example:
850```haskell
851sayHello :: IO ()
852sayHello = do
853 putStrLn "What is your name?"
854 name <- getLine -- this gets a line and gives it the name "name"
855 putStrLn $ "Hello, " ++ name
856```
858Exercise: write your own version of `interact` that only reads one line of
859input.
861If you are running this module in GHCi, it can run any IO action on demand:
863```
864ghci> sayHello
865What is your name?
866Pavel
867Hello, Pavel
868```
870Let's understand better how the function `getLine` we just used works. Its type
871is:
873```haskell
874getLine' = getLine :: IO String
875```
877You can think of a value of type `IO a` as representing a computer program that
878will generate a value of type `a` when executed (in addition to anything else it
879does). We can name and reuse this value using `<-`. We can also make our own
880action of type `IO String`:
882```haskell
883action :: IO String
884action = do
885 putStrLn "This is a line. Duh"
886 input1 <- getLine
887 input2 <- getLine
888 -- The type of the `do` statement is that of its last line.
889 -- `return` is not a keyword, but merely a function
890 return (input1 ++ "\n" ++ input2) -- return :: String -> IO String
891```
893However, do-notation is not some compiler magic, this is just a syntactic sugar
894over two operators called `>>` and `>>=`. The action below is the same as the
895action above:
897```haskell
898action' = putStrLn "This is a line. Duh" >> (getLine >>= (\input1 ->
899 getLine >>= (\input2 -> return (input1 ++ "\n" ++ input2))))
900```
902We can use this just like we used `getLine`:
904```haskell
905main'' = do
906 putStrLn "I will echo two lines!"
907 result <- action
908 putStrLn result
909 putStrLn "This was all, folks!"
910```
912The type `IO` is an example of a "monad". The way Haskell uses a monad to do IO
913allows it to be a purely functional language. Any function that interacts with
914the outside world (i.e. does IO) gets marked as `IO` in its type signature. This
915lets us reason about which functions are "pure" (don't interact with the outside
916world or modify state) and which functions aren't.
918This is a powerful feature, because it's easy to run pure functions
919concurrently; so, concurrency in Haskell is very easy.
921## Monads
923Actually, `do`-notation has wider application than only constructing IO values.
924Is is applicable for every _monad_:
926```
927ghci> :i Monad
928type Monad :: (* -> *) -> Constraint
929class Applicative m => Monad m where
930 (>>=) :: m a -> (a -> m b) -> m b
931 (>>) :: m a -> m b -> m b
932 return :: a -> m a
933```
935In other words, `m` is a monad if it allows to construct "pure" values of type
936`m a` from any value of type `a` and if it allows to sequence actions of type
937`m a` and `m b` and yield a result of type `m b`, where the second action either
938can (`(>>=)`) or cannot (`(>>)`) depend on the result of the first action.
940This is discussed more in detail in the next lecture.
942# Sources & References
944* [Wikipedia page on Haskell](https://en.wikipedia.org/wiki/Haskell)
945* [Serokell blog post on Haskell in industry](https://serokell.io/blog/top-software-written-in-haskell)
946* [Memo for basic Haskell constructions](https://learnxinyminutes.com/docs/haskell/)
947* [Hoogle, Haskell code search](https://hoogle.haskell.org/)