
Haskell and the "foul stench of cargo cult mathematics" - fogus
http://symbo1ics.com/blog/?p=788
======
jerf
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.)

~~~
xi
_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.

~~~
jerf
"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.

------
contextfree
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.

------
jrockway
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.

~~~
Zaak
> 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.

~~~
thesz
>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).

~~~
Zaak
(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.

------
dfranke
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>

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

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

~~~
applicative
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.

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

~~~
gammarator
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."

~~~
Confusion
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.

~~~
gammarator
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.]

~~~
applicative
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.

------
llimllib
Cached:
[http://webcache.googleusercontent.com/search?hl=en&safe=...](http://webcache.googleusercontent.com/search?hl=en&safe=off&biw=912&bih=765&q=cache%3Ahttp%3A%2F%2Fsymbo1ics.com%2Fblog%2F%3Fp%3D788&aq=f&aqi=&aql=&oq=&gs_rfai=)

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

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

~~~
reikonomusha

        (* 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...

~~~
applicative
reikonomusha, you left one out:

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

~~~
reikonomusha

        :set +t

~~~
applicative
`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.

~~~
reikonomusha

        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`?

~~~
applicative
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

~~~
reikonomusha
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?

~~~
xi
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.

~~~
applicative
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?

~~~
xi
_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.

~~~
applicative
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.

------
hesselink
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.

------
Aaronontheweb
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."

~~~
pohl
Looks like the author marked up mathematical notation using this:

    
    
       http://www.mathjax.org/
    

...which must be broken for your environment.

~~~
MrMan
The whole post is about math processing errors.

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

------
notallama
does this guy not know how to use type declarations?

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

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

~~~
hesselink
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.

~~~
rtperson
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.

~~~
jwatzman
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.

~~~
rtperson
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.

