0

Being new to Purescript, I am struggling to figure out why the following code (taken from "PureScript By Example") works as it does:

> flip (\n s -> show n <> s) "Ten" 10
"10Ten"

This makes sense to me: flip calls its first argument (the lambda expression) with its second and third argument in reverse order and so produces the concatenated string.

But I wonder why we get the following response with this snippet:

> flip (\n s -> show n <> s) 10 "Ten"

Could not match type Int with type String

Here is my line of thinking: operator <> which is actually a shorthand for Data.Semigroup.append is called with n deduced to String (an instance of Data.Semigroup) and s deduced to Int. So why can't <> append an Int to a String ? (I suppose because of it being right-associative, but am unsure ...)

Angle.Bracket
  • 1,438
  • 13
  • 29
  • `show n` turns `n` into a string. Thus the `<>` operator is resolved to be coming from the semigroup instance for strings. It expects two arguments of the same type, but in your second example `s` is `10`, which is not a string. – Regis Kuckaertz Apr 18 '18 at 09:40
  • I wasn't aware of how operator precedence works here. See answer and comments below. – Angle.Bracket Apr 18 '18 at 10:15
  • Nice one! It doesn’t have to do with precedence, rather the semantics of `flip`, which does what you did manually, I.e. flip the arguments of the given function. Because you flip twice (once with ˋflip` and once manually), you end up with an Int where a string is supposed to be... adding a signature to the lambda would have made that clear. – Regis Kuckaertz Apr 19 '18 at 10:48
  • But if operator `<>` had precedence over function application the function body would effectively be `show (n <> s)` and so the first call would also not get compiled, right ? – Angle.Bracket Apr 23 '18 at 08:49

2 Answers2

2

To be clear...

flip (\n s -> show n <> s) "Ten" 10  == show 10 <> "Ten"
flip (\n s -> show n <> s) 10 "Ten"  == show "Ten" <> 10

(<>) (an alias for Data.Semigroup.append) has the type:

append :: a -> a -> a

That is, it's arguments must be of the same type (they must match). But in your second call you're passing it a String and an Int, hence the type error.

This behaviour might be surprising if you're coming from a weakly typed language like javascript with implicit type coercion.

Jordan Mackie
  • 1,193
  • 10
  • 17
  • Right, but in my first call I'm also passing two different types, only in a different order: an `Int` and a `String`, so I'd suppose they don't match either. – Angle.Bracket Apr 18 '18 at 09:52
  • Yes, but you're calling `show` which has the type `a -> String`, and function application (prefix) has highest precedence. So in the first case your function body is effectively `(show n) <> s` – Jordan Mackie Apr 18 '18 at 10:00
  • Now the penny has dropped. So its operator precedence that makes the first call legal. – Angle.Bracket Apr 18 '18 at 10:10
  • To everybody who wants to learn about PureScript's type deduction mechanism: also have look at @The Unix Man's answer below – Angle.Bracket Apr 24 '18 at 08:21
2

So let's take this apart a bit. First, the original function:

flip (\n s -> show n <> s) "Ten" 10

If we take the type of each part, what do we see?

> :t flip
forall a b c. (a -> b -> c) -> b -> a -> c

This is really just flip of course, and it takes a two-parameter function and turns it into another one with the arguments flipped.

Next, and this is the interesting part, the (a -> b -> c) of flip, which becomes b -> a -> c:

> :t (\n s -> show n <> s)
forall t4. Show t4 => t4 -> String -> String

Ok, so, how does it come up with t4 -> String -> String? The only function in this one that references String specifically is show:

> :t show
forall a. Show a => a -> String

Also, this is where the Show constraint on t4 comes from. What PureScript is telling us is that (\n s -> show n <> s) is a function, it takes two arguments, and returns a String.

It's calling our first argument t4, which is a valid and unique type variable for this session. It also can't tell us anything about t4 except that since show requires an instance of Show, t4 must be an instance of Show.

Now, calling show on our Show => t4 will return a String, which we expect show to do:

> :t show
forall a. Show a => a -> String

(Yes, we saw this already.) So, inside our function (\n s -> show n <> s), the show n term has type String. This is because show has been fully applied with t4, which is an instance of Show, and so the compiler can infer that show n will have the type of show, String.

Now this is where it gets interesting. <> in its most general form has a single type parameter:

> :t (<>)
forall a. Semigroup a => a -> a -> a

It's a function, and it takes two values of type a, and returns a new value of type a. Note how while the function has a type parameter, it only has one type parameter, so any particular (<>) will not be polymorphic in its types.

Now, while our function is polymorphic in its first parameter, it also only has a single type variable. This is actually a bit of a red herring anyway, because let's see what flip does:

> :t flip (\n s -> show n <> s)
forall t5. Show t5 => String -> t5 -> String

Woah. Our polymorphic parameter is still there, but now it's the second one. This is what flip does. So what does this mean, really?

> flip (\n s -> show n <> s) "Ten" 10

In this case, "Ten" is a String, which our flipped function expects. 10 is, well, it's something:

> :t 10
Int

In this case it's an Int. Is Int an instance of Show?

> show (10 :: Int)
"10"

Yep! So, the values "Ten" and 10 satisfy the parameters of the flipped function, forall a. Show a => String -> a -> String, where "Ten" is String and 10, an Int, is an instance of Show.

Now, let's look at the failing case:

> flip (\n s -> show n <> s) 10 "Ten"

"Ten" is a String. Is String an instance of show?

> show "Ten"
"\"Ten\""

It is! Neat! So that's fine. Now, is 10 a String? Well, no, sadly, it's not. So 10 can't be used as the first argument to the flipped function.