I’m far from convinced that I really understand this the way I should.
If so, please kindly direct me the right way.
For a while, I’ve internally thought of the Effect monad as being a sort of black-box version of the State Monad. Instead of threading around and updating some state, you pass around an opaque token representing the world (“magically” given to you at the entry point of a program).
Any time it’s even remotely possible that the outside world has changed this is represented as a new equally opaque token, with the catch being that using an “outdated” token is forbidden or undefined or some-such. This models each new token as a sort of general discreet undefined step in computation/time/changing outside world. Hens using monads to model an order of evaluation.
Looking at the implementation now though, it looks like the effect monad doesn’t really carry that extra baggage. Ie. What I described above isn’t what’s actually happening in the program. It looks like the effect monad is really just an annotation? It’s computation strategy/programmable semi-colon is similar to the identity monad, but by the name we understand that some impure shenanigans is possible.
Am I reading this properly?
data Effect :: Type -> Type
main :: Effect Unit
Here, main returns a function that, when run (what’s the input type? Doesn’t matter?) produces unit. I don’t see bind doing anything with the input. So a Purescript program creates a single function that ultimately ignores it’s input and produces nothing. The effect type basically annotates to us that what it has actually done is (maybe) create some side effect(s).
I think this intuition is not actually very helpful in most cases. I’d recommend that you think of it only as an implementation detail which is specific to GHC Haskell. Because PureScript is strict, the strategy you described is unneeded - we can instead represent values of the type Effect a as JS functions which take no arguments, and when called, perform effects and then return a value of the type a. So for example, if I were to write a function in JS like
function greet() {
console.log("hello, world!");
return 1;
}
then it would be acceptable to give that function the type Effect Int in PureScript.
means that if you have some type T :: Type, then you can write Effect T, and the type Effect T also has the kind Type. This tells the compiler that we can have values with types such as Effect Unit, Effect Int, or Effect (Array Int), but Effect Array and Effect Record don’t make sense and shouldn’t be allowed.
This is super pedantic, but representationally, our Effect thunk is equivalent to GHC’s token passing, if the token is zero-sized, and returned as part of an unboxed tuple. This is exactly what GHC does, AFAIK.
That would allow people to unwrap the newtype and run the inner function directly, performing effects arbitrarily.
That it’s a function is kinda just an implementation detail - there’s not really a need to expose that to people, it’s only strictly required information if you’re writing FFI code or something like that.
Interesting, I hadn’t considered a case where Effect would have a different runtime representation than a function. I do follow, at least, that Effect being a function is actually just an implementation detail. That makes sense to me.
So conceptually, an Effect and a Purescript function are very different things, right? Because that difference doesn’t exist for JS, it only exists in the type system. Another language, however, might have first-class support for mappings between sets (pure functions) as a different thing than a procedure/method. In that case, you couldn’t represent Effects as a newtype wrapper around PS functions since PS functions would have a pure runtime representation (compilation target wouldn’t support that).