@@ -57,7 +57,7 @@ difference between code that does IO and code that does not, you really have no
5757hope in reasoning with _ anything_ .
5858
5959If 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
6161sense that we are purely constructing an IO action. That is, `putStrLn ::
6262String -> 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."
130130And, so what? Why do we care in the first place, if the purpose of our function
131131is 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
199204The 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