Purescript Book - Questions

At the top of the page it says:

Or, “what is the PureScript equivalent of in JS?”

So it only mentions string concatenation since that’s the only relevant JS operator that is covered by it :wink:

1 Like

It’s asking you to come up with a way of describing those arrays - what do they all have in common which is not true of other arrays? Essentially you’re being asked to express in more natural language what foldl (==) false does.

1 Like

I’ll walk though an example of doing this for lift2.

  1. Click the Source link next to the function on pursuit
  2. Select “Branch: master” from the dropdown in the upper-left corner on the github source page.
  3. Follow these steps to edit the file, and submit a PR.

Here’s my PR to add examples to lift2.

I remember this question being tricky to reason through at first, so one way to get started is to just try a bunch of different input arrays and see if you observe a pattern. Then you can work backwards and think through what foldl is doing with all the parameters.

logShow $ foldl (==) false []
logShow $ foldl (==) false [ true ]
logShow $ foldl (==) false [ false ]
logShow $ foldl (==) false [ true, false ]
logShow $ foldl (==) false [ true, true ]
logShow $ foldl (==) false [ false, false ]

that summary of Functor, Apply, Applicative in terms of argument -arity seems really good, and i can’t recall seeing it before anywhere, wish i’d had that when i was learning it.

1 Like

That guy articles are pretty good. When I started learning some CS I used his algorithm book and was a very gentle introduction @afc

What is meant by -arity?

arity is the number of parameters to a function

1 Like

@milesfrain @JordanMartinez done added ch4 tests if you want to check https://github.com/p2327/purescriptbook/blob/master/ch4/test/Main.purs

If you’d like to have the tests you write incorporated into the book, feel free to sign-up for a chapter here. Looks Scott just tackled Ch4.

1 Like

Moving on the book to chapter 6 I am not sure I understand what is asked in the exercise:

(Easy) The following declaration defines a type of non-empty arrays of elements of type a :

data NonEmpty a = NonEmpty a (Array a)
  • Write an Eq instance for the type NonEmpty a which reuses the instances for Eq a and Eq (Array a) .

I checked the source code and the instance for Eq for Array sends me to a JS implementation:

-- from PS source 
instance eqArray :: Eq a => Eq (Array a) where
  eq = eqArrayImpl eq
-- Eq.js
exports.eqArrayImpl = function (f) {
  return function (xs) {
    return function (ys) {
      if (xs === ys) return true;
      if (xs.length !== ys.length) return false;
      for (var i = 0; i < xs.length; i++) {
        if (!f(xs[i])(ys[i])) return false;
      }
      return true;
    };
  };
};

I wrote my own implementations of Eq and show for the NonEmpty type like the below, but I am not sure I am doing what the exercise is asking?

-- Ex 1
instance eqNonEmpty :: Eq a => Eq (NonEmpty a) where
    eq (NonEmpty x xs) (NonEmpty y ys) = (eq x y) && (eq xs ys)

instance showNonEmpty :: Show a => Show (NonEmpty a) where
    show (NonEmpty x xs) = show x <> show xs

I’m pretty sure that’s exactly what the exercise expects you to do for the Eq instance. The Show instance probably doesn’t quite do what you’d want it to, you could give it a try in the repl to see.

Thanks @hdgarrood cool! So is simply asking use eq for both combined…

Yes I don’t think show is correct…

Maybe

-- snip
show (append [x] xs)

Not sure I’m reading too much into this but what’s the use of such data type, for example with the above implementation I get:

-- omit required imports 
> x = NonEmpty 1 [2, 3]
> show x
”[1, 2, 3]"

I wonder if there might be a somewhat more practical / real-world way to have an exercise on type constraints than the NonEmpty example, but I haven’t come up with something concrete :frowning:

The goal is for show x to print the PureScript code required to construct x, but your implementation above will construct an Array instead of a NonEmpty.

1 Like

I would argue that being able to distinguish empty versus non empty arrays in a way that the compiler can check for you (which is what NonEmpty does) is very practical - it allows you to rule out bugs. For instance, you might want to have a parser produce an array of error messages if it fails, but you want to guarantee that if it does fail, the array of failures is nonempty. The “foreign” library does exactly this.

1 Like

@paulyoung The goal is for show x to print the PureScript code required to construct x , but your implementation above will construct an Array instead of a NonEmpty .

Ok but then I am not sure I understand - the definition of nonempty given is of an element of type a with an array of the same type, e.g. NonEmpty 0 [4, 5]

How would I represent this with show?

I checked Data.NonEmpty in the source and this is the Show instance

instance showNonEmpty :: (Show a, Show (f a)) => Show (NonEmpty f a) where
  show (a :| fa) = "(NonEmpty " <> show a <> " " <> show fa <> ")"

So I tried this in the repl

> import Data.Show
> import Control.Plus        
> import Data.NonEmpty   
> nonEmptyX = NonEmpty 1 [1, 3, 4]  
> show nonEmptyX
"(NonEmpty 1 [1,3,4])"

But my initial implementation

instance showNonEmpty :: Show a => Show (NonEmpty a) where
    show (NonEmpty x xs) = show x <> show xs

Looks similar to the one in the source? So I just need to do:

