Hi all,
Today I wanted to showcase a simple example of side effects as handled in Haskell and in Python on social media. I thought I understood it, but maybe not because a comment made me doubt enough to take down the post. I’d love to get more insight here.
Consider the following snippet from a ghci session:
>>> e = print 5
>>> :type e
e :: IO ()
>>> e
5
>>>
And here is one from Python:
>>> e = print(5)
5
>>> type(e)
<class 'NoneType'>
>>> e
>>>
To me, the 5
appearing under the variable assignment in Python is clearly a side effect due to evaluating print 5
and assigning what it returns (None
) to e
, while on the Haskell end print 5
gets bound as IO ()
to e
without getting evaluated until passed top-level to the REPL which can run IO. However, the commenter mentioned this was just due to lazyness on the Haskell side, saying that the print function only gets evaluated anyway once you display e in the REPL. Fair, and he also provided examples of languages F# and OCAML that can behave differently, showing 5
in the REPL immediately after the assignment.
A bit confused, I tested what Purescript (strict and pure language afaiu) does and got the same behavior as in Haskell …
>>> import Effect.Console (logShow)
>>> :t logShow
forall a. Show a => a -> Effect Unit
>>> e = logShow 5
>>> :type e
Effect Unit
>>> e
5
unit
>>>
I thus still think it’s a valid example and it’s correct to say Haskell doesn’t show it immediately because it’s capturing side effects in a IO ()
datatype. But is this correct? My final thought was, that yes, lazyness and capturing-side effects in a type like IO are closely related in that they both delay execution until triggered by something. That something is, however, more constraint for IO, requiring to be a very specific context such as the REPL.
Do you think observing that side effect after a = print(5)
in the REPL is a valid example? If not, what alternatives are equally short, common and easy to understand?