Hello everyone.
I was curious why Effect a
is represented by Unit -> a
under the hood. Why this level of indirection is needed?
If Effect a
would by just a
then pureE
could be id
and bindE f a
could be f(a)
.
The same for ST a
which is built the same way, accurate to types, under the hood.
Because then:
a :: Effect Unit
a = log "hello"
would print to the console right as the window gets loaded
Welcome @thesame!
Effects in PureScript are delayed such that defining an Effect and running an Effect are separate. That helps with being able to run Effects multiple times and with keeping evaluation order sane. For example,
import Effect.Random (random) -- random :: Effect Number
-- would this print the same number 5 times, or 5 different random numbers?
randomNumbers :: Effect Unit
randomNumbers =
for (1 .. 5) \_ -> do
r <- random
logShow r
One could imagine a language in which all Effects are run as soon as they’re defined, and if you wanted to delay some Effect, you have to make it take in some argument to turn it into a “function.” (So random
really would need to be random :: Unit -> Effect Number
in order to behave the way you expect), but that violates one of the key principles of any pure FP language: referential transparency, which means that any function call should be able to be replaced by that function’s contents and the behavior stays the same. Let’s look at an example using the imaginary language I described above
sayHi :: Effect Unit
sayHi = log "hi"
f1 = do
sayHi
sayHi
f2 = do
log "hi"
log "hi"
f1
would output nothing at all (presumably, a single “hi” was output when defining sayHi
), while f2
would output 2 "hi"s, even though the only difference between it and f1
is we substitute the name sayHi
with the contents of sayHi
.
If the example was PureScript instead of this imaginary language, f1
and f2
would be identical.
Since PureScript is a functionally pure language by design, it has to be referentially transparent.
4 Likes
@Adrielus @ntwilson thank you. Your answers are clear and explanatory.
2 Likes