instance showNonEmpty :: Show a => Show (NonEmpty a) where
    show (NonEmpty x xs) = "(NonEmpty " <> show x <> " " <> show xs <> ")"

@hdgarrood gotcha - I have a question tho

I can construct a NonEmpty by doing

a = NonEmpty [1, 2]

So the compiler knows a is NonEmpty.

But if I try show a, I get an error, is this intended?

> a = NonEmpty [1, 2]
> show a
Error found:
in module $PSCI
at :1:1 - 1:7 (line 1, column 1 - line 1, column 7)

  No type class instance was found for
  
    Data.Show.Show (t1 (Array Int) -> NonEmpty t1 (Array Int))
  
  The instance head contains unknown type variables. Consider adding a type annotation.

Yes, that error is intended. If the error message were a bit better it might say “maybe you haven’t applied a function to enough arguments?” The reason is that you haven’t fully constructed a NonEmpty: the NonEmpty constructor takes two arguments. A NonEmpty Int consisting of the elements 1, 2 would be constructed with NonEmpty 1 [2].

1 Like

Even though this was probably not your intent, showing how to apply the remaining argument might clarify things. For example, you could put your Array Int into a NonEmpty collection of another Array of arrays or a List of arrays. This parent collection is what that t1 type variable in the error message represents.

> a = NonEmpty [1, 2]

> b = a [[3, 4], [5, 6]]
> b
(NonEmpty [1,2] [[3,4],[5,6]])

> :t b
NonEmpty Array (Array Int)

> c = a ([3, 4] : [5, 6] : Nil)
> c
(NonEmpty [1,2] ([3,4] : [5,6] : Nil))

> :t c
NonEmpty List (Array Int)

The NonEmpty syntax tripped me up too when going through the book, so I figured it’s about time to add some more beginner-friendly examples to the docs (PR). Looking forward to when these can be replaced with doctest.

Also, I’ll add a note in the book that show should produce what you’d need to paste into the repl to create the value being shown. (Edit: PR)

2 Likes

Fantastic, thanks both! :slight_smile:

I am stuck again, trying to implement Semigroup

So I tried

instance semiGroupNonEmpty :: Semigroup (NonEmpty a) where
    append (NonEmpty x xs) (NonEmpty y ys) = 
        (append (NonEmpty x) (NonEmpty y)) (append xs ys)

In the repl

--
> import Exercises        
> b = NonEmpty 2 [1, 2]
> a = NonEmpty 1 [1, 2]
> append a b           
C:\projects\purescriptbook\ch6\.psci_modules\node_modules\Data.Semigroup\foreign.js:13
    return xs.concat(ys);
              ^

RangeError: Invalid array length

Then I tried the same operation with the source module, why is this giving an error?

> import Data.NonEmpty
> a = NonEmpty 1 [1, 2]
> b = NonEmpty 2 [1, 2]
> append a b
Error found:
in module $PSCI
at :1:1 - 1:9 (line 1, column 1 - line 1, column 9)

  No type class instance was found for
  
    Data.Semigroup.Semigroup (NonEmpty Array Int)
  

while applying a function append
  of type Semigroup t0 => t0 -> t0 -> t0
  to argument a
while inferring the type of append a
in value declaration it

where t0 is an unknown type

Then if I try

instance semiGroupNonEmpty :: Semigroup (NonEmpty a) where
    append (NonEmpty x xs) (NonEmpty y ys) = 
        NonEmpty x (append xs (append [y] ys))

In the repl

> x = NonEmpty 1 [ 2, 3 ]
> y = NonEmpty 4 [ 5, 6 ]
> append x y
(NonEmpty 1 [2,3,4,5,6])

I have a bit of a hard time grasping this… In my head If I append NonEmpty 1 [ 2, 3 ] to NonEmpty 4 [ 5, 6 ] I should get something like NonEmpty [1, 4] [[2,3], [5, 6]] …as @milesfrain has done with the a, b example above? So I would end up with an array type and array of arrays?

Not quite. The unit tests should help answer this question.


You should enable those to check your work as you solve each exercise.

Looks like the latter solution you wrote is correct.

A good way to think about NonEmpty is that it should behave like an array (or other collection) that’s guaranteed to have at least one element (represented by that first element displayed outside of the collection). Appending two 1D arrays should still result in a 1D array.

1 Like

When you write this:

(append (NonEmpty x) (NonEmpty y))

you’re actually making use of the semigroup instance for functions, which is defined like this:

instance semigroupFn :: Semigroup s' => Semigroup (s -> s') where
  append f g x = f x <> g x

so what you end up with is:

append (NonEmpty x) (NonEmpty y)
= \a -> NonEmpty x a <> NonEmpty y a

Therefore, when you write append (NonEmpty 1 [1,2]) (NonEmpty 2 [1,2]) it evaluates as:

append (NonEmpty 1 [1,2]) (NonEmpty 2 [1,2])
= (\a -> NonEmpty 1 a <> NonEmpty 2 a) (append [1,2] [1,2])
= (\a -> NonEmpty 1 a <> NonEmpty 2 a) [1,2,1,2]
= NonEmpty 1 [1,2,1,2] <> NonEmpty 2 [1,2,1,2]

I think the error you’re seeing is arising from the fact that eventually the array becomes so large that your JS VM is refusing to concatenate them.

1 Like