Hi All,
(I welcome any sort of replies, even containing foul language, as
long as there's also information inside.)
I'm about to give a lecture on FP. Now, some concepts in FP I love, but for some I have a hard time finding a good angle. Specifically, the IO monad.
Consider the following imperative code:
service.fetchData
service.closeObviously, both method calls create side effects.
Now, (discarding iteratees), lets consider making this with the IO monad:
for (data <- service.fetchData;
_ <- service.close)
yield data
where all methods are changed to return IO[Data], IO[Unit] and also the calling code must now return an IO[X] for some X.
The question is, what have we gained?
Instead of sequencing by line order, we're explicitly sequencing in the for comprehension. Not much of a gain here, since any mistake that causes code to be written in the first order in the imperative case can also be done in the functional case.
I also don't see anything that makes the code less prone to other mistakes. Such as forgetting to close or calling close twise. Obviously, there are ways to fix this such as service.withData(f: Data => X), but again, the more imperative style of silently calling close inside withData doesn't loose to the functional style that must return IO[X].
What am I missing? I'd appreciate any other examples which show how the use of IO is superior by making code that is more robust.
Regards,
Ittay
The IO monad was a neat trick for combining side effects with lazy
evaluation. But your title seems to be wrong. Questioning FP? FP is
much broader than lazy evaluation. In fact, there is only one lazily
evaluated language in wide usage today and even its creators have said
that laziness was probably a mistake.
Strict languages don't need the IO monad, and generally don't have it,
even though they could. Bob Harper's posts in his "existential type"
series are a good explanation on why not.
An interesting quote from Simon Peyton Jones is that "laziness has
kept us pure". That is, it has motivated the use of monads to
represent effects.
I agree that a static representation of effects is very useful, but my
gut feeling is that there must be a more lightweight and polymorphic
way to get effect checking than what monads provide.
-- Martin
Well, the IO monad (or rather the IO type) is a simple and
straightforward way to keep the language pure without introducing an
additional effects system. So there are certainly benefits to using it
regardless of if the language is lazy or strict.
/Jesper Nordenberg
The IO monad was a neat trick for combining side effects with lazy evaluation. But your title seems to be wrong. Questioning FP? FP is much broader than lazy evaluation. In fact, there is only one lazily evaluated language in wide usage today and even its creators have said that laziness was probably a mistake. Strict languages don't need the IO monad, and generally don't have it, even though they could. Bob Harper's posts in his "existential type" series are a good explanation on why not. An interesting quote from Simon Peyton Jones is that "laziness has kept us pure". That is, it has motivated the use of monads to represent effects. I agree that a static representation of effects is very useful, but my gut feeling is that there must be a more lightweight and polymorphic way to get effect checking than what monads provide. -- Martin
You can also make the "World" argument not implicit; but in any case,
the type system has to ensure that you don't reuse an "old" world. This
is called "linear types", and there is a language which does something
similar ("uniqueness types"): <http://wiki.clean.cs.ru.nl/Clean>
How can this ever work in a distributed concurrent setting? I saw “Concurrent Clean” mentioned on that page, but couldn’t (quickly) find further mention. Where can I dig deeper?
Roland Kuhn
Typesafe – Enterprise-Grade Scala from the Experts
twitter: @rolandkuhn
That's basically the same question as "what does a type system buy me?".
It enables automatic detection of programming errors at compile time
rather than at runtime.
It also opens up opportunities for performance optimizations and makes
your code a whole lot easier to test.
> 2. what are the alternatives to IO monad? an implicit World argument?
The ones I know are uniqueness typing (for example Clean) and effects
tracking (for example D, DDC, upcoming Scala effects system). So, either
you track effects using the type system, or you build a separate system
for it.
/Jesper Nordenberg
Cheers
-- Martin
If I'd search for good examples where purity is useful, I'd look
elsewhere. For instance, parallel collections allow automatic speedups
in exchange for all computations being pure.
Cheers
-- Martin
Ittay Dror skrev 2011-10-10 07:16:
The title is probably too much of a troll. Sorry about that.
My questioning is specifically about making functions pure. And the
hoops one need to jump through to get that (at least the way I know
which is the IO monad). More specifically:
1. what does it buy me? in theory, pure functions are nice since they
behave better, but i want to find a use case that appeals to everyday
java developers. That is, a case where making a function pure removes
pitfalls (while the function remains basically the same)
That's basically the same question as "what does a type system buy me?". It enables automatic detection of programming errors at compile time rather than at runtime.
It also opens up opportunities for performance optimizations and makes your code a whole lot easier to test.
It's intriguing to contemplate the possibility of something lighter weight than a three method api!
I have to disagree here, or at least ask why you think so. Let's
consider the function `putStrLn` from Haskell with the type `String ->
IO ()`. The definition of "purity" I am used to basically requires that
`putStrLn "foo"` does not execute a side-effect (true) and does not
depend on (hidden) state such that subsequent executions yield exactly
the same value (also true). The side-effect is executed by the
underlying system only if it is somehow included in `main`.
On Mon, Oct 10, 2011 at 2:47 AM, Meredith Gregory <lgreg.m...@gmail.com> wrote:
It's intriguing to contemplate the possibility of something lighter weight than a three method api!(I know you're aware of all this, Greg, just pointing it out for the sake of the "debate" part of this mailing list)so, let's see...something with few methods may sound like it's lightweight, but that's like saying programming straight in binary is even lighterweight: look! there's only ones and zeroes!then again, don't forget about the monad laws, which, implicit in your program though they may be, are an important constraint on the interface (2 downsides: they are implicit, they add more constraints)the real deal breaker, I'd say, is that monads don't compose well -- even simply mixing Option (I mean, Maybe) and List in a for (pardon me, do) comprehension is cumbersome! (not so much in Scala, thanks to CanBuildFrom and breakOut). For a recent proposal to solve monad composition, see "the monad zipper" (http://lambda-the-ultimate.org/node/3929),
I say, monads are starting to sound like a regular straightjacket by now (well, this is a feature, not a bug -- to some degree)Personally, I think we (as in, a strict language with subtyping, objects,...) are much better off with a (lightweight...) polymorphic effect system (being developed for Scala by Lukas Rytz -- see also Disciple (http://disciple.ouroborus.net/) for something more Haskelly)cheersadriaan
val x = a; b
On the type level, every type T would be interpreted as IO[T]. Every
function application is a bind. And voila! No side effects!
So the only thing IO provides is that code *not* using it is certified
to be pure, whereas in Scala everything is in the IO monad.
Cheers
-- Martin
Ittay Dror skrev 2011-10-10 07:16:That's basically the same question as "what does a type system buy me?". It enables automatic detection of programming errors at compile time rather than at runtime.
The title is probably too much of a troll. Sorry about that.
My questioning is specifically about making functions pure. And the
hoops one need to jump through to get that (at least the way I know
which is the IO monad). More specifically:
1. what does it buy me? in theory, pure functions are nice since they
behave better, but i want to find a use case that appeals to everyday
java developers. That is, a case where making a function pure removes
pitfalls (while the function remains basically the same)
It also opens up opportunities for performance optimizations and makes your code a whole lot easier to test.
The ones I know are uniqueness typing (for example Clean) and effects tracking (for example D, DDC, upcoming Scala effects system). So, either you track effects using the type system, or you build a separate system for it.2. what are the alternatives to IO monad? an implicit World argument?
Simple:
foo : Int => Int
If your language is impure (like Scala), foo can format your HD, store and
retrieve stuff in a DB etc. If your language is pure, foo will always return the
same value given the same argument and it will do nothing else, thus it's much
easier to verify it's behavior.
You are thinking backwards, it's not the functions that perform side effects
that are easy to test, it's the ones that have no side effects. Problem in a
language without effects tracking is that you have no idea or control over which
functions perform side effects.
Also, knowing that a function is pure gives the compiler more options for
optimization, it can memoize the return value, it can complete remove calls if
the result is not used, it can parallelize calls etc.
So, how do you keep the language pure and still allow side effects? The IO monad
is a quite natural solution to that.
> If I have
> code that does 'for (_ <- close(socket); _ <- close(socket))',
> it will fail just the same as the (simpler) imperative code
> "socket.close; socket.close", wouldn't it? Side effects are not
> gone. They are just abstracted over, the question is whether the
> abstraction helps.
You have just discovered that ';' is shorthand for flatMap. It's the same
abstraction.
/Jesper Nordenberg
ok.foo: Int => IO[Int]Are you now somehow safe from having your HD reformatted?
Replies inline. Please note I'm replying from the perspective of
an every-day developer, not from the theoretical point of view. I
want to find a killer argument for using IO.
Ittay Dror <ittay.dror <at> gmail.com> writes:I'd love to see a code example where the introduction of IO[_] makes more errors detectable. In the code example I used in the first message, I couldn't find where it helps.Simple: foo : Int => Int If your language is impure (like Scala), foo can format your HD, store and retrieve stuff in a DB etc. If your language is pure, foo will always return the same value given the same argument and it will do nothing else, thus it's much easier to verify it's behavior.
You are thinking backwards, it's not the functions that perform side effects that are easy to test, it's the ones that have no side effects. Problem in a language without effects tracking is that you have no idea or control over which functions perform side effects.
Also, knowing that a function is pure gives the compiler more options for optimization, it can memoize the return value, it can complete remove calls if the result is not used, it can parallelize calls etc.
I don't understand your objection. It's the guaranteed lack of side effects
that buys us something. "Int => IO[Int]" is the same as
"Int => World => (Int, World)" so obviously it's still a pure function.
Compared to "Int => Int" though it's much harder to verify its behaviour.
/Jesper Nordenberg
Sorry, but that doesn't work. I have stumbled upon so many seemingly non
side-effecting methods with "innocent" names which did some really
fundamental state changes that I came to the conclusion that anything
which is not "documented" in the types harms development.
> For the same reasons, a function 'printMsg: String => IO[Unit]' can format my HD
> just as well
You are mixing two problems. Admittedly, putting so many different side
effects into `IO` is not a good idea. IMHO it would be good to separate
different effects into `Filesystem` or `Network` etc.
> If IO[_] is just a tag to mean a function is dangerous, then I'm fine with this
> approach (it is similar to how Option is an alternative to document the possible
> return of null), but then why go through the hassle of a monad? I can have:
> case class Dangerous[A](value: A)
This smells like you want to re-invent monads, as `Dangerous` can be
treated as a monad. I challenge you to write an API using that class and
I bet that you will use some monadic patterns with or without knowing it.
Fine, you obviously don't need and value compile time checking. Personally,
I value it very high. The IO monad may not be most precise and user friendly
way to encode side effects, but it's a simple solution and compared to having
no effects tracking I would take it any day.
> If IO[_] is just a tag to mean a function is dangerous, then I'm
> fine with this approach (it is similar to how Option is an
> alternative to document the possible return of null), but then why
> go through the hassle of a monad? I can have:
> case class Dangerous[A](value: A)
What's stopping you from doing: def makeSafe[A](d : Dangerous[A]) : A =
d.value? You do realize that many side effects are transitive?
> But when I test I know the function I am testing. It's not a random
> thing. Testing is a very big thing in the Java world, people manage
> to do it fine. Sometime they manage the difference between a test
> environment and a production environment with DI (so a fetchUser
> function will work with a real database in production, but be tested
> with a mock database)
Really? So you never test an abstract interface method without knowing the
details of the implementation?
That people in the Java world manage to do proper testing is a huge
overstatement. I would rather say that the opposite is true.
> True, but this can cause the reverse effect, like large memory
> consumption, thrashing of the cpu etc. In practice, I'd rather have
> code that works plainly and then optimize the bottlenecks I find,
> controlling how I do it. Of course it is easier to optimize pure
> code (e.g., scala's parallel collections). Btw, when the code is not
> pure I might need to optimize it manually anyway, so I need to use a
> profiler anyway.
So, your argument is that because you can't optimize some parts of the code
you might as well not do it for any part? Or maybe you're totally against
compiler optimizations? The JVM is not the right platform for you then.
/Jesper Nordenberg
That's quite a huge thing, don't you agree?
/Jesper Nordenberg
This is true in theory, but in practice, I usually know what a function does when I use it. People don't name their functions 'foo', they name them 'formatHD', 'readFromDB' etc. I can't imagine a situation where I'd write a function named 'factorial' and inside have code to format the HD.Sorry, but that doesn't work. I have stumbled upon so many seemingly non side-effecting methods with "innocent" names which did some really fundamental state changes that I came to the conclusion that anything which is not "documented" in the types harms development.
For the same reasons, a function 'printMsg: String => IO[Unit]' can format my HD just as wellYou are mixing two problems. Admittedly, putting so many different side effects into `IO` is not a good idea. IMHO it would be good to separate different effects into `Filesystem` or `Network` etc.If IO[_] is just a tag to mean a function is dangerous, then I'm fine with this approach (it is similar to how Option is an alternative to document the possible return of null), but then why go through the hassle of a monad? I can have: case class Dangerous[A](value: A)This smells like you want to re-invent monads, as `Dangerous` can be treated as a monad. I challenge you to write an API using that class and I bet that you will use some monadic patterns with or without knowing it.
Ittay Dror <ittay.dror <at> gmail.com> writes:This is true in theory, but in practice, I usually know what a function does when I use it. People don't name their functions 'foo', they name them 'formatHD', 'readFromDB' etc. I can't imagine a situation where I'd write a function named 'factorial' and inside have code to format the HD. For the same reasons, a function 'printMsg: String => IO[Unit]' can format my HD just as wellFine, you obviously don't need and value compile time checking. Personally, I value it very high. The IO monad may not be most precise and user friendly way to encode side effects, but it's a simple solution and compared to having no effects tracking I would take it any day.
If IO[_] is just a tag to mean a function is dangerous, then I'm fine with this approach (it is similar to how Option is an alternative to document the possible return of null), but then why go through the hassle of a monad? I can have: case class Dangerous[A](value: A)What's stopping you from doing: def makeSafe[A](d : Dangerous[A]) : A = d.value? You do realize that many side effects are transitive?
But when I test I know the function I am testing. It's not a random thing. Testing is a very big thing in the Java world, people manage to do it fine. Sometime they manage the difference between a test environment and a production environment with DI (so a fetchUser function will work with a real database in production, but be tested with a mock database)Really? So you never test an abstract interface method without knowing the details of the implementation?
That people in the Java world manage to do proper testing is a huge overstatement. I would rather say that the opposite is true.True, but this can cause the reverse effect, like large memory consumption, thrashing of the cpu etc. In practice, I'd rather have code that works plainly and then optimize the bottlenecks I find, controlling how I do it. Of course it is easier to optimize pure code (e.g., scala's parallel collections). Btw, when the code is not pure I might need to optimize it manually anyway, so I need to use a profiler anyway.So, your argument is that because you can't optimize some parts of the code you might as well not do it for any part? Or maybe you're totally against compiler optimizations? The JVM is not the right platform for you then.
/Jesper Nordenberg
Sure. But I think there will at some point be better ways to track
effects than monads. Monads are too
rigid. Adding a single println somewhere means you have to rewrite
your whole program.
-- Martin
This thread is epic.
Tony, I was just wondering when you would chime in. Care to enlighten us with
your views on the epicness of this thread and the IO monad?
/Jesper Nordenberg
Same argument would apply to `Option`. As a careful programmer, you
would always check `x.isDefined` before calling `x.get`, wouldn't you?
Actually, that would be the same as using `null`. Instead, we have that
higher level abstraction called "catamorphism" (or use the pattern
matching instead if you wish) which allows the compiler to check what
you are doing. Same thing for side effects.
Types also document aspects of the program for the reader.
--
Dean Wampler
"Functional Programming for Java Developers" and
"Programming Scala" (O'Reilly)
twitter: @deanwampler, @chicagoscala
http://polyglotprogramming.com
The usual approach is to write as many parts of your program as pure
functions and as few functions as possible using IO. That turns out to
work quite well.
Documentation which cannot get out of sync with the code and can be
reasoned about. The latter might seem like a totally academic idea, but
is extremely useful.
But you agree that this interpretation is not useful at all? Having no
possibility to choose whether to use IO or not does not make an effect
system.
Of course.
-- Martin
Word.
def isScalaLang(url: java.net.URL): Boolean = url == new
java.net.URL("http://www.scala-lang.org")
--
Daniel C. Sobral
I travel to the future all the time.
That's only true because `Option` is not "opaque" in its definition. If
`IO` had a catamorphism you also had a way out.
> With IO, I must propagate it up. Not only is this inconvenient, it also hurts
> abstraction. I know formatting the HD is a horrific thought, but I never
> encounter this. I do encounter reading from files and maybe creating them maybe
> even deletion. When I do that, I don't want this implementation detail to
> propagate up my whole program. e.g., If I have a fetchUser(userName) method, I
> wish for it to return User, regardless if it is a constant function (e.g. for
> testing), a partially applied one with a list of Users, or one that reads from
> the DB or from the filesystem. I would be happy if the compiler warned me before
> reading from the DB, so I'd make sure I'm doing it right, but then get out of my
> way once I've made sure my code is sound.
That's not entirely true. If you want to both side-effecting and not
side-effecting functions uniformly, you could simply lift the latter to
the former.
However, I see a point (also what Martin said) that Monads are too
"rigid", i. e. you have to propagate the use, but as long as there are
no other formalisms which doesn't suffer from that "problem" (if you
wish to call it so), `IO` is a reasonable solution.
As I said, the usual approach is that you strictly separate both types
of functions and transform the side effects with your pure functions.
`bind` and `fmap` are there for that.
With Option, I have a way out. I can write 'someOption getOrElse default'. So if some low level function returns Option, it doesn't mean every client of it needs to also return Option.That's only true because `Option` is not "opaque" in its definition. If `IO` had a catamorphism you also had a way out.
With IO, I must propagate it up. Not only is this inconvenient, it also hurts abstraction. I know formatting the HD is a horrific thought, but I never encounter this. I do encounter reading from files and maybe creating them maybe even deletion. When I do that, I don't want this implementation detail to propagate up my whole program. e.g., If I have a fetchUser(userName) method, I wish for it to return User, regardless if it is a constant function (e.g. for testing), a partially applied one with a list of Users, or one that reads from the DB or from the filesystem. I would be happy if the compiler warned me before reading from the DB, so I'd make sure I'm doing it right, but then get out of my way once I've made sure my code is sound.That's not entirely true. If you want to both side-effecting and not side-effecting functions uniformly, you could simply lift the latter to the former. However, I see a point (also what Martin said) that Monads are too "rigid", i. e. you have to propagate the use, but as long as there are no other formalisms which doesn't suffer from that "problem" (if you wish to call it so), `IO` is a reasonable solution.
Better to propagate up than down.
To see the difference, imagine that all IO methods in Java had
something like a @throws clause (let's call it @io), except that it
could not be caught. One can easily see that it propagates up. Now
consider a Function class -- how would the apply method be annotated?
If it doesn't have an @io annotation, it can call anything annotated
with @io. If it has @io, then everything calling it will have to be
annotated with @io. Consider what this would do to a collections
library... What is happening is that this tagging is propagating
*down* to methods that, otherwise, would not need to know anything
about it.
With an IO Monad, I can pass the whole monad to any method, and it
won't care less. So while it has to propagate "up" to the "main", it
does not need to propagate "down" to all methods.
Better to propagate up than down.
To see the difference, imagine that all IO methods in Java had
something like a @throws clause (let's call it @io), except that it
could not be caught. One can easily see that it propagates up. Now
consider a Function class -- how would the apply method be annotated?
If it doesn't have an @io annotation, it can call anything annotated
with @io. If it has @io, then everything calling it will have to be
annotated with @io. Consider what this would do to a collections
library... What is happening is that this tagging is propagating
*down* to methods that, otherwise, would not need to know anything
about it.
With an IO Monad, I can pass the whole monad to any method, and it
won't care less. So while it has to propagate "up" to the "main", it
does not need to propagate "down" to all methods.
See documentation: `mapM` is just `sequence . map`. `sequence` exists
precisely for that.
Let the Haskellites correct me if needed, but all I saw there was a
simple type mismatch.
"Abstract ideas are conceptual integrations which subsume an incalculable number of concretes—and without abstract ideas you would not be able to deal with concrete, particular, real-life problems. You would be in the position of a newborn infant, to whom every object is a unique, unprecedented phenomenon."
The fact that it's possible to defeat an effect system (for example, in the presence of unsafePerformIO) does not mean that it's not useful. Just like the type system remains useful even though it's possible to defeat that (e.g. in the presence of asInstanceOf).
The fact that it's possible to defeat an effect system (for example, in the presence of unsafePerformIO) does not mean that it's not useful. Just like the type system remains useful even though it's possible to defeat that (e.g. in the presence of asInstanceOf).
First, you gain modularity by separating concerns. In the case of the IO data type, you are separating the concern of what to do from how to do it and when to do it. When you say service.fetchData, you're simply constructing a value of the IO data type.
Secondly, you gain compositionality because your IO actions (which are just ordinary data) can be composed with other actions in a completely predictable way. And I don't mean that you can copy and paste. I mean that since your IO actions are just first-class values, they can be composed arbitrarily to form more complex programs. What's more, such composition can be understood by local reasoning alone. You don't need to have an understanding of your entire program in order to understand what happens when you compose one IO action with another.
To illustrate, this let's go back to the familiar List. A value of type List[IO[String]] can be turned into a value of type IO[List[String]] by composing all the actions in the list:
ios.foldRight(io(Nil))((a, b) => for { t <- b; h <- a } yield h :: t)
Note that we only needed to know how to compose two IO actions, and that was enough to compose the entire list.
This brings us back to modularity. There is nothing particularly "IO" about this code. So we can abstract that part out and re-use it for, not just IO, but all monads:
def sequence[M[_]:Monad, A](xs: List[M[A]]): M[List[A]] =
xs.foldRight(Nil.pure[M])((a, b) => for { t <- b; h <- a } yield h :: t)
Of course, you could argue that it's somehow "simpler" to reinvent this functionality, in different ways, all over your code. It's a prevalent fallacy that it is simpler to not abstract. But you are in possession of a language, Scala, that makes abstraction easy. Why not take advantage of it?
+1
The argument boils down to: It's very useful to have a way to compose
actions. But it's not specific to IO in any way. You can just as well
compose closures in a language without the IO monad. What's specific
about IO is that it marks the underlying action as having a particular
kind of effect.
I argue there are better, more polymorphic, ways to do this than monads
Cheers
-- Martin
Gents, isn't the STM monad a better fit for this discussion than the IO? There might be less questions as to it's intinsic value and more focus on the monadic constructs and alternatives.
Martin, you stated that possibility a few times already - I'm sure we're all really curious now about what you have in mind :)
Also, since monads don't compose etc, why hasn't the discussion mutate to Applicative?
* unlike other monads, it is hard for me to find what additional important information IO adds. Option[_] for example documents the fact that an equivalent of null may be returned. IO[_] documents that some side effect can happen, so what? The CPU may heat up and more IO instances take more memory, do we document these effects?
* say I have two programs, one touches some configuration file, deep inside its logic, another opens a socket to read some data, again, deep inside. Both return IO of something. Now what does this IO tell me? nothing. I can't tell what side effect they have made.
* I can compose functions that hide the side effect by just composing them
* I don't mind having IO to tag that a low-level function is doing side-effect. Just as long as I can escape it when I want to (like I can use catamorphism on Option or List). But then is it so useful?
I'd love to see a code example where the introduction of IO[_] makes more errors detectable. In the code example I used in the first message, I couldn't find where it helps.
Sure. But I think there will at some point be better ways to trackOn Mon, Oct 10, 2011 at 12:56 PM, Jesper Nordenberg <mega...@yahoo.com> wrote:
> martin odersky <martin.odersky <at> epfl.ch> writes:
>> So the only thing IO provides is that code *not* using it is certified
>> to be pure, whereas in Scala everything is in the IO monad.
>
> That's quite a huge thing, don't you agree?
effects than monads. Monads are too
rigid. Adding a single println somewhere means you have to rewrite
your whole program.
-- Martin
effects than monads. Monads are tooSure. But I think there will at some point be better ways to track
rigid. Adding a single println somewhere means you have to rewrite
your whole program.
Thank you for this excellent example.
All your points are valid, and very true. The question is not
whether we gain something by using the IO monad. It is whether we
gain enough. Since using it comes with its own price of trickier
code, and mental hoops to jump through, spending brain CPU on how
to deal with the monad, rather than the business problem.
Normally, one does not work with functions without knowing what they do. When I know what a function is supposed to do, I usually know what side effects it is doing, or, I don't care because they are an implementation detail. If they were not, then all I know is that something is going on under there, but can't do anything about it.
Ittay
Thank you for this excellent example.
All your points are valid, and very true. The question is not whether we gain something by using the IO monad. It is whether we gain enough. Since using it comes with its own price of trickier code, and mental hoops to jump through, spending brain CPU on how to deal with the monad, rather than the business problem.
Normally, one does not work with functions without knowing what they do. When I know what a function is supposed to do, I usually know what side effects it is doing, or, I don't care because they are an implementation detail. If they were not, then all I know is that something is going on under there, but can't do anything about it.
We are using an IO-like monad to isolate database effects where I work. We're also using a separate State-like monad to compose computations that have state (instead of using mutable state). We find this to be a huge benefit. Especially when we can re-use the same code for both things that seemingly have nothing to do with each other. The code that is IO-specific is very short and confined to one or two files.
In practice, once you have factored out this concern, almost all of your code is pure. I.e. You don't see "IO" in very many places. It's not at all true that most code has to have side-effects.
The argument I'm hearing is that IO is somehow special. A "cross-cutting concern". Bullshit. It's just another DSL.
I'm kind of done with "oh, but don't you want to convince me?!" No, I really don't. The best you can do is try it both ways, know what you're talking about first hand, and then decide for yourself. There's no magic in monads. It's just separation of concerns and combination of components according to a simple interface.
Thanks for the examples, but...you didn't actually do anything IO-specific.
You made more or less the same point three times, and independently of those points decorated some types with IO.
In particular, you have not given an example that shows why it is a bad idea to inject new state with IO but deal with that state functionally thereafter, or to output existing state, treated functionally prior to that point, with IO. Just leave off the IO marker and all your points remain; you can do all the same things with unwrapped and/or Option-wrapped types (depending on whether or not the result could fail to exist).
The minimal example where the IO monad is actually better than the _most sensible alternative_ (not a mythically messy imperative situation) seems to me to require an awfully complicated setup, if it even exists at all. (I suspect based on the logic of the arguments that it does exist, but I have yet to see an example that would not have been simpler without the IO monad specifically.)
-- Martin
--
Martin Odersky
Prof., EPFL and Chairman, Typesafe
PSED, 1015 Lausanne, Switzerland
Tel. EPFL: +41 21 693 6863
Tel. Typesafe: +41 21 691 4967
Start with "The Marriage of effects and monads". Then, add
polymorphism to the effect system. This is still too complicated for
my taste, so we have to find simpler solutions. It's research, which
comes before published papers.
-- Martin
Well, that's true with anything, that you have to decide for yourself if it's worth the trouble to understand. Before you can say "this is beneficial" you have to ask "beneficial for whom and for what purpose?" We are using an IO-like monad to isolate database effects where I work. We're also using a separate State-like monad to compose computations that have state (instead of using mutable state). We find this to be a huge benefit. Especially when we can re-use the same code for both things that seemingly have nothing to do with each other. The code that is IO-specific is very short and confined to one or two files.
Interesting. Maybe I missed something. I thought that when you use IO you need to propagate it up to 'main' (or 'run'). So one function that does IO (a println) pollutes your whole program. Where am I wrong?
On Oct 11, 2011 7:56 AM, "Ittay Dror" <ittay...@gmail.com> wrote:
>
>
>
> Runar Bjarnason wrote:
>>
>> Well, that's true with anything, that you have to decide for yourself if it's worth the trouble to understand. Before you can say "this is beneficial" you have to ask "beneficial for whom and for what purpose?"
>>
>> We are using an IO-like monad to isolate database effects where I work. We're also using a separate State-like monad to compose computations that have state (instead of using mutable state). We find this to be a huge benefit. Especially when we can re-use the same code for both things that seemingly have nothing to do with each other. The code that is IO-specific is very short and confined to one or two files.
>
> Interesting. Maybe I missed something. I thought that when you use IO you need to propagate it up to 'main' (or 'run'). So one function that does IO (a println) pollutes your whole program. Where am I wrong?
As someone who hasn't gotten much further then LYAH, I'll see if I can explain (as much for my benefit as anything else):
Just like the fact that you can use the value stored within an Option using map or flatMap, the value within IO can be passed to pure functions as well.
If you consider the previous example of adding 2 numbers that are retrieved by some IO, you do not need '+' to be aware of the source of these numbers. Ideally you would keep the IO type as close to your 'main' method as possible, using pure functions to actually do something with the results of the IO.
This results in a program where you naturally tend to separate your IO from the functions that process that data, hopefully leaving the majority of your program IO free.
--
Derek
On Tue, Oct 11, 2011 at 9:55 AM, Ittay Dror <ittay...@gmail.com> wrote:Right there.
Interesting. Maybe I missed something. I thought that when you use IO you need to propagate it up to 'main' (or 'run'). So one function that does IO (a println) pollutes your whole program. Where am I wrong?
In practise, you compose effectful things in two ways:
for {
x <- effectsHere
y <- effectsHereToo(x)
} yield noEffectsHere(y)
Because someone has to generate the applicative. Once you have 2 functions that takes normal arguments and produce an Applicative, that applicative needs to be a monad for the functions to compose. AFAIU, monads don't compose in that M[_] and N[_] cannot be composed to M[N[_]], which is why all side effects (reading a file, opening a socket, querying a DB) all use IO and not a more specific monad (like FileIO, SocketAction etc.), if they did, and you needed to use to such functions with different monads you'd need to compose them, but you would not be able to
Lets take a more concrete example. I have a function fetchUser: String => IO[User]. What you're suggesting is that this method needs to be used at 'main', to get the User instance and pass it to the rest of my application ('noEffectsHere'), right? So I do all my IO upfront, and then call the rest of my code?
doing all sideeffect-stuff before or after all pure stuff is not always possible, and most likely very ugly for complex operations. i tend to just ignore side effects that "shouldn't matter much"
> -------- Original-Nachricht --------
> Datum: Tue, 11 Oct 2011 16:20:42 +0200
> Von: Ittay Dror <ittay...@gmail.com>
> An: Runar Bjarnason <runar...@gmail.com>
> CC: scala-...@googlegroups.com
> Betreff: Re: [scala-debate] Re: questioning FP
You can just as well
compose closures in a language without the IO monad. What's specific
about IO is that it marks the underlying action as having a particular
kind of effect.
On Oct 11, 2011 7:56 AM, "Ittay Dror" <ittay...@gmail.com> wrote:
>
>
>
> Runar Bjarnason wrote:
>>
>> Well, that's true with anything, that you have to decide for yourself if it's worth the trouble to understand. Before you can say "this is beneficial" you have to ask "beneficial for whom and for what purpose?"
>>
>> We are using an IO-like monad to isolate database effects where I work. We're also using a separate State-like monad to compose computations that have state (instead of using mutable state). We find this to be a huge benefit. Especially when we can re-use the same code for both things that seemingly have nothing to do with each other. The code that is IO-specific is very short and confined to one or two files.
>
> Interesting. Maybe I missed something. I thought that when you use IO you need to propagate it up to 'main' (or 'run'). So one function that does IO (a println) pollutes your whole program. Where am I wrong?As someone who hasn't gotten much further then LYAH, I'll see if I can explain (as much for my benefit as anything else):
Just like the fact that you can use the value stored within an Option using map or flatMap, the value within IO can be passed to pure functions as well.
The difference is that for Option I have a catamorphism. So I can
always "get out" of using Option. With IO, once I use it, for no
matter how small an issue, I need to use it in all the call
hierarchy and change everything to work through for comprehension.
If you consider the previous example of adding 2 numbers that are retrieved by some IO, you do not need '+' to be aware of the source of these numbers. Ideally you would keep the IO type as close to your 'main' method as possible, using pure functions to actually do something with the results of the IO.
This results in a program where you naturally tend to separate your IO from the functions that process that data, hopefully leaving the majority of your program IO free.
A sane developer will do that anyway (a bad developer will muck any codebase).
Returning to my original question, using Options for example (and
monads in general) is easy to explain: you have an alternative,
more semantically true, to returning null, so your code becomes
safer (a function that returns nothing is clearly documented with
the type), and remains clean since the use of Option is usually
local (at some point you have a default value, and use getOrElse
to reduce the Option to a value). With IO, it is trickier to
explain:
First, because IO is not necessarily dangerous (unlike trying to invoke a method on null).
Second, because the IO monad does not prevent me from doing a dangerous action, just wraps it (if I try to read a non existent file, my app will fail, with or without IO[_]).
Third, it is pervasive (no catamorphism)
So the question is, what clear gain can I show?
Sure, then fetchUser is mostly non-IO, but needs to return IO[User]. Similarly, the functions that use fetchUser will also need to return IO[Something]. So a small implementation detail in a function can change its signature and forces it to be used differently (inside a for comprehension).
-- Martin
-- Martin
object B {def run(f:String,b:String):Int =for {a <- A.foo(f)b <- A.bar(b)} yield a+b}Is the exact same program as:object B {def run(f:String,b:String):Int =for {b <- A.bar(b)a <- A.foo(f)} yield a+b}Because in the case of a purely functional language I know that I can always commute evaluation order. Further, the computer knows this too! Substitute in the IO monad for the Option and the guarantees about commutativity go out the window.
Granted that using functions in an IO context like above where I didn't take steps to eliminate the possibility of a bug by forcing the correct order (say through the use of a Reader or State monad) is possible.
The minimal example where the IO monad is actually better than the _most sensible alternative_ (not a mythically messy imperative situation) seems to me to require an awfully complicated setup, if it even exists at all. (I suspect based on the logic of the arguments that it does exist, but I have yet to see an example that would not have been simpler without the IO monad specifically.)
I'm not aware of a "sensible alternative", I'd be happy to hear of one.
Second, because the IO monad does not prevent me from doing a dangerous action, just wraps it (if I try to read a non existent file, my app will fail, with or without IO[_]).
On Tue, Oct 11, 2011 at 11:36 AM, Ittay Dror <ittay...@gmail.com> wrote:And if I take the head of a List, my app will fail too. If I divide an Int by zero, my app will fail. This doesn't mean List and Int are useless types.Second, because the IO monad does not prevent me from doing a dangerous action, just wraps it (if I try to read a non existent file, my app will fail, with or without IO[_]).
That having been said, I am working on getting regional IO into Scalaz's IO monad. This will guarantee, in the type system, that you can never read, write, or close a file that isn't open. Having kind polymorphism in Scala would make this much easier than it is (Martin, Adriaan, et al, take note).
I thought that similar to how Option guards us from nulls, IO guards us from side-effects (Int is not supposed to guard us from division.). If it doesn't, then again, what is the point? Just to tag a function as doing IO? And then tag all its callers? And make their code a bit awkward (for comprehension)? Isn't this like checked exceptions?
As someone who hasn't gotten much further then LYAH, I'll see if I can explain (as much for my benefit as anything else):
Just like the fact that you can use the value stored within an Option using map or flatMap, the value within IO can be passed to pure functions as well.
The difference is that for Option I have a catamorphism. So I can always "get out" of using Option. With IO, once I use it, for no matter how small an issue, I need to use it in all the call hierarchy and change everything to work through for comprehension.
If you consider the previous example of adding 2 numbers that are retrieved by some IO, you do not need '+' to be aware of the source of these numbers. Ideally you would keep the IO type as close to your 'main' method as possible, using pure functions to actually do something with the results of the IO.
This results in a program where you naturally tend to separate your IO from the functions that process that data, hopefully leaving the majority of your program IO free.
A sane developer will do that anyway (a bad developer will muck any codebase).
Returning to my original question, using Options for example (and monads in general) is easy to explain: you have an alternative, more semantically true, to returning null, so your code becomes safer (a function that returns nothing is clearly documented with the type), and remains clean since the use of Option is usually local (at some point you have a default value, and use getOrElse to reduce the Option to a value). With IO, it is trickier to explain:
First, because IO is not necessarily dangerous (unlike trying to invoke a method on null).
Second, because the IO monad does not prevent me from doing a dangerous action, just wraps it (if I try to read a non existent file, my app will fail, with or without IO[_]).
Third, it is pervasive (no catamorphism)
So the question is, what clear gain can I show?
Cool - It is the first time someone points out that this is like using null or exceptions… BUT, isn’t the point that this kind of IO[] wrapping creates the same kind of headaches that a checked exception does?
Cool - It is the first time someone points out that this is like using null or exceptions… BUT, isn’t the point that this kind of IO[] wrapping creates the same kind of headaches that a checked exception does?
Cool - It is the first time someone points out that this is like using null or exceptions… BUT, isn’t the point that this kind of IO[] wrapping creates the same kind of headaches that a checked exception does?
Very good observation. That's why you want polymorphism.
-- Martin
Well, that's true with anything, that you have to decide for yourself if it's worth the trouble to understand. Before you can say "this is beneficial" you have to ask "beneficial for whom and for what purpose?"
We are using an IO-like monad to isolate database effects where I work. We're also using a separate State-like monad to compose computations that have state (instead of using mutable state). We find this to be a huge benefit. Especially when we can re-use the same code for both things that seemingly have nothing to do with each other. The code that is IO-specific is very short and confined to one or two files.
Can you give an example of the polymorphism you want that the IO monad
cannot provide?
/Jesper Nordenberg
should be effectful if f is.
-- Martin
xs map f
should be effectful if f is.
I'm not sure I agree with that. If f doesn't have side effects the map
operation can for example be performed in parallel. If f has side
effects the map operation must be performed sequentially.
So, the lack of side effects gives you less implementation restrictions.
/Jesper Nordenberg
I believe on parallel collections map might require its argument to be
side-effect free. But the only practical solution I see for the
foreseeable future would be to do that as part of an optional type
system. On sequential collections map should certainly be polymorphic.
Cheers
-- Martin