My experience as a PS beginner learning from the PureScript by Example book

Hi everyone, @milesfrain mentioned that it would be useful to people who are editing the PureScript by Example book to know more about what people are thinking as they are reading through the book.

I’d like to contribute so I began taking notes. I started taking notes from Chapter 4 and I will add more as I make more progress through the book.

6 Likes

Chapter 4 (roughly first half)

Maps

Chapter 4 mentions Arrays a few times and shows Array literals, such as “[1, 2]”. This makes me wonder if Arrays are the main list-like construct (like in JavaScript) or if other links of list-like constructs are more commonly used in PureScript, such as pairs, tuples or linked lists and whether PureScript has literal notations for them too.

Infix Operators

:thought_balloon: Ok, so the notation for calling a function as an infix operator is the same as in Hasekell.

:thought_balloon: If I want to find out which function implements the computation of an operator, what would be the “standard” way to find out? Do I look it up on Pursuit?

Exercises (after Filtering Arrays)

In the medium difficulty exercise, we’re encouraged to experiment in the REPL with setting the precedence level of custom operators.

The problem description mentions PSCi.

:thought_balloon: I remember that PSCi is a separate command line too. Is this exercise mentioning that CLI tool (directly or indirectly) or is PSCi just the idea of a PureScript REPL? Now I wonder if spago repl is a compilation step followed by a call to the PSCi command."

I played around a bit, writing expressions in which the custom operator is used next to other operators, to see which precendence level would allow me to write the expressions I want to, without too many parantheses.

I didn’t get very far though, partly because I struggle to understand the type errors given by PureScript compiler and partly because my knowledge of language so narrow that there is not much room for me to experiment.

I ended up thinking that I should understand left- and right-associativity. I read a bit of that on the Wikipedia page, then gave up. In the end, I just copied the precedence declaration from the <$> operator :).

I like this idea. Playing around with concepts is an essential part of learning a new programming language, but I think I would have had more success if given a nudge in the right direction (or a hint of the scope of the problem).

Do Notation

There is a tangential comment “map and concatMap are so fundamental, that they (or rather, their generalizations map and bind)” that made me wonder:

:thought_balloon: In what sense and how are map and bind generalisations of map and concatMap? And oddly, is map a generalisation of map?

Then in the next paragraph, the book mentions monads (saying “write so-called monad comperhensions”).

:thought_balloon: Good, I was looking forward to doing IO.

In the same paragraph, “but in this chapter, we will only consider arrays”.

:thought_balloon: Oh, ok. What, so an array is a monad!?

Then the book shows this code snippet:

$ do
  i <- 1 .. n
  j <- i .. n

:thought_balloon: Well ok I still don’t understand it completely but it just looks like a nested for loop.

Then the book says this: “In the last line, we use the pure function. This function can be evaluated in PSCi, but we have to provide a type:”

:thought_balloon: Is the book saying that there are “typeless” values, that are defined, but do not have a type? How can that be? Can “typeless” values exist in any source file or code context, or only in a special context, like … something to do with monads?

In various code snippets: factors, factors', factors'' and factorsV2. A passing thought for me, when seeing these binding names was if I should expect to see similar name conventions in other PureScript code.

In the last paragraph of this section, the book says “In the case of arrays, pure simply constructs a singleton array. In fact, we could modify our factors function to use this form, instead of using pure:”

:thought_balloon: Okay, well now I want to understand pure. What is it? Something similar to the unit function? Does it do some sort of packing or unpacking of a type? Is it a part of the PureScript language definition or is it something defined in PureScript code, something I could define myself?

Guards

The book shows this code snippet.

...
  guard $ i * j == n
  pure [i, j]

:thought_balloon: Ok, so pure and guard are the same kind of thing. Like, they’re “made of the same stuff”.

After re-reading this sentence in the last paragraph of the section I relised I got stuck on it: “This means that if the guard fails, then the current branch of the array comprehension will terminate early with no results.”

:thought_balloon: Do we conclude that the “guard has failed” because of what the output is like or do we know that the guard has been given a failed condition because the value “false” was passed to it?

“[…] guard is equivalent to using filter on the intermediate array.” So this “do …” construct is a way to manipulate/transform things? Is guard different from filter, but equivalent when guard is used within a certain construct?

