I'm barely getting started with Purescript, and I hit a snag quite quickly. Consider the simple (and very artificial) example below:
module Main where
import Prelude
import Data.String.Common (joinWith)
import Data.String.Utils (lines)
import Effect (Effect)
import Effect.Console (log)
contents :: String
contents = "1 2\n3 4"
l :: Array String
l = lines contents
main :: Effect Unit
main = do
log (joinWith "\n" l)
Simple stuff: create an array of strings using the lines
function, join it back to a single string with joinWith
, display it on the console. As expected, it compiles and outputs
1 2
3 4
Now, I rewrite this using "do notation", which seems to be equivalent to me:
module Main where
import Prelude
import Data.String.Common (joinWith)
import Data.String.Utils (lines)
import Effect (Effect)
import Effect.Console (log)
contents :: String
contents = "1 2\n3 4"
main = do
l <- lines contents
log (joinWith "\n" l)
I've just moved l
inside the do
block, so in my mind, it should be equivalent... however, the Purescript compiler disagrees:
Could not match type
String
with type
Array String
while checking that type String
is at least as general as type Array String
while checking that expression l
has type Array String
in value declaration main
If I read this error message correctly, it implies that now l
has type String
, while in the original code (where l
is outside the do
block), it has type Array String
. Yet, in both cases, it receives the result of split
, which does have type Array String
according to Pursuit.
I don't understand what's different in both cases. Why is l
's type different in these two cases, and how could I make the second code example work?
The "left arrow" <-
thingy isn't "variable assignment" as it seems like you're assuming.
Instead, the <-
thingy is something called "monadic bind", which basically means "run the computation on the right and give its result the name on the left". But the important bit is that "computation on the right" is not just any expression. It has to be a computation in the same monad in which the line appears.
So in your case, since main :: Effect Unit
, the do
notation is "running" in the Effect
monad, and therefore, the thing on the right of the <-
arrow has to also be an Effect
. That effect would be executed and the result of it would be named l
.
But the expression lines contents
is not an Effect
! It's an Array
. So the whole thing doesn't compute.
To give a name to a value, which is not a computation in the same monad, use let
instead of the <-
arrow:
main :: Effect Unit
main = do
let l = lines content
log (joinWith "\n" l)
But you complicated things a bit by removing the main :: Effect Unit
type signature, in what I can only assume to be an attempt to shoehorn the bloody thing into working. This was a mistake, because it moved the error and made it less obvious.
The thing is, Effect
is not the only thing that works with the do
notation. Not the only monad, to use a fancypants term. There are many of them. Too many, if you ask me.
In particular, Array
is also a monad. Surprise! So the do
notation also works with arrays. Something like:
squares :: Array Int
squares = do
n <- 1..10
pure (n * n)
And so, since your main
didn't have a type signature to tell the compiler that it's supposed to be an Effect
, and the first line of it was trying to <-
bind an expression of type Array String
, the compiler decided that you meant the whole thing to run in the Array
monad, and happily complied.
And since the whole thing is now in the Array
monad, and the expression to the right of the <-
arrow is of type Array String
, that makes the "result" of that just String
, and so this is what the type of l
must be.
And from there you get an error with joinWith
, because it expects a parameter of type Array String
, but you gave it just String
.
Lesson learned: use type signatures to tell the compiler what you expect things to be. Otherwise it will come up with its own conclusions.
You may be also interested in reading this explanation: Can someone clarify monads / computation expressions and their syntax, in F#