Hacker News new | past | comments | ask | show | jobs | submit login
Haskell and the "foul stench of cargo cult mathematics" (symbo1ics.com)
65 points by fogus on Dec 27, 2010 | hide | past | favorite | 51 comments



I find myself wondering if this is getting upvoted by Haskell contrarians who aren't actually reading the article and noticing that it not about Haskell "being too mathematical", but not being out-of-the-box suitable as a really convenient ring theory explorer. I bet a lot of the upvoters would actually consider this a good sign rather than a bad one, on average....

There's still some room for Haskell syntax improvements. I've found the recent OverloadedStrings to be a big step forward, allowing you to directly use strings in places where it actually wants another type, removing a ton of useless and hard-to-factor-out code converting strings to ByteStrings, Text, and various other types that are there simply to label Strings as something more specific.

As dfrankes says, it is known the Prelude isn't perfect, but I'm not sure there's actually a perfect formulation regardless of what you do. Even if you could get all the mathematicians to agree completely about what all relevant mathematical entities are, something that is a lot harder than it sounds because it turns out a lot of them can have arbitrarily-chosen fiddly details when you really get down to it (how many set theories are there again?), you'll never bridge the gulf between a conventional programmer and any mathematician, and I think Haskell's promise as an interesting language lies rather more with the conventional programmer case than making mathematicians happy. (Not because the latter is bad, but because it is a sufficiently hard problem that trying to reconcile the needs of such a beast with something still useful is probably impossible.)


I find myself wondering if this is getting upvoted by Haskell contrarians who aren't actually reading the article and noticing that it not about Haskell "being too mathematical", but not being out-of-the-box suitable as a really convenient ring theory explorer.

I love how you, in the same sentence, managed to put in doubt credibility of the upvoters and downplay the issue in hand.

To answer your question: yes, I actually read the article. Why I upvoted it? Haskell's way of wrapping integer literals with implicit `fromInteger` seems to be a clever hack that alleviates some pain when dealing with basic arithmetics, but I always wondered what kind of unexpected problems it may cause. I got an answer in this article.


"downplay the issue in hand."