Does guard return anything other than a length 0 or length 1 array?

Does guard return the actual value “unit”, wrapped around the monadic array thing or is the meaning of the return value more complex?

How come guard true returns an error in the REPL but length (guard true) executes successfully and returns the value “1”.

In this precise moment, I’m wondering if the language should be advertised as appropriate for people with Haskell experience :).

Ok, so it’s difficult to test the individual pieces in REPL. I’ll try a whole block of code with the “do” construct in a source file and make modifications to that to get a feel for things.

Is there any way to just run a source file, without having to setup a spago project?

Exercises (after Guards)

Exercise (3) says “[…] and calculates all Pythagorean triples whose components are less than or equal to n […]”. Here, although I sort of guessed it, I wasn’t so sure that the word “components” refers to the values “a”, “b” and “c”.

This meaning could be made more explicit, such as by wording it like this: “each component (a, b and c) is less than or equal to”.

Exercise (4) I sort of enjoyed this exercise but also sort of spent a few hours staring at type errors that I couldn’t understand. There were two main difficulties that I had solving this exercise.

Difficulty (1): The problem definition seems to be inconsistent with the test cases. It seems that the problem description is asking for a list of all possible factorisations of an integer (e.g.: 20 can be 2x2x5 but also 4x5) while the test cases are verifying that the function performs the prime factorisation of the input (which is a different task as the prime factorization is the one that is unique).

The problem description could be made more clear with wording such as the following: "Write a function factorize which produces the prime factorization of integer n".

Difficulty (2): My brain kind of insisted that solving this the right way must be done with pattern matching on Maybe things. After some searching on the internet I found out how to pattern match on the Maybe data thing (data value? data type?) but still had to deal with errors such as the following.

Cannot import type Just from module Data.Maybe
It either does not exist or the module does not export it.

Lastly, I think it would more appropriate if the test did not check the order of the prime factors.

3 Likes

I made a pull request for exercises 2-3 and 2-4.

Does guard return anything other than a length 0 or length 1 array?

Does guard return the actual value “unit”, wrapped around the monadic array thing or is the meaning of the return value more complex?

You might have been able to answer these questions by looking up guard in Pursuit and following the link to its source code, which will show you that it is implemented like this:

I’d be interested to know if these things are still unclear after seeing the implementation of guard.

The “Cannot import type Just from module Data.Maybe” is something we should fix inside the compiler. The syntax is import Data.Maybe (Maybe(Just)) to import the Maybe type and the Just constructor, or alternatively import Data.Maybe (Maybe(..)) to import the Maybe type and all of its constructors. It’s very common for it to trip people up as they try to import constructors by writing e.g. import Data.Maybe (Just), which as you saw doesn’t work because Data.Maybe doesn’t export a type Just. If you try to import a type from a module, and the module doesn’t export a type with that name, but it does export a constructor with that name, then the compiler should be able to suggest the correct syntax.

3 Likes

You might have been able to answer these questions by looking up guard in Pursuit and following the link to its source code, which will show you that it is implemented like this:

That’s interesting. I didn’t think to lookup guard in Pursuit because I thought it might e a language construct. Seeing that it is simply a function with an implementation in PureScript helps a lot, it means that I can lookup MonadZero, pure and empty and gain a grounded understanding of how it works.

I also remember reading somewhere that the do notation is a syntactic sugar and so it can be de-sugared. That means that I can break down do blocks and study the parts one by one.

The “Cannot import type Just from module Data.Maybe” is something we should fix inside the compiler.

It would be quite great to get a nudge in the right direction from the compiler.

Thank you for your response and the information!

3 Likes

Regarding do notation desugaring… If you want a 5-minute explanation of Monads and the gist of how they work, my see “Understanding Monads” from my learn-halogen repo. It also covers do notation briefly. Ignore the “Monad Transformers” part as the explanation behind why “monads don’t compose” is wrong and I haven’t updated it yet.

If you want a deeper explanation, I’d recommend you read through the following resources from my learning repo:

Great, I will have a look.

Okay, I read the “Understanding Monads” article (very useful, by the way!) so I now understand that the “do …” notation is shorthand for chained calls to bind, a function that Monads are defined with.

So now I know that I want to focus on understanding Applicatives and Monads :+1:

1 Like