Haskell for Mathematicians: Canceled

(Edit: This post seems to have been put on Reddit and has caused some downtime.)

This post is a pseudo-continuation of Part I.

This series is officially canceled! Let’s discuss why.

I was preparing some rather elaborate code for this post. Basically I was going to start playing with structure and type classes for you all. But I ran into some issues. Ones that turned me off.

Let’s dive right in.

Consider the following definition of Monoid and declaring Int is an instance of Monoid .



class Eq a => Monoid a where add :: a -> a -> a addId :: a instance Monoid Int where addId = 0 add = (+)

To mathematicians, this is essentially stating what operations and elements a monoid must have, and stating that Int , or $\mathbb{Z}$, is a monoid given the assignments shown.

Now consider:



data UnivariatePolynomial t = UnivariatePolynomial [(t, Int)]

phi :: Monoid a => [a] -> UnivariatePolynomial a

phi [1,2,3,4,5]

Basically, it says that $P(t)\cong L(t\times\mathbb{Z})$ where the polynomials over $t$ are denoted $P(t)$, and lists of $t$ are denoted as $L(t)$. Now consider an isomorphism $\phi : L(t)\to P(t)$ for monoid $t$.Assume this is suitably defined. Given that we have declared no other monoids (we have only declared integers as such), what do you expectto return? If you said “the polynomial $5x^4+4x^3+3x^2+2x+1$”, you’d be absolutely…WRONG.

This is what was particularly upsetting. $\phi([1,2,3,4,5])$ is ambiguous from the type theoretical point of view. Let’s figure out why.

Well, we have a list of integers $l \in L(\mathbb{Z})$. And $\phi : L(t)\to P(t)$ for monoid $t$. So $\phi(l)\in P(\mathbb{Z})$ since $\mathbb{Z}$ is a monoid, yes? Of course.

Except that’s not how Haskell works.

In an effort to alleviate issues with explicit type casting of numerical types, Haskell has a type class called Num . Here is its definition.



class (Eq t, Show t) => Num t where (+), (-), (*) :: t -> t -> t negate :: t -> t abs, signum :: t -> t fromInteger :: Integer -> t

You have all sorts of numerical types, all of which should be a superset of the integers. So by classifying an umbrella class to cover all of these numerical types, we can create generalized functions, like addition, to work on all numerical types.

How does this play into my distaste? Well.



*Main> phi [1,2,3,4,5] interactive:1:20: Ambiguous type variable `t' in the constraints: `Num t' arising from the literal `5' at interactive:1:20 `Monoid t' arising from a use of `phi' at interactive:1:0-21 Probable fix: add a type signature that fixes these type variable(s)

Num

phi

Num

Monoid

So what does this mean? It is saying $5$ is a type which is of the type class, butis looking for a monoid. All of that is indeed true! Is it getting confused that it doesn’t know what to decide $5$ is that ofor that of

Turns out that’s not the issue. The issue is that Haskell doesn’t know that $5$ is an integer. More specifically, it knows it could be interpreted as an integer, but it could be interpreted as any numerical type. So we have to clarify.

*Main> phi [1,2,3,4,5] :: UnivariatePolynomial Int 5x^4 + 4x^3 + 3x^2 + 2x + 1 *Main> phi [1,2,3,4,5::Int] 5x^4 + 4x^3 + 3x^2 + 2x + 1

Int

*Main> :type 5 5 :: (Num t) => t

We have to clarify that $5$ is an? Seriously?Yep. Well, okay, it makes sense if you consider \[\big\vert\mathtt{Monoid}\ t\cap\mathtt{Numeric}\ t\big\vert > 1\] to be true. And in general mathematics, it is. But for our definitions, it$1$; I have only defined integers to be an instance of monoids, and Haskell assumes that though I have only declared integers an instance of such, I may declare more in the future!

The fine folks at #haskell clarify.



[18:33] <conal> Quadrescence: i think this behavior is referred to as "the open world assumption". [18:35] <conal> Quadrescence: i guess the behavior you expected is like nonmonotonic logic. where learning new information invalidates previous deductions.

[18:35] <Quadrescence> conal: So let me get this straight. Haskell reads 5, and just determines that it is of type Num t => t. So we have [Num t => t]. Use (phi), we have that [(Num t, Monoid t) => t]. And it is not able to tell what t can possibly be because I perhaps would define more instances of Monoid in the future, and therefore it can't discern that it is the only current possibility: an Int [18:36] <mauke> no, (Num t) => [t] [18:36] <Quadrescence> mauke: I did it for emphasis. [18:36] <mauke> you are using syntax errors and/or different types [18:36] <Quadrescence> mauke: I'm not talking to Haskell inter- preters, I'm talking to humans over the internet. [18:36] <conal> Quadrescence: right (modulo mauke's correction).

mauke

You can say that again. Of course I don’t expect any more than I have already asserted.Pardoning‘s pedantry, this indeed seems to be the case.

I can accept this mathematically, but not practically. If I am to accept this mathematically, then Haskell’s Num is a major issue I don’t want to deal with (and I don’t have to, #haskell said I should rewrite the entire default standard library called the Prelude ). If I am to accept practically, I need to not choose Haskell. The air on which Haskell programmers seem to thrive reeks of foul stench of cargo cult mathematics, something of which I don’t want to be a part. [Edit: Please read the follow up.]

Epilogue

Here’s the code I was going to incrementally present in Part II, if you are interested in exploring further.