No, I don't. The issue is serious. I'm personally a proponent of tossing the current Prelude and replacing it with something more useful. I think the supposed costs are smaller than expected (it's already modular) and the benefits underestimated. I'm not a Haskell booster, I'm an interested skeptic.

I just think an article about a relatively esotoric issue about Haskell got upvoted suspiciously quickly on a holiday weekend. It's not a bad article by any means.


However, you should wonder whether that is also the motivation of most other upvoters. I find the title so appallingly lacking in understanding and respect that I really couldn't care less about the contents when deciding whether to up- or downvote it. I don't doubt the same could hold for many others, only resulting in the reverse action.


`xi`, notice that if you substitute `Float` or `Double` for his `Int` throughout his module, you get the same type error he gets, as you would intuitively expect:

    [1 of 1] Compiling Main      
    Ok, modules loaded: Main.
    *Main> p [1,2,3,4,5]
    <interactive>:1:11:
        Ambiguous type variable `t' in the constraints:
 
If however you substitute `Integer` for `Int`, which is after all what he claims to have meant, then suddenly:

    [1 of 1] Compiling Main              
    Ok, modules loaded: Main.
    *Main> p [1,2,3,4,5]
    (5x^4 + 4x^3 + 3x^2 + 2x + 1)
    *Main> :t it
    it :: UnivariatePolynomial Integer
It seems, then, that if he had known that in Haskell the 'integer' type is called Integer, not Int, he wouldn't have had any difficulty, or any blog post, or any occasion unjustly to malign his teachers `conal` and `mauke`, to whom he ought rather to be grateful.


People don't neceessarily up vote in agreement, it could be so that the story stays near the top long enough to be addressed by a Haskell proponent. For a while there were no replies to this submission.

Besides, now we've all got a good excuse for why we've not learned Haskell yet: the type system is like so broken ;)


I guess because Conal explains the type inference limitation in question using mathematical terminology, it's tripped the author's "cargo cult mathematics" detector, but it actually sounds more like a software engineering concern. They want to avoid having new instance declarations break existing code (inferences), which seems like a reasonable property to not want to have from a software engineering POV. It makes more sense when building from code files than in interactive mode.


A programming language where you have to tell it the type of expressions if the type is ambiguous? That would be a deal killer, no wonder nobody uses Haskell. Java handles this much better:

    Integer i = new Integer(5);
I mean, look at how concise and readable that is. Java knows that 5 is an Integer and you don't even have to tell it.

Oh wait, the opposite.

Basically, the author seems upset that "5" is a Number rather than an Integer. But that's fine, because it's true. 5 is a real fraction. 5 is a complex fraction. 5 is a double. 5 is a char. 5 is an unsigned long long. 5 is a lot of things to a computer. So sometimes, you have to tell it which one you want.

This isn't cargo-cult mathematics, but rather reflects that Haskell is a computer programming language. There is a lot of math in there, sure, but at the end of the day, you are writing computer programs to run on computers. And computers don't know what an Integer is. It just knows that it has some bits in memory and that it should interpret them in a certain way.

This guy's blog is weird.


> Basically, the author seems upset that "5" is a Number rather than an Integer. But that's fine, because it's true. 5 is a real fraction. 5 is a complex fraction. 5 is a double. 5 is a char. 5 is an unsigned long long. 5 is a lot of things to a computer. So sometimes, you have to tell it which one you want.

A contributing factor here is that there are no subtype relations in Haskell. In math, "5" is all of those things simultaneously, but in Haskell it can only be one at a time.


>In math, "5" is all of those things simultaneously, but in Haskell it can only be one at a time.

How is that?

Excerpts from ghci dialog:

  Prelude> :t 5
  5 :: (Num t) => t
  class (Num a) => Fractional a where
    (/) :: a -> a -> a
    recip :: a -> a
    fromRational :: Rational -> a
  Prelude Data.Complex> :i RealFrac
  class (Real a, Fractional a) => RealFrac a where
  ...
  Prelude Data.Complex> :i RealFloat
  class (RealFrac a, Floating a) => RealFloat a where
  ...
  Prelude Data.Complex> :i Complex
  data (RealFloat a) => Complex a = !a :+ !a
  instance (RealFloat a) => Eq (Complex a)
So, 5 is an Int, Integer, Rational, Double and Complex (which, actually, could be Complex Int or Complex Double).


(Num t) => t means that the value can have the type of any member of class Num, but it can't be more than one type at a time. There are no subtypes in Haskell.


He's not the first mathematician to get annoyed by how poorly designed the numeric classes in the Haskell prelude are. But there are better alternatives already out there. http://hackage.haskell.org/package/numeric-prelude


No, he seems to get tripped up by the open world assumption, which is a limitation of Haskell's type system. It allows separate compilation of different modules, for example.

But nobody said that Haskell's type system is without limitations, right?


On what grounds are you affirming that this guy http://symbo1ics.com/blog/?page_id=2 is a mathematician?


On what grounds are you affirming that guy's not?


I didn't make any affirmation. I don't know what the criteria are for coming under the heading "mathematician". I would count an undergraduate degree as adequate -- but he is 20; he does not call himself a mathematician; he says rather that he is 'passionate about mathematics, mathematical exposition and programming'. One could also study the internal evidence of the post.


I don't think "cargo cult" means what the author thinks it means. It's not just a general epithet.


I think he's making an appropriate reference to Feynman's definition of "cargo cult science" (http://www.lhup.edu/~DSIMANEK/cargocul.htm):

"So I call these things cargo cult science, because they follow all the apparent precepts and forms of scientific investigation, but they're missing something essential... It's a kind of scientific integrity, a principle of scientific thought that corresponds to a kind of utter honesty... In summary, the idea is to try to give all of the information to help others to judge the value of your contribution; not just the information that leads to judgment in one particular direction or another."


I downvoted you, because we may assume jemfinch and other readers understand the reference (or will look it up) and concluded this isn't a case of 'cargo-cult'-anything. In response, you merely cite the source where the phrase is coined, without explaining why this would be an instance of it, even though there is obviously disagreement on exactly that.

This is not an example of 'cargo-cult', because we are not dealing with people going through motions without knowing why. Asserting such is just insulting the creators of Haskell/the Prelude/standard Haskell classes, who are obviously making trade offs and perhaps, gasp mistakes. Being wrong, impure or simply making choices some others disagree with does not make one a 'cargo cult' anything. Feynman must be turning in his grave.


jemfinch suggested that the author did not understand the phrase, but it was not obvious to me that jemfinch understood it--a "cargo cult" and "cargo cult science/mathematics" are two different concepts, and I find physics literacy on HN rather low.

By "appropriate," I meant to suggest the author's usage of it can be construed as consistent with Feynman's meaning. I linked the source so folks could decide for themselves if the author's claim was correct in addition to having a meaning we could all agree on.

[I don't know anything about Haskell, I was just trying (and apparently failing) to clarify some muddy discourse.]


To people who had read the essay and were familiar with the post-Feynman epithet "cargo cult X" it will have been plain that jemfinch knew what it meant and that the original author was clueless about how to apply it.


Why is this 'appropriate' rather than unjust slander against conal and mauke?


Because it's not slander against either of them. See the comments.


Oh I hadn't realized I was dealing with another of his sock puppets.

It is slander against them as I point out in [my comment on your comment](http://symbo1ics.com/blog/?p=788) . If you cut out all use of the text from #haskell, it would be just another uninformed farrago of lies about Haskell, like the other one you generate in your comment -- the one about the Monad type class, there are perhaps several other -- which is a criminal slander directed against Philip Wadler in particular. He introduced type classes in the late 80's thinking in particular of ML. See the papers assembled on http://homepages.inf.ed.ac.uk/wadler/topics/type-classes.htm... (It is by the way this rather unremarkable use of type classes that is the source of your difficulties). Note that even in the 1995 co-authored paper "Type classes in Haskell" makes no mention of a Monad typeclass; indeed type classes that operate not on individual types, but on type formers like Maybe or IO etc. are no where envisaged.

Later, following the lead of Moggi's 'monadic' semantical doctrine, he saw that a programming language could internalize this idea, and moreover that he could introduce a type class Monad into Haskell itself, a move far outstripping anything you could previously have anticipated from the type class concept. Papers on this are to be found here http://homepages.inf.ed.ac.uk/wadler/topics/monads.html

Haskell was well under way before this move was made. Type class Monad was a rather late introduction.

Now, you declare these actions of Wadler's to be a "use of ... pseudo-mathematics as reasoning in the decision-process for the Haskell language". You suggest that Wadler was unaware that every monad on a category is a functor from the category to itself. And you suggest that Wadler can be brought under this heading: "These individuals assimilate the concepts, and then pervert them, giving them names which are not appropriate — a kind of pseudo-mathematics."

This is all outright slander. Here your rigorous mathematical meditation on the pitiful wanna-be and fraud Wadler in full:

There are individuals, however, who thrive on the “mysteriousness” afforded by this unapproachability. They revel, rather than find distaste, in the “genius” image pinned to those who can speak in this technical tongue. These individuals assimilate the concepts, and then pervert them, giving them names which are not appropriate — a kind of pseudo-mathematics. Haskell’s Monad is a prime and very uncontrived example. It is impossible to ensure that a monad within Haskell conforms to the monadic axioms (in other words, it is impossible to ensure a monad within Haskell is actually a monad). Furthermore, though it is often regarded as a historical mistake, monads in Haskell are not instances of the Functor type class. This is particularly strange, because monads in category theory are functors by definition. By considering the use of this pseudo-mathematics as reasoning in the decision-process for the Haskell language, we arrive at the conundrum described in the post. Claiming mathematics as the reasoning for such errors contradicts the flagrant disregard for mathematical correctness with Monad or Num, for example. It is this contradiction with which I take severe offense.



"We have to clarify that 5 is an Int? Seriously?"

Yup, just like you do in most statically typed languages.


    (* Standard ML of New Jersey v110.72 [built: Sun May 16 15:16:12 2010] *)
    - 5;
    val it = 5 : int


    (* Objective Caml version 3.11.2 *)
    # 5;;
    - : int = 5


    // D programming language
    void main(string[] args){
       writefln(typeid(typeof(5)));
    }
    // Output: int


    // C# 4.0
    var n = 5;  // int


    // Go programming language
    var n = 5   // int


    // Welcome to Scala version 2.7.7final (OpenJDK 64-Bit Server VM, Java 1.6.0_20).
    scala> 5;
    res0: Int = 5


    // C++
    #include <iostream>
    #include <typeinfo>

    int main(){
        cout << typeid(5).name() << endl;
        return 0;
    }
    // Output: i

I see...


reikonomusha, you left one out:

    // GHCi, version 6.12.3: http://www.haskell.org/ghc/ 
    Prelude> :set +t
    Prelude> 5
    5
    it :: Integer


    :set +t


`reikonomusha`, you've caught me out: it was the old `:set +t` trick -- the last refuge of Haskelling scoundrel.

    GHCi, version 6.12.3: http://www.haskell.org/ghc/  :? for help
    Prelude> :?
    ...
    Options for ':set' and ':unset':
        +t            print type after evaluation
Of course it isn't necessary at all.

    $ ghci
    GHCi, version 6.12.3: http://www.haskell.org/ghc/  :? for help
    Loading package ghc-prim ... linking ... done.
    Loading package integer-gmp ... linking ... done.
    Loading package base ... linking ... done.
    Loading package ffi-1.0 ... linking ... done.
    Prelude> 5
    5
    Prelude> :t it
    it :: Integer
Try it at home.


    GHCi, version 6.12.1: http://www.haskell.org/ghc/  :? for help
    Loading package ghc-prim ... linking ... done.
    Loading package integer-gmp ... linking ... done.
    Loading package base ... linking ... done.
    Prelude> :t 5
    5 :: (Num t) => t
This is the sort "practicality" issue. Care to enlighten on the rules about when `(Num t) => t` resolves to `Integer`?


It will infer you mean Integer if you just evaluate it, read what it says about ":set +t". Crudely put, `:t foo` puts a question to the interpeter that is about the expression "foo" - "in what sense can this expression be taken?" -- it would be tiresome but maybe a little clearer if `:t` demanded quotes. If you just ask it what 5 or (4 + 1) or 2+2+1 is, it will say that it is 5 and assume that we were talking about an `Integer` -- the command `:t it` gives the type it is associating with the sign it is exhibiting to you as the result of calculation or evaluation, the second sign, not the one you entered.

In the type query `:t 5`, ghci doesn't have to decide or resolve anything, it gives all the possibilities, `5: Num a => a`. But if it is to evaluate the expression it needs you or it to make decisions. You can see this pretty clearly in the following:

     Prelude> :t 5
     5 :: (Num t) => t
     Prelude> 5
     5
     Prelude> :t it
     it :: Integer
     Prelude> 5 :: Float
     5.0
     Prelude> :m + Data.Ratio
     Prelude Data.Ratio> 5 :: Rational
     5 % 1
     Prelude Data.Ratio> :t it
     it :: Rational
In the first case, it decided, in the other two, I decided what type I meant.

I'm not sure I grasp everything about this, but in your example, `p [1,2,3,4,5]` ghci couldn't make its natural assumption - i.e. the one you in fact think should be it's natural assumption -- since no Monoid (in your sense) instance had been declared for its preferred case, Integer, so it backed off and demanded clarification.

You had unconsciously over-ruled the defaults you wanted. As has been pointed out, if you had realized that Integer is the name for Haskell's integer type, and Int a dirty approximation like Float, and had used it, everything would have gone as you claim to have wanted. In fact you get everything you want if you just add these lines to your module:

    instance Monoid Integer
        where
          addId = 0
          add = (+)
keeping all of the other Int stuff intact. Then in ghci can use its defaults -- the defaults you want -- and you get:

    Ok, modules loaded: Main.
    *Main> p [1,2,3,4,5]
    (5x^4 + 4x^3 + 3x^2 + 2x + 1)
    *Main> :t it
    it :: UnivariatePolynomial Integer
Though of course the unevaluated expression is given a more general type:

    *Main> :t  p [1,2,3,4,5]
    p [1,2,3,4,5] :: (Num t, Monoid t) => 
                       UnivariatePolynomial t
-- and rightly, since this very module permits an Int interpretation of the expression. In a case that goes against its defaults, I have to tell it that that's how I want the complex functional expression to be understood, before I ask for evaluation:

    *Main> p [1,2,3,4,5] :: UnivariatePolynomial Int
    (5x^4 + 4x^3 + 3x^2 + 2x + 1)
     *Main> :t it
     it :: UnivariatePolynomial Int

It is the same if you add another Monoid instance for the non-default reading of "1", e.g.

    instance Monoid Float
        where
          addId = 0
          add = (+)
This can exist side by side with the others. Then you get:

     *Main> p [1,2,3,4,5] :: UnivariatePolynomial Float
     (5.0x^4 + 4.0x^3 + 3.0x^2 + 2.0x + 1.0)
     *Main> :t it
     it :: UnivariatePolynomial Float
Note that even with three Monoid instances in play in the module, we still get what you wanted:

    *Main> p [1,2,3,4,5] 
    (5x^4 + 4x^3 + 3x^2 + 2x + 1)
    *Main> :t it
    it :: UnivariatePolynomial Integer


What are your thoughts on this behavior overall? I mean, your opinion. When do we precisely know that '5' means '5::Integer'? At which point does GHC or really Haskell in general, I assume, make this decision?


I think I don't really have a view about this, I'm accustomed to ghci's behavior from practice. It's clear there are other ways of resolving these problems, but don't they all have downsides? It does seem excellent that ghci's preferred type for "5" is Integer, rather than Int, as I think you would agree on reflection.


It's called defaulting:

http://www.haskell.org/onlinereport/decls.html#sect4.3.4

In my opinion, though it wasn't me you asked, it's an ugly workaround for Haskell's lack of proper coercion rules.


Of course we know what defaulting is. Why is it bad? I don't think I myself would even notice if it weren't there, since I write type signatures before I write the value expressions for them. I guess it is convenient if you are noodling around in ghci. With -XOverloadedStrings you get the same effect:

    ghci -XOverloadedStrings
    Prelude> :m +Data.Text
    Prelude Data.Text> "cargo cult"
    "cargo cult"
    Prelude Data.Text> :t it
    it :: String
    Prelude Data.Text> "cargo cult" :: Text
    "cargo cult"
    Prelude Data.Text> :t it
    it :: Text
It defaults to String. What's wrong with this exactly? A general coercion, understood as something different from a function (pack, unpack), would have to coerce between two types of wildly different structures. One is a list type [Char] that you can map over and into from any kind of list you like - and the user is forever employing this fact - the other is totally different.

It is different from other systems, but doesn't that just mean you have to learn something different, which is of course annoying until it becomes second nature. There doesn't seem to be any legitimate technical objection here, or am I wrong?


Of course we know what defaulting is. Why is it bad?

I wonder who is that we you refer to. Is applicative a Bourbaki-style pseudonym for a group of aspiring Haskell hackers?

I never said it is bad, it looks like a useful and convenient mechanism. My complaint is about its non-genericity. Indeed, it is limited to numeric literals only; even to reuse it for string literals, you need a compiler extension.

A general coercion, understood as something different from a function (pack, unpack), would have to coerce between two types of wildly different structures.

By general coercion, I mean implicit conversion not limited to numeric literals (or string literals, with an extension) only.


By "we" I meant reikonomusha and myself, as the context made plain. I don't see how calling something an ugly hack isn't a form of calling it bad, but never mind.

Numeric and string literals are all that were ever under discussion. `OverloadedStrings` is a language extension, not a compiler extension; it is a candidate for inclusion in later specifications, not in the new Haskell 2010. The Ghc supports it with a language pragma. That it, and the associated IsString class, didn't exist in Haskell 98, the previous specification, is obviously due to the fact that there was not widespread use of types like ByteString and Text for handling text; if there had been it would have been, surely; the idea is pretty straightforward.


Well, in many languages you don't have to declare it: it is inferred, starting from some basic assumptions on what the string '5' is probably intended to mean in the vast majority of cases. However, that is besides your point: even if it's taken care of in some other way, it's still necessary to add a type to the string '5' you just typed in an editor. This also holds in dynamically typed languages.


As an aside, note that this isn't the recommended way to make Int an instance of Monoid, since there is another, equally valid definition using '1' and '*'. The recommended way is to make a newtype. In fact, the Sum and Product newtypes are already defined in Data.Monoid for this reason.


Am I the only one seeing this?

"Well, we have a list of integers [Math Processing Error]. And [Math Processing Error] for monoid [Math Processing Error]. So [Math Processing Error] since [Math Processing Error] is a monoid, yes? Of course."


Looks like the author marked up mathematical notation using this:

   http://www.mathjax.org/
...which must be broken for your environment.


The whole post is about math processing errors.


The syntax highlighting on this blog creates some yellow-on-white text that is difficult to read or even look at.


does this guy not know how to use type declarations?

[1,2,3,4,5] :: [Int] problem solved!


He knows, as it is clearly visible in his article. His point is, he should not need to.


Actually, you don't need to in most real programs. You usually use the elements of a list with some function that makes it clear what type they have.

In GHCi (the interpreter) this doesn't happen. It has some defaulting rules to alleviate this, but I don't quite understand them, so I can't say why they didn't kick in in this case.


The thing with Haskell is that it doesn't automatically coerce types the way other languages do. It uses Hindley-Milner type inference up front if you don't specify your types, but this is far from foolproof. Once it's decided you're using type Foo, you're stuck. You need explicit typing in many cases.

One of the first things you learn as a Haskell programmer is to treat type inference with suspicion.


Languages like SML and OCaml also use this sort of type inference, but you tend to not need type annotations in those languages except in very special cases. I think this is also fairly true of Haskell -- type inference works in 99% of the cases, and explicit type annotations are just a stylistic thing.


Most of the time you're right. But I've done a fair bit of tinkering with Haskell's OpenGL library. Trying to get GHC to differentiate between a Float and a GLfloat inside a Vector is pretty much impossible without resorting to explicit typing. This definitely qualifies as a special case, but it's cropped up often enough as to make me a little gun shy. You don't need to explicitly type everything, just enough to give the compiler a credible hint as to what you're trying to do -- such as the last term in your vector.

The sad thing about the original article is that the writer is throwing his hands up and raging over what is really a small implementation hiccup. I kind of like that the compiler withholds judgment on whether '5' is a Float, Int, or Integer. It means that I no longer need to type ".0" after every Float.


This is a property of many statically typed languages, and Haskell isn't known for being one of the more forgiving languages in that family.

I appreciate that his goals were more mathematically oriented than not, but surely he would've seen something like this coming?




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: