Haskell and the "foul stench of cargo cult mathematics"
symbo1ics.com
symbo1ics.com
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 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.
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.
[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.Besides, now we've all got a good excuse for why we've not learned Haskell yet: the type system is like so broken ;)
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.
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.
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).But nobody said that Haskell's type system is without limitations, right?
"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."
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.
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.
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.]
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... // GHCi, version 6.12.3: http://www.haskell.org/ghc/
Prelude> :set +t
Prelude> 5
5
it :: Integer :set +t 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`?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 Integerhttp://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.
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?
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.
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, 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."
http://www.mathjax.org/
...which must be broken for your environment.[1,2,3,4,5] :: [Int] problem solved!
I appreciate that his goals were more mathematically oriented than not, but surely he would've seen something like this coming?
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.
One of the first things you learn as a Haskell programmer is to treat type inference with suspicion.
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.