As a Haskell thinker Maybe/Either/->/List/State (as in pure Haskell State)/ST/etc can all be effects in addition to IO. All of those effects are pure in the sense that you can "run" them w/o resulting in an IO. I get the sense from when Richard talks about effects as meaning specifically Haskell IO. Does the Roc community use the term side-effect differently than effect? (I use them interchangeably)
From a Haskell perspective I would say that Roc basically has a fixed monad stack that has Try and State (maybe others?), and each -> function has a built-in do for that monad stack to some degree. The Try monadic values are first class, but the state ones aren't (I think?). Making a function => adds a platform-limited version of IO to the monad stack. I'm speaking a bit loosely here, but that's the feeling I get working in Roc with a Haskell perspective.
I just mean "not pure"
hrm, what do you mean by "pure"? Is Try pure?
is pure a synonym for referentially transparent?
if I write a platform function that implements plus on floats I think it's still !, but is it not an effect?
yeah, sorry - pure as in "pure function" as in:
so Try is just data (same as Either in Haskell, except with the parameters in the opposite order)
to me Try is both data and an effect
hm I don't think it's reasonable to call it an effect
some languages with algebraic effects use effects instead of it
but effects definitely require execution, and Try is just memory
nothing gets executed
in Haskell all functions are pure, even if they return IO, the IO is a value that only has an effect when you return it from main, so even functions in IO are referentially transparent themselves
right
Richard Feldman said:
hm I don't think it's reasonable to call it an effect
Haskell programmers would pretty universally disagree
that's not true of Roc functions though
Do you understand why a Haskell programmer would say Try is an effect and disagree?
I know a lot of Haskell programmers, and I've written Haskell professionally, and this conversation is the first time I've ever heard anyone say Either is an effect :sweat_smile:
or agree it's an effect in Haskell, but isn't in Roc?
well it's named Either in Haskell
and I don't know why anyone would say Either is an effect
like it's a monad obviously
but monads and effects are of course different
yeah it models the effect of exceptions, just as in Roc, but Roc has language-level support for making them convenient, in Haskell you use do, just like you would when dealing w/ IO effects
Richard Feldman said:
but monads and effects are of course different
I don't agree w/ that
and I think most Haskell programmers wouldn't, or at least would understand why I disagree
I have certainly heard people say "monads represent effects" - which is totally reasonable
in that it's a value that represents an effect
not every instance of list is used to model effects, but you use lists to model the non-determinism effect
I dunno, I think we may be at an impasse here haha - I understand what you're saying, I just disagree both that "monads are effects" is reasonable and also separately that most Haskellers would agree that "monads are effects" (as opposed to e.g. "monads can represent effects")
anyway, to the original point - when I'm talking about effects in Roc I specifically mean "functions that are not pure functions"
I think on some level we're just arguing terms which isn't that constructive, but among the most beautiful things I've learned form Haskell is understanding the whole first class modeling of effects in general including IO
IO is basically the state monad, but with the entire universe
in other words, functions whose names end in ! and whose function types use => instead of -> before their return values
do you consider the State monad pure?
of course
all monads are pure haha
but IO isn't?
IO is pure too
a function in IO?
like is the putstrln Haskell function pure?
that's the whole point! before Wadler came up with IO, Haskell literally had to do I/O by having you build an entire program that took as input the output of the previous I/O operation and it was apparently miserable
and the whole reason they had to come up with a way to represent I/O using only pure functions was that nonstrict evaluation only works if all computations are pure
which is why unsafePerformIO is not something that's supposed to be used
if you consider a => Roc function to be an implicit do over a monad stack including IO is Roc's verison of putstrln different? is it impure?
or do you see it as fundamentally different than an implicit do over IO?
so, a previous version of Roc used Haskell's approach - all Roc functions were pure, and you could call any Roc function and be absolutely certain that no side effects were happening
one way this is obvious: if you call putStrLn 1,000 times, what happens?
in Haskell if you call it 1,000 times, nothing observable happens. You get some heap allocations and your CPU heats up a bit.
you have to actually hand the IO off to something else (e.g. main, or something that will ultimately hand it off to main) before you actually see anything in stdout
in Roc, if you call an equivalent put_str_ln! 1,000 times, you most definitely get 1,000 stdout printings
hrm, I'm failing to make a connection here, yeah totally on the same page that if IO is first class then holding an IO value is not a side effect, but a function returning IO in a do in main now produces side effects
so the IO value is a first class side effect aka effect
right, but main is what's doing that (behind the scenes, traversing the inert IO value and maybe calling pure function continuations inside it), not the function
in the same way I'd say Maybe a value is a first class effect
if you fill in a default and get rid of the maybe you've now run the effect
the distinction matters because the entire point of having the term "side effect" is to describe what happens when you call a function
this is why Haskell names things in little DSL monads run
if you call the function and the I/O happens, then a side effect happened
if you call the function and no I/O happens, then that side effect didn't happen
describing that precise distinction is the entire point of the term!
so collapsing them to say they are equivalent is to miss the point of the term imo
yes, so a function returning Maybe can have the effect of failing, if you use it in a do that failure automatically propagates like Rust/Roc's ?
Jake Brownson said:
or do you see it as fundamentally different than an implicit
doover IO?
I think you're saying you do see it as fundamentally different
yeah for sure!
This is where we part I guess, I don't, I don't see the IO effect as fundamentally different than Maybe/Try, all effects
I even see a C++ function as an implicit do, my brain thinks in Haskell
one reason I don't think it's useful to see Maybe and Either as effects is that I think it reduces to "everything is an effect, therefore the term is useless"
like Maybe is just two alternatives, Just and Nothing, and Just holds a payload
same with Either, except it holds 2 payloads
no, effects ~= monad, so like Num is not an effect
same with Bool except it holds 0 payloads
so is Bool an effect?
why not? it can represent failure
some effects can be applicatives, but all applicatives are monads
for example in my puri I have a Canvas type, basically a free "monad" that defines like "draw circle", etc. but since none of the effects I'm modeling are monadic it can just be applicative
Bool is not a monad
why is Bool not an effect to you?
because it's not a -> a
Maybe is a monad
so your definition of effect is just another word for monad? :thinking:
I would use those terms interchangably, it's possible there is some nuance I'm not aware of
so since there's an identity monad, there's an "identity effect" that's the same thing?
and technically effect is a value of a monad, not the monad itself
to me, "identity effect" is an oxymoron :sweat_smile:
that's an interesting question, identity things often feel pretty cheap, but the identity monad is useful if you have a thing that is parametric on monads, and you don't need anything monadic
I could stipulate that it isn't and I don't think it changes my overall conversation here
Is State () an effect?
() as in unit
again, to me values are never effects haha
effects are performed by the CPU, they're not bytes in memory
an IO a is a value that represents an effect in the way 2::uint is a value that represents 2, I'd say 2::uint is a number and IO a is an effect
so I would say "writing to standard out" is an effect
but an IO value that represents writing to standard out is not an effect, it's a value
pure functions are also computed by the CPU
yeah it's necessary but not sufficient
I'm not saying all CPU activity is effects, but rather that all effects involve CPU execution
that is fairly totalogical
What I'm saying is a pretty standard Haskell perspective I think, I think I have the answer to my question, Roc just doesn't see things that way
Not necessarily as an intentional deviation from Haskell
I think this is at the core of why I'm not jumping full force onto Roc
Richard Feldman said:
I know a lot of Haskell programmers, and I've written Haskell professionally, and this conversation is the first time I've ever heard anyone say
Eitheris an effect :sweat_smile:
https://chatgpt.com/share/6a7b737d-43b4-83e8-a70c-2c6de0646bfc
It took me like 4 years of trying to learn Haskell to get to appreciating the category theory stuff
for those first years I wouldn't have described Maybe a as an effect
Also all that said, I think it would be a reasonable design decision to say "we're just not doing first class effects", possibly for perf reasons, but also just to avoid the whole Scala-style drama and keeping things accessible. I've actually wondered what it would look like to specifically design a language that is Haskell w/ an implicit do basically, but w/ more flexibility in the monad stack than how Roc is doing it
a lot of smart people have been working on effect modeling and all of them seem unsatisfied when you get to scale for reasons I don't understand despite having done some reading on it, seems like a hard problem
Richard Feldman said:
so I would say "writing to standard out" is an effect
but an
IOvalue that represents writing to standard out is not an effect, it's a value
is 2::uint a number?
making something first class means making them values, if effects are first class as in Haskell then they are values I'd say
regarding how Haskellers view this, I just rewatched this clip of SPJ where he uses a bunch of effects terminology and frankly I think he across the board agrees with my view:
by effects I mean "side effects"
when you call a method ... the sole reason for calling it is to have an effect on the world. It is fundamentally effectful.
In the end, a program with no effects - there's no point to it, is there? ... The reason to run a program is to have an effect.
We figured out how to combine effectful computations and effect-free ones without polluting each other.
I don't think there's a reasonable way to interpret SPJ saying "the reason to run a program is to have an effect" as him thinking about Maybe and List when he's saying "effect" - the point he's making is that interacting with the outside world (which is how he and I are using the term "effects") is where programs become fundamentally more useful than calculators.
and then when he says "after many years of embarrassment, we figured out how to combine effectful computations and effect-free ones" in Haskell - again, they had Maybe and List and other monads long before Wadler figured out monadic I/O. I don't think the reason he's talking like this is that he's ignorant of the history of Haskell, since he participated in all of it. :smile:
a Java programmer in the 90s wouldn't have thought of a function as a value
Richard Feldman said:
I don't think there's a reasonable way to interpret SPJ saying...
link the clip?
I don't find that phrasing inconsistent w/ what I'm saying, in Haskell we cause external IO effects by modeling them as first class values and then returning them in main
Jake Brownson said:
What I'm saying is a pretty standard Haskell perspective I think, I think I have the answer to my question, Roc just doesn't see things that way
again I think we'll have to agree to disagree on whether it's a standard Haskell perspective :sweat_smile:
oh sorry - the links in the quoted sections are links to parts of the clip
ahh duh
Did you see my ChatGPT link? Not that it's god, and possible it's like influenced by my chat history, but it agrees that Haskellers would describe a Maybe as an effect, interestingly it separates "side effect" which I could agree w/, side effect is a loose term I think
he also mentions "any effects, anywhere" as being in the "unsafe" part of the graph - so again, if Maybe and List are effects, then it's really hard to understand why he would possibly be thinking "any effects, anywhere, such as Maybe and List, is unsafe, and C# is an example of this unsafety because it has monads like List, whereas Haskell is on the other side of this axis because it...does not have Maybe and List"
For people (like me) who are new to functional programming, Richard's definition of purity is easy to grasp, but I humbly don't understand Jake's. Could you please explain it like I'm 5? Also, what are the practical consequences, is it mainly agreeing on vocabulary?
Jake Brownson said:
Did you see my ChatGPT link? Not that it's god, and possible it's like influenced by my chat history, but it agrees that Haskellers would describe a Maybe as an effect, interestingly it separates "side effect" which I could agree w/, side effect is a loose term I think
I just looked at it and it seems to agree with me: :smile:
More precisely, a careful Haskeller might say:
Maybeis a monad that models the failure effect.rather than:
Maybeis an effect.
that's exactly the distinction I'm drawing!
Richard Feldman said:
regarding how Haskellers view this, I just rewatched this clip of SPJ where he uses a bunch of effects terminology and frankly I think he across the board agrees with my view:
by effects I mean "side effects"
when you call a method ... the sole reason for
Jake: calling it is to have an effect on the world. It is fundamentally effectful.
he's talking about methods in C# here right?In the end, a program with no effects - there's no point to it, is there? ... The reason to run a program is to have an effect.
Jake: yes, as it sounds like you're aware, Haskell decided to be non-strict which required being pure, and they didn't have a good answer for how to do effects for a long time until they made them first class and composed them monadically, Haskell has effects, they're first class.
We figured out how to combine effectful computations and effect-free ones without polluting each other.
Jake: Yes, we combine them by modeling effects as monadic IO values.
I'm not sure how the quoting is going to turn out on this, I'll edit it if it's strange
ugh I don't know how to fix the quoting
he's talking about C# yeah
right, but the distinction here is "monads model effects" (or, equivalently to me, "monads describe effects") versus "monads are effects"
it's like the difference between writing down a list of 5 things to do vs. actually doing those 5 things
doing them is the effect, the list just models them
yeah, there are kind of two layer to Haskell, there's like the Lisp with types layer, then there's like the category theory layer, this way of thinking is the category theory layer, it's possible to use Haskell w/o thinking this way, but I find it to be very beauitful and useful to bring back to other languages even if they can't express it directly. It's why there's the trope of everyone finally understanding Monad and wanting to write a Monad tutorial.
Aurélien Geron said:
For people (like me) who are new to functional programming, Richard's definition of purity is easy to grasp, but I humbly don't understand Jake's. Could you please explain it like I'm 5? Also, what are the practical consequences, is it mainly agreeing on vocabulary?
It's not really easy to concisely address your question, I've been attempting to at least provide some contradictions here that can open a crack to the way of thinking, but it's the kind of thing you need to like build new brain circuits to really grok. There is a trope in Haskell that there are too many Monad tutorials because when someone finally has the epiphany they see how simple and beautiful it is and want to share it w/ the world. It's hard not because it's complex but because it's abstract.
It goes deeper than agreeing on vocab though, it's about seeing a larger abstraction that unifies "side" effects, non-determinism, exceptions, and more. In most languages you see those things as distinct and features of the langauge, but in Haskell the Monad abstraction unifies them and more in a beautiful way.
Richard Feldman said:
right, but the distinction here is "monads model effects" (or, equivalently to me, "monads describe effects") versus "monads are effects"
Anyway, we're kind of looping here, but I do promise you what I'm sharing is a perspective that you get in Haskell once you really grok Monad. Gaining this perspective felt the same as gaining the perspective that functions can be first class which I got from Clojure.
I hope I'm not coming off as condescending or anything, obviously you're super well read and Roc is super impressive, just trying to wrap my head around the part of it that I'm not resonating w/
understood! :smile:
I'm not saying you should change how you're thinking about those things, I just disagree with the factual claim about most Hakellers using "effect" and "monad" interchangeably
but regardless, I suppose it's not that big of a deal as long as we're clear about how we're using it here :smile:
I may underestimate the folks that use Haskell as a typed Lisp, so it may be factually untrue that over 50% think that way, but it's very much a Haskell way of thinking
I do think it would be a super beneficial perspective to understand when building something like Roc, I'm not sure you would want to go full HKT and do, but it might help inform the platform design
yeah that was actually the original design! :smiley:
I would want you to go full HKT and do, but I think it is a design tradeoff, probably for perf, that seems to be an area of active research still, but definitely from which audience you attract
Task (aka IO) was a data structure you'd pass to the host, which would then interpret it
but we ultimately moved away from it for various reasons
there will still be a form of that when we get to concurrency though
I'm still curious if 2::uint is a number ;)
er sorry, what syntax is that?
I mean the number 2 is a number haha
pseudosyntax
then sure haha
but Task isn't an effect?
wasn't*
right
to me, only functions can be effects
or rather
only calling functions can produce effects
(hence "effectful function" and not just "effect")
Richard Feldman said:
right
okay, I think that's the core of the disagreement
assuming Task was very similar to IO
yeah it's basically IO (Either _ _)
so it has 2 type parameters, one for success and one for failure
baked in
anyway, thanks for the convo, seems like an agree to disagree thing :)
fair enough!
I switched my focus back to my main project in Rust, but I keep thinking about Roc
I'd probably be switching my project to it except for the stability and awkwardness of the effects/platform stuff, which I don't mean to say you should change it for me, just a data point, but I will def stick around to keep an eye on it and play here and there
I love the community
and it blows me away how quickly bugs get fixed
Btw, I'm totally behind the idea of not allowing unsafeIO and having the guarantees behind exposing only certain external effects, what bugs me w/ platforms is mostly the lack of composability, and the awkwardness around how you can't write package libraries against types in a platform for example. I suspect they would feel less awkward w/ more mature platforms that really do include an API for _everything_ you might want on a CLI/desktop app.
Huh, I suppose there's nothing stopping someone from doing an IO platform that just has a single "do this IO value" like Task used to be? Are platforms a generalization of Task?
I would be curious to know more about why the departure from Task, was perf a big concern? Is it documented somewhere?
Setting aside debate on if monads are effects, would Roc be open to a do construct that works for types with the equivalent of bind and return (maybe call them something less mathy like and then and wrap)? I don't think this would require HKT. Maybe on or with would be a better keyword than do. I can mock up a pseudocode example if it's not clear what I mean.
hrm, maybe it would require HKT...
I would also question whether the mainstream Haskell perspective is that Maybe is an effect... I think most Haskellers would agree that an effect is something observable that happens (e.g. printing to the console), not that an effect == monad. It's all splitting hairs/terms, but that's my view. Note: I've only done Haskell professionally for two years.
Interesting, to me that's the most important insight of Haskell, using it w/o that idea is just using a typed Lisp, which is also neat of course
Jake Brownson said:
would Roc be open to a
doconstruct that works for types with the equivalent ofbindandreturn(maybe call them something less mathy likeand thenandwrap)? I don't think this would require HKT. Maybeonorwithwould be a better keyword thando. I can mock up a pseudocode example if it's not clear what I mean.
we used to have it, but we took that out too :smile:
it was called "backpassing" - you can find discussions of it on Zulip.
hah okay, I take that as a "no"
Jake Brownson said:
Interesting, to me that's the most important insight of Haskell, using it w/o that idea is just using a typed Lisp, which is also neat of course
I may just never have reached that insight, that's entirely plausible! I think you need a lot of time with a tool to really understand it, and perhaps I didn't have enough yet.
If I look at it from the outside, I think "effect is something observable that happens" makes a lot of sense, both for Roc programmers and for people coming from the outside (whether they're JavaScript developers or Haskellers). Hopefully it's something that can appeal to a lot of people, because it's such a clear definition!
yeah, first class effects is not a super accessible concept in the same way lambdas used to be for like Java programmers, but it's neat that lambdas are now basically mainstream, maybe first class effects are next
probably not, we'll hit the singularity first haha
I searched "backpassing" in Zulip and yeah that's exactly what I was picturing. It would def be hard to explain to folks that aren't coming from Haskell, so it does seem like a reasonable choice not to include it.
I actually do think Richard and my disagreement may come down to just definitional stubbornness one one of our parts. He obviously completely understands modeling what I would call effects monadically, but just objects to calling the values effects.
I don't see calling a value of type IO () an effect, or even in this case side-effect, as any different than a value of type Num a number. I would agree on everything but IO if we replace Richards use of "effect" with "side-effect". i.e. I would not call Just 42 a side-effect, but I would call it an effect. It's more obvious that Nothing is an effect (a thrown exception), but succeeding is just as effectful as failing to me.
Hah okay I'm going to go work on something else for real this time.
Here is a convo I had w/ ChatGPT analyzing this thread, just as evidence I'm not completely crazy ;): https://chatgpt.com/share/6a7b964e-72b4-83e8-a45b-442534e29572
ChatGPT is quite helpful there, thanks for sharing
yeah! This part in particular is the distinction I was trying to get across:
![]()
I thought you'd like that part, but you'll see down later the arguments i'm presenting here convince it
I do like its distinction of computational effects vs side effects, though the boundary is fuzzy if you think about it, like IO being a State monad over the universe.
but if you draw a line somewhere, then effects outside that line are side effects
Haskell/Roc have reasonably clear lines for that
I hadn't really thought about a distinction between effect and side-effect before, but that does seem like a useful distinction, and might bring our perspectives together
though I would still call the value putsrtln "Hello" a side effect _and_ an effect
even if you never pass it through main
in the same way I call the value 2 a number and a lambda a function
they're all first-class values in Haskell
Maybe there is a distinct disagreement for non-IO monads, I think of a monad as a little programming language with effects available particular to the specific monad, so in the Maybe monad you have all the normal pure Haskell things, but you also have “null”, I.e. your functions can be partial, aka they can fail. Functions that return Maybe are written in that monad.
You could go the other way of course and say that 2 as a uint is not a number, it’s a bunch of voltages in a register or something, but that convo requires a hot tub
image_4CE9D086-9FA3-4E16-9338-8BBB5A870FBC_1786487729.jpeg
I don't really want to heat this discussion or anything. I feel like from a practical standpoint it is quite pointless by now. I feel like we understand each other's points but just disagree
However from a philosophical standpoint I find it quite interesting. As someone with no industrial, only academical experience with Haskell, I've never met anyone who said monad equals effect. I've written a thesis about type inference and using a custom monad type to build constraints and the AST. I'd not call it an effect. I'd call the handler or whatever unwraps the monad actually doing the effect. Like in case of Either that might be unwrapping both cases. But in case of IO, that happens by the Haskell runtime even after main is constructed, when the actual console logs/http calls/file writes/... are happening
From the abstract of Wadler's Monads for Functional Programming:
Monads provide a convenient framework for simulating effects found in other languages, such as global state, exception handling, output, or non-determinism.
global state -> state monad
exception handling -> Maybe/Either aka Try
output -> Writer
non-determinism -> list
Here's a more contemporary source from a Haskell keynote: https://youtu.be/m821Vz8N_bo?si=gxDbuRMXNIKAqohR&t=1616
Last updated: Aug 12 2026 at 12:35 UTC