5 ms·
It's a pure function because it doesn't do any IO. It returns an action that, when run, performs the IO. Because it's a pure function, we have referential trans
by willtim 7y ago
It's a pure function because it doesn't do any IO. It returns an action that, when run, performs the IO. Because it's a pure function, we have referential transparency, we can safely replace the function call with its computed value (the action) anywhere in the program.
- victorNicollet 7y agoIn a typical IO function, the first 10% of the code returns a monad-bind that holds the remaining 90% of the code in a closure. The pieces are pure, but the whole isn't meaningfully pure.
- deleted 7y ago[deleted]
- bad_user 7y agoA function returning IO preserves referential transparency. fn() + fn() // ---- val a = fn() a + a Are the two programs above equivalent? For a function returning IO, they are. For a function that's side effectful, they are not. (As an exercise for the reader, think of our function here returning Future/Promise instead of an IO, how would that change the program?) This is a definition of purity that you can work with. The trap that people fall in is to talk about fiction essentially. What is "meaningful" anyway? If you don't give it a definition, is it meaningful to talk about what's meaningful? I agree that a function returning IO is opaque. You can't reinterpret it, it's no longer a plan that you can change the interpreter for, but something concrete. There are of course better abstractions for dealing with effects in a way as to make them less opaque ... like the Free Monad, or Eff. Many of them have been expressed in Scala as well.
- victorNicollet 7y agoPurity is not a goal in itself, its purpose (its meaning) is to help me answer useful questions about the behaviour of the program. In a typical IO function, `fn()` only gives me 10% of the code in `fn`: the part up to the first bind. To get the rest, I need to `do` it, and this is where purity is no longer meaningful: a <- fn "a" b <- fn "b" Can I swap these two lines ? The IO monad tells me there might be consequences, but what are they ? Another example: foo fn Will `foo` trigger the IO effects of `fn` zero, one or more times ? I believe these are meaningful questions, and yet, a program built from pure functions can answer them no better than an imperative program.
- bad_user 7y ago> "Purity is not a goal in itself" Agreed. However having referential transparency for IO does help. Let me explain... The program you mentioned has one clear property: those two computations will execute sequentially. This sequence of executing operations becomes independent of evaluation via IO. And if you want parallelism, you need to make it explicit. val a = fn(1) val b = fn(2) for { r1 <- a r2 <- b } yield r1 + r2 For IO this always has the same execution characteristic. "B" will execute after "A". You have a clear happens-before relationship. As an exercise: 1. think of what happens if that function returns a Future/Promise 2. think of what happens if that function is synchronous, but still side effectful In both cases the actual order of execution is not guaranteed. In the second case the ordering is only guaranteed from the point of view of the current thread only. And this is interesting b/c the only way to force ordering in a multi-threaded context, besides memory barriers which are a runtime trick, is to create a data dependence. In other words, while this code doesn't guarantee visibility with IO, it does have stronger ordering guarantees than any side effectful code you can throw in there. You're a little unfair here btw. If the two operations here are not dependent on each other, then the Monad's flatMap/bind isn't the operation you want, since Monads literally describe sequencing. And you want sequencing in case order of execution does matter. In other words, when you see a sequence described via bind/flatMap, you have to assume that the order is relevant, because the author could've done this instead, which is self explanatory: (a, b).parMapN { (r1, r2) => r1 + r2 } And now they are executed in parallel, with no related evaluation gotchas (exposing the API in the Cats library here). So to say that IO doesn't give you any useful property here is factually untrue. The obvious gain is that you look at an expression and know exactly how and when it will execute.