27

I saw Haskell has a sleep function called "delay": Control.Concurrent.Thread.Delay

My question is: if Haskell is purely functional, how is it possible to have such thing like this? Isn't sleep a collateral effect or am I missing something?

Chris Stryczynski
  • 30,145
  • 48
  • 175
  • 286
Felipe
  • 16,649
  • 11
  • 68
  • 92
  • 1
    Haskell isn't purely functoinal, otherwise you won't have I/O. – simonzack Aug 29 '14 at 21:01
  • 22
    @simonzack Well not true. Haskell is purely functional. The `IO` monad builds up a separate side effecting program. By strictly separating sdie effecting functions (like `delay`) we can due things that look impure, but are really just purely generating instructions to do impure things. – daniel gratzer Aug 29 '14 at 21:07
  • 1
    @jozefg You should post that as an answer. – Sibi Aug 29 '14 at 21:08
  • 2
    If Haskell were entirely purely functional, all it would be good for is heating a room. You'd never do anything more than run a computation without printing anything. As it stands, IO is possible, but as jozefg states, the IO monad more or less builds up a pure sequence of instructions to perform and then the runtime interprets it to perform the impure actions. Something you might be interested in looking at are Free monads, which let you build "languages" quite simply that you can interpret in different ways. – bheklilr Aug 29 '14 at 21:14
  • 14
    Based on the title, I wanted so badly to close this question as off-topic and should be on travel.stackexchange.com. – Erik Philips Aug 29 '14 at 21:16
  • 7
    @bheklilr: Heat generation is a side effect. – mipadi Aug 29 '14 at 21:49
  • 3
    You could argue that all of haskell is pure functional programming, including manipulating things with type `IO a`, apart from the magic that anything successfully bound to `main` actually happens. – AndrewC Aug 30 '14 at 07:26
  • If `delay` always returns the same result given the same input, isn't it pure regardless of how long it takes to execute? – runeks Apr 09 '16 at 07:05
  • Related: https://stackoverflow.com/questions/7267760/how-can-a-time-function-exist-in-functional-programming . – atravers Nov 17 '20 at 08:48

3 Answers3

56

Alright, moving my comment to an answer. Do note that there are multiple views of the IO monad and these ideas. You can achieve similar results with uniqueness types and whatnot.

I just happen to find this the simplest and coolest explanation.

Faking Impurity

Haskell is a purely functional language. This should mean that evaluating a Haskell program should always produce the same results. However, that doesn't seem like the case! Look at something like

-- Echo.hs
main :: IO ()
main = getLine >>= putStrLn

This seems to do something different depending on user input.

Really anything that lives in IO looks like it can do wildly different things depending on everything from the state of the moon to the life insurance costs of Schrodinger's cat.

More over, it seems like any language that can do anything useful must be impure. Unless your interested in watching your CPU spin, producing side effects is all that programs exist for after all!

Evil Interpreters

In fact this isn't actually the case! The appropriate mental model is to imagine IO as something like

data IO a = PutStrLn String a
          | GetLine (String -> a)
          ...

So IO could just be a data structure representing a sort of "plan" for the program to execute. Then the evil impure Haskell runtime actually executes this plan, producing the results you see.

This isn't just a minor semantic quibble though, we can do something like

runBackwards :: [IO ()] -> IO ()
runBackwards  = foldr (>>) (return ()) . reverse

In other words we can manipulate our "plans" as normal, first class values.

We can evaluate them, force them, drop a ton of bricks on them, even say mean things about them behind their backs and they'll never produce a side effect! They can't you see, normal Haskell code can only build up IO actions to be evaluated by the run time, it's incapable of doing anything noticeable.

In a way you can almost view a Haskell program as the ultimate form of metaprogramming, producing programs on the fly during runtime and having them evaluated by some interpreter.

So when you say

 foo = delay 20

You're not saying "Delay this program for 20 whatevers", you're saying "In the program that this code builds, pause its execution for 20 whatevers when it runs".

Who Cares

It's fair to ask "Who Cares": if this code gets run at some point who cares who runs it? What good does it do to be purely functional in this way? It can actually have some interesting effects (heh).

For example, think of something like http://www.tryhaskell.org, clearly it needs to run Haskell code, but it also can't just blindly execute whatever IO it gets! What it can do is provide a different implementation of IO, while exposing an identical API.

This new IO builds up a nice tree like datastructure which can be easily sanitized and checked by the web backend to ensure that it never runs something evil. We can even compile our fake-IO structure to the normal one that GHC provides and execute it efficiently on the server! Since there's never anything evil in their to begin with we only have to trust code we wrote.

No more endless applet-style security holes. By replacing IO we know beyond a shadow of a doubt that we can execute this code and it will never attempt to do something evil.

Evil Interpreters Everywhere

In fact, this notion of building up data structures is useful for more than just IO. It's a great way to structure any project that aims to provide a limited DSL. Anything from

  • A query language
  • Game scripting
  • Code generation
  • Writing "client side haskell"

