Skip to content

Commit ad1c957

Browse files
committed
you should care
1 parent 10dd058 commit ad1c957

File tree

1 file changed

+71
-68
lines changed

1 file changed

+71
-68
lines changed

copy/entries/five-point-haskell-3.md

Lines changed: 71 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ difference between code that does IO and code that does not, you really have no
5757
hope in reasoning with _anything_.
5858

5959
If you have some familiarity with the topic (or have read my [past][]
60-
[posts][])), _technically_ we can look at IO in Haskell as still "pure" in the
60+
[posts][]), _technically_ we can look at IO in Haskell as still "pure" in the
6161
sense that we are purely constructing an IO action. That is, `putStrLn ::
6262
String -> IO ()` purely returns the same `IO ()` action every single time.
6363
`readIORef :: IORef a -> IO a` returns the same `IO a` action every time, even
@@ -130,71 +130,76 @@ stop, that is exactly the situation you _should_ be stopping in."
130130
And, so what? Why do we care in the first place, if the purpose of our function
131131
is to do IO anyway?
132132

133-
Well, you should care about unmarked IO if you care about global variables. IO
134-
is the ultimate unrestricted global variable: any `IO` action can freely modify
135-
the process environment or the file system: it can call `setEnv` or `lookupEnv`
136-
against global variables that can be access across your entire process.
137-
138-
You should care about IO if you care about memoization: we can safely cache
139-
`mkString 42` and `mkString 67` forever, without worrying that every time we
140-
access them they should have been different numbers.
141-
142-
You should care about unmarked IO if you care about safe refactoring and common
143-
subexpression elimination. Consider populating data with `mkString`:
144-
145-
```haskell
146-
mkUser :: Int -> User
147-
mkUser n = User { name = mkString n, ident = mkString n }
148-
```
149-
150-
This _should_ be the same as:
151-
152-
```haskell
153-
mkUser :: Int -> User
154-
mkUser n = User { name = nameAndIdent, ident = nameAndIdent }
155-
where
156-
nameAndIdent = mkString n
157-
```
158-
159-
In many cases, this could be more performant and save space. You might only
160-
have to allocate only a single heap object instead of multiple. However, this
161-
is _not_ a valid program optimization if `mkString` could do IO! Calling it
162-
twice could give different results, or affect the environment in different
163-
ways, than calling it once!
164-
165-
You should care about unmarked IO if you care about laziness. Granted, this
166-
requires a desire for laziness in the first place (so, Ocaml users, you're off
167-
the hook). But if you can imagine:
168-
169-
```haskell
170-
mkUser :: Int -> User
171-
mkUser n = User { name = b, ident = a }
172-
where
173-
a = mkString n
174-
b = mkString (n + 1)
175-
c = mkString (n + 3)
176-
```
177-
178-
What...what order are those `mkString`s called, in a lazy language? If there
179-
was unmarked IO, the order _does_ matter. If there wasn't, they _can't_. And
180-
`c` could not even be freely discarded if there was unmarked IO.
181-
182-
You should care about IO if you care about concurrency. Imagine parallel
183-
mapping over multiple numbers:
184-
185-
```haskell
186-
myStrings :: [String]
187-
myStrings = parMap mkString [1..100]
188-
```
189-
190-
If `mkString` had unmarked IO and accessed locks or mutexes, this could easily
191-
be a race condition. But it doesn't, so we can guarantee no race conditions. We
192-
can also be assured that the order in which we schedule our threads will have
193-
no affect on the result.
194-
195-
You should care about unmarked IO if you care about testing. Because it states
196-
no external dependency, you can test `mkString` without requiring any
197-
sandboxing, isolation, or extra interleaving interactions with other functions.
133+
Well:
134+
135+
* You should care about unmarked IO if you care about global variables. IO is
136+
the ultimate unrestricted global variable: any `IO` action can freely
137+
modify the process environment or the file system: it can call `setEnv` or
138+
`lookupEnv` against global variables that can be access across your entire
139+
process.
140+
141+
* You should care about IO if you care about memoization: we can safely cache
142+
`mkString 42` and `mkString 67` forever, without worrying that every time we
143+
access them they should have been different numbers.
144+
145+
* You should care about unmarked IO if you care about safe refactoring and
146+
common subexpression elimination. Consider populating data with `mkString`:
147+
148+
```haskell
149+
mkUser :: Int -> User
150+
mkUser n = User { name = mkString n, ident = mkString n }
151+
```
152+
153+
This _should_ be the same as:
154+
155+
```haskell
156+
mkUser :: Int -> User
157+
mkUser n = User { name = nameAndIdent, ident = nameAndIdent }
158+
where
159+
nameAndIdent = mkString n
160+
```
161+
162+
In many cases, this could be more performant and save space. You might only
163+
have to allocate only a single heap object instead of multiple. However,
164+
this is _not_ a valid program optimization if `mkString` could do IO!
165+
Calling it twice could give different results, or affect the environment in
166+
different ways, than calling it once!
167+
168+
* You should care about unmarked IO if you care about laziness. Granted, this
169+
requires a desire for laziness in the first place (so, Ocaml users, you're
170+
off the hook). But if you can imagine:
171+
172+
```haskell
173+
mkUser :: Int -> User
174+
mkUser n = User { name = b, ident = a }
175+
where
176+
a = mkString n
177+
b = mkString (n + 1)
178+
c = mkString (n + 3)
179+
```
180+
181+
What...what order are those `mkString`s called, in a lazy language? If
182+
there was unmarked IO, the order _does_ matter. If there wasn't, they
183+
_can't_. And `c` could not even be freely discarded if there was unmarked
184+
IO.
185+
186+
* You should care about IO if you care about concurrency. Imagine parallel
187+
mapping over multiple numbers:
188+
189+
```haskell
190+
myStrings :: [String]
191+
myStrings = parMap mkString [1..100]
192+
```
193+
194+
If `mkString` had unmarked IO and accessed locks or mutexes, this could
195+
easily be a race condition. But it doesn't, so we can guarantee no race
196+
conditions. We can also be assured that the order in which we schedule our
197+
threads will have no affect on the result.
198+
199+
* You should care about unmarked IO if you care about testing. Because it
200+
states no external dependency, you can test `mkString` without requiring
201+
any sandboxing, isolation, or extra interleaving interactions with other
202+
functions.
198203

199204
The Real Worlds
200205
---------------
@@ -226,5 +231,3 @@ The Bespoke World
226231
## Extensible Effects
227232

228233
## Wait, did I just write a Monad Tutorial?
229-
230-

0 commit comments

Comments
 (0)