Hello everyone first time poster here.
I am going through the purescript by example book and one of the proposed solution to this exercise raised the question about let-in
vs let
(with out in) when do I use one or the other?
Why this example was not written with let-in
?
recurseFiles :: FilePath -> Aff (Array FilePath)
recurseFiles file = do
contents <- readTextFile UTF8 file
case contents of
"" -> pure [ file ]
c -> do
let
dir = Path.dirname file
files = split (Pattern "\n") contents
filesFromRoot = map (\f -> Path.concat [ dir, f ]) files
arrarr <- parTraverse recurseFiles filesFromRoot
pure $ file : concat arrarr
Thanks for taking your time to answer this silly question
4 Likes
I don’t think it’s a silly question. It’s pretty confusing to start with! Simply put, let
without in
is used only in do
blocks, and is transformed to a let ... in
expression by the compiler as part of its desugaring of do
blocks. (If you don’t know already, do
blocks are just a convenient way of writing expressions with monadic “stuff” going on.)
The description of the syntax for do
blocks is here and the description for “normal” let
is here. The example for the do
block there might make it a bit clearer what’s going on.
7 Likes
Ok now I know how it is used. Now when should I use on or the other?
What is more idiomatic, performant or has an edge over the other:
recurseFiles :: FilePath -> Aff (Array FilePath)
recurseFiles file = do
contents <- readTextFile UTF8 file
case contents of
"" -> pure [ file ]
c -> do
let
dir = Path.dirname file
files = split (Pattern "\n") contents
filesFromRoot = map (\f -> Path.concat [ dir, f ]) files
arrarr <- parTraverse recurseFiles filesFromRoot
pure $ file : concat arrarr
recurseFiles :: FilePath -> Aff (Array FilePath)
recurseFiles file = do
contents <- readTextFile UTF8 file
case contents of
"" -> pure [ file ]
c -> let
dir = Path.dirname file
files = split (Pattern "\n") contents
filesFromRoot = map (\f -> Path.concat [ dir, f ]) files
in do
arrarr <- parTraverse recurseFiles filesFromRoot
pure $ file : concat arrarr
Or even the where
version
recurseFiles :: FilePath -> Aff (Array FilePath)
recurseFiles file = do
contents <- readTextFile UTF8 file
case contents of
"" -> pure [ file ]
c -> do
arrarr <- parTraverse recurseFiles filesFromRoot
pure $ file : concat arrarr
where
dir = Path.dirname file
files = split (Pattern "\n") contents
filesFromRoot = map (\f -> Path.concat [ dir, f ]) files
There isn’t a difference between any of those. They all end up the same. I personally never use let/in
. At Awake we almost always use do/let
, even for “pure” expressions, while also using where
if it reads better. Sometimes things read better when the nitty-gritty is put last (no need to worry about the details). where
doesn’t work with lambdas though, and so if you want uniformity, then you should use let/in
or do/let
, and I think let/in
looks terrible.
2 Likes
I originally used let in
until I learned that I could use do let
for even non-monadic code. I think using do let
is better than let in
for at least one reason. If I ever needed to migrate non-monadic code to monadic code, I only need to change the type signature and the implementation, not the let
part.
4 Likes
I did not know about do/let
in pure function, I think you should add it to jordan reference
but I hate that purty
does not allow let
in single line
It’s kind of hard to describe do/let
in a reasonable location in my repo. I have tried to ensure that every page never assumes you know more than what I’ve already described in the pages before that one.
I currently describe the do/let
part in the Prelude Syntax folder. However, the issue of do/let
is that one needs to understand monads before one describes it to readers. Else, why not just use let/in
? So, if most people just read through the Basic Syntax, where I cover let/in
, they might not read the Prelude Syntax.
Moreover, this is a syntactical choice, so it doesn’t fit in the “Design Patterns” part of the work.
Perhaps I need to state do/let
more explicitly in the Prelude Syntax folder?
1 Like
oh I’m not finished reading your reference yet, so I didn’t know that you mentioned do/let
anywhere.
1 Like
Wow, I never knew that do
/let
could be used for non-monadic code too. Neat!
I’m also surprised to see that where
binds to do
! But only sometimes? This seems to work:
x :: Int
x = case true of
false -> 0
true -> do a + b
where a = 1
b = 2
But not this:
x :: Int
x = (
do a + b
where a = 1
b = 2
)
Is it just for do
-blocks directly after =
, ->
, and in
?
you can use where in any case branch! (you do not need the do block)
1 Like
Grammatically, where is tied to the right-hand-side of = and case ->. It is not an arbitrary infix operator.
1 Like