All of these can be solved by building up normal datastructures and "compiling" them to the appropriate language. The usual trick for this is to use a free monad. If you're an intermediate Haskeller, go learn about em!

daniel gratzer
  • 52,833
  • 11
  • 94
  • 134
  • 3
    Shouldn't `runBackwards` have type `[IO ()] -> IO ()`? – amalloy Aug 30 '14 at 02:40
  • As far as I can see, it's possible to view a Haskell program as one huge, pure function that produces a sequence of IO actions. The IO actions, when evaluated, may produce different results given the same input, but the Haskell program always produces the same sequence of IO actions given the same input, which means that it's pure. – runeks Apr 09 '16 at 07:02
7

"Purely functional" is a interesting term.

Everything that needs to deal with the real world isn't purely functional at the first look. But somehow Haskell needs to deal with the real world. Otherwise the language wouldn't make sense.

So the solution is the IO monad. I won't go into much detail here (I'm sure you can find another explanation of the IO monad). The IO monad is roughly a function with the type RealWorld -> RealWorld.

That means that instead of doing something outside of our program, we pretend the real world would be part of our program and every time we want to change something in the real world, we create another real world that we like more.

The trick with delay is that we take the current world and create another one where the time is advanced the given amount of seconds.

Kritzefitz
  • 2,644
  • 1
  • 20
  • 35
  • 2
    The `RealWorld` function explanation isn't very good. It doesn't address many of Haskell's actual features (ie multiple threads) and, in my experience, tends to confuse people more often than enlightening. – Tikhon Jelvis Aug 29 '14 at 22:49
  • GHC has something called `RealWorld` internally, but it's an implementation detail which is actually a bit misleading if you take it at face value. That just adds to the confusion of using this explanation for IO. – Tikhon Jelvis Aug 29 '14 at 22:50
  • This reminds me a kind of "virtual machine". Is it wrong to make this association? – Felipe Aug 30 '14 at 01:46
  • @TikhonJelvis It's right, the explanation isn't as good as that of jozefg. Actually if I think about IO I think of it more like jozefgs version, but didn't think of that when writing that answer. – Kritzefitz Aug 30 '14 at 13:26
-7

A pure functional language shouldn't have the functions like "delay", "writeFile", etc. because they produce side effects. But Haskell has these functions and it deals with them by a container titled IO which is also a monad. IO hides all the side effects made by these functions.

Incerteza
  • 32,326
  • 47
  • 154
  • 261
  • 7
    I get that it's a hard question, but this is a really misleading answer. Thinking of everything in IO as containers that "hide side effects" is simply an incorrect mental model. – John Tyree Aug 31 '14 at 21:07
  • @JohnTyree, I don't see anything incorrect in that. – Incerteza Sep 02 '14 at 14:36
  • 3
    Because monads in general and *particularly* IO are not containers. If you read the accepted answer by @jozefg he does a great job of illustrating this idea. A swing through #haskell-beginners on freenode will help a lot too. This discussion happens pretty frequently. – John Tyree Sep 02 '14 at 19:08
  • @JohnTyree, monads are containers. Just 12 - is a container for 12. – Incerteza Sep 02 '14 at 19:15
  • 3
    No. *Some* monads can be described by analogy with containers, *sometimes*. `Just 12` can be thought of as a `Just` container with a `12` in it, but what is the `Nothing` constructor then? It's clearly not a box because it doesn't *contain* emptiness. It *is* nothingness. The absence of stuff. There is no box at all. In reality `Maybe a` is not *container*, it's a *context* in which you can describe the presence or absence of a value of type `a`. – John Tyree Sep 05 '14 at 19:02
  • @JohnTyree, yes, you're the one who should read the the documentation before giving advice - http://www.haskell.org/haskellwiki/Monads_as_containers And Maybe monad is a container in particular. – Incerteza Sep 06 '14 at 05:31
  • 3
    That article (which *also* says that monads as computation is a more natural way to think of it) is fairly disputed. The Talk page shows this. Most importantly, it's clear that the wiki just chooses a few Monads which *kind of* work as an introduction. Even then, all of the explanations require that you ignore the "Monad" part entirely and just think about them as Functors. What about `Parser a`, `Snap ()` web apps, or the Tardis, which models time travel? Thinking of these as "containers" is useless. It's only a baby step towards a real understanding of Monads as computational contexts. – John Tyree Sep 06 '14 at 17:40
  • @JohnTyree, there always have been people who don't understand something and the Talk page proves that. Take another subject and there'll be the same kind of people too. – Incerteza Sep 06 '14 at 17:49
  • 3
    Well that's the whole point. There are always people who don't understand something and when faced with a problem they just try to reframe it in terms that they *do* understand. In this case it's not understanding monads and therefore only being able to see them as containers. You completely didn't address the only meaningful part of my comment. How is your mental model going to accommodate monads which clearly don't function like containers *at all*? Parsing? Operational? Snap? The list goes on... – John Tyree Sep 06 '14 at 20:19