We have all experienced it: you make an innocent statement about a programming language online, and suddenly you are mired in a flamewar. You are labeled a fanboy and flamebait.
Why is choice of programming language such a touchy issue? It's like once you enter into certain areas of the topic of programming languages*, you are instantly transported to a land of Black and White, good and bad, binary logic, of bitter conflict and rivalries between factions.
You can explain why people are so fighty with cognitive dissonance. Learning the intricacies of a programming language takes time and effort. It requires a lot of trial and error, along with reframing your fundamental understanding of how to approach a problem. In order to justify such an struggle, programmers raise the level of importance of the particular features of that programming language, particularly the foundational ones. So any statement that seems to abase the language is seen as an insult to your own effort and thought process. Programming language is a personal issue.
If you compound this with the impersonal and anonymous magnifier of the internet, where each past statement is indelibly marked alongside other comments, and people tending to post before they read all of them, and those incendiary one-line comments tend to be read more, and all of the other incitements of the expanding spiral of discussion threads, you get something like you see online all the time.
Unfortunately, there is nothing to do about it. It appears that,
like religious or political disputes, they are here to stay. It's
the price you pay for being online.
Clojure + Terracotta = Yeah, Baby!
I just stumbled across this effort to get Clojure running
on Terracotta, a JVM
clustering infrastructure. Terracotta gives your distributed
JVM's a shared heap, so the possibilities of distributing
computation are immense. This kind of thing really gets me going.
All I need now is a room full of server racks.
Recently, I posted an article about why I didn't like Haskell's type checker (or really, any of the other type checkers I know of) in theory. I sensed that it got a lot of flack. It was an idea I had been pinging around in my head for a while. It was not meant as a criticism against Haskell, but a statement of my preference.
Someone suggested to me that I post specific instances of things I would like to be able to say in Haskell, but cannot. It appealed to me. I don't know why. Perhaps it was his patience, which seems to be a rare commodity on the internet. Instead of insulting me for my mistaken views, he actually tried to explain my errors to me. Perhaps, if I gave the examples he requested, I would be shown that I really could do what I wanted. Maybe he wouldn't make me feel like a douche for not reading a book on Haskell just to write one blog post about it. This post is for him.
I will give a couple of examples of things in Haskell that violate my own personal taste. They are both examples of failures of the type system to encompass my intent.
So, here is the first thing that I remember trying to do in Haskell, before realizing it was impossible. It is something that is explicitly disallowed (having values of different types in the same list). My understanding of what Haskell meant by type was faulty. I did not get the difference between a type and a type class.
--List Polymorphism
--Note that I have been told that this is possible in some
--controversial extension
sum [1.5, 2, 3, 5.9]
I thought that the type of the list would
be [Num]
, not Num
a => [a]
. I was wrong, but I don't think I should have
been. Addition is blind to what type of number it is, and summing
should be, too.
There are some standard workarounds for the previous example. The next example shows perhaps a more practical reason for wanting lists of different types. Unfortunately, I can't even express the broken Haskell code. I've tried. In Haskell, how do you express a function of n arguments, where n is unknown? So I'll do it in Clojure. I apologize if it makes the conceptual translation difficult.
(defn mymap [fn & lsts]
(let [ls (map first lsts)]
(if (some nil? ls)
nil
(lazy-seq
(cons (apply fn ls) (apply mymap fn (map rest lsts)))))))
It's a function that takes a function fn and n lists of arguments. fn takes n arguments. A new list is created whose members are the application of fn to the corresponding elements of the lists of arguments.*
The best way I found in Haskell was to define several different map functions. There's map1, for functions of one argument, map2 for functions of 2 arguments, etc. Each carries basically the same functionality. If Haskell's type system is so advanced, why can it not generalize the pattern? If it is not that advanced, why would I want to use it?
Maybe you can do this in Haskell. I don't know. I tried a few approaches and decided it was fundamentally impossible. I don't know Haskell very well, so please correct me if I'm wrong.
But in the end, it's just an example. Can Haskell represent a
generalized function type? Maybe it can. But I can always find
something that Haskell's type checker (or any type system) can't
deal with. Without a trapdoor, I will always hit a wall of what I
can express.
I've gotten two very thoughtful comments from my readers. And, well, it turns out you can do both of the things I described above. My mistake.
I also got a pretty good explanation of why you don't want to do it in Haskell. Currying is so common, you need some way of saying how many arguments to consume. So variable arguments are generally avoided.
I am starting to reshape my ideas about Haskell. I have always considered it at the forefront of functional programming. I just never wanted to be at the forefront of functional programming.
Lisps still have something over pure functional languages! You probably know what it is. I daresay that linguistic dynamism, that is, being able to compile new code from arbitrary locations is pretty darn cool. But the way things are going, you might be able to do that in Haskell, too.
Nowadays we have thousands of times the processing power, memory and storage yet, from the user's perspective, software for the desktop, web and mobile seems to run slower than it should, or used to.
While it does start a bit backwards-looking*, this article wraps it up with a great positive, futurist spin: mobile devices are exploding in Africa and will revive a focus on space and time requirements.
I think smaller memory footprint and faster speed are truly needed right now, though I think looking back to assembly is the wrong answer. Temporarily, assembly will be very useful in the developing world until a better solution is found. Speed and power consumption are critical concerns. But we know so much more about computer science than we used to. We should look for solutions that enable aggressive computational abstraction without the bloat. And we might find (as some already have) that computers can write faster, smaller code than any person ever could.
I'm always searching for a good way to explain what you can do with Lisp that you can't do with most other languages. Joe Marshall has a compelling story, from a skeptic's point of view, for what makes functional programming different. There are multiple parts that trace his way through a course at MIT and toward a love of lisp.
From the post:
I was floored. Here we take the simple, straightforward definition of the derivative of a function. Type it in with essentially no modification (we're a little cavalier about dropping the limit and just defining dx to be a 'reasonably small number') and suddenly the computer knew how to do calculus! This was serious magic. I had never seen anything like it before.
Good stuff. It made me
revisit Structure
and Interpretation of Computer Programs, an
all-time classic.
C's type system has been criticized for being flawed. It certainly is not as logical as Haskell's. I am not going to argue with that. But C's type system has (at least) one redeeming quality: an escape hatch.
When the Creators brought C into existence, they endowed it with a few datatypes and a way to make some new ones. But by letting any pointer be cast to (void *), they blessed C with the unifying principle that makes it so useful*. That principle is eloquently stated in three words:
Bits is bits.
Type systems are right because they impose a rigid view of a
domain (otherwise known as a model) that can then be reasoned
about in a structured and reliable way. Well defined types define
an ordered space in which the programmer can solve a problem.
Outside of that space lies chaos.
In Programming as an Experience: The Inspiration for Self, the authors discuss successes and failures in bringing uniformity and minimalism to the language. One such failure is that data slots and method slots have different semantics.
They discuss different options for unifying method semantics. All of the options they mentioned were disagreeable with the authors, since they require methods to be treated fundamentally differently from data. Any attempt to make a method (that is, runnable code) act like data (that is, values) breaks the unity.
What is weird to me is that they do not discuss making data act like code. Why do they not make all slots act like method slots?* That is the idea of getter and setter methods. It is so simple and uniform and solves the problem. Why did they not try this? Or did they?
Why is it that people unfailingly want to put words in my mouth on the Internet?
I suppose it happens all the time in everyday speech. Or behind my back. But on the Internet, everything you say is visible to all. There is no behind your back. And seeing it in writing, permanently attached to your site, that just sucks. It is one of the main reasons I have disabled comments*.
I (often) write what I believe. I have written articles I am not proud of. Either because I did not do enough research or I wrote what I thought others wanted to hear. But you catch the same flack for regretable articles as you do for the really personal ones. But the personal pieces--the ones that express my views, whether right or wrong--at least meant something to me when I wrote them. They might actually lend themselves to communication on a deeper level. But, invariably, those are the ones people call "flame bait".
I guess that means I should just keep writing what I want. It is
more enjoyable, anyway.
Haskell's type system disallows two broad classes of typing problems. It does not allow what it can prove to be incorrect (for example assigning a String value to a Number variable). That makes sense. It also does not allow anything that it cannot prove correct. That also makes sense, though it is somewhat annoying when you can see that it could be correct. This problem comes down to the issue of provability.
Haskell has a restricted inference engine to deduce the type of values. It is restricted to reasoning somewhere below first order logic. There are definitely things you cannot prove without at least first order logic*. So Haskell's type engine cannot prove every type inference I can know is correct. So it just blatantly limits what you can say.
If it had a more powerful inference engine, one that was at least as powerful as first order logic, it would not even know what it could not prove. Such is the problem with powerful logics*. That means that there are typed expressions you could state that Haskell would not be able to prove correct (or incorrect). And what is worse, Haskell would not even know that it could not prove them. So Haskell must be restricted to a lower-order logic.
At least, that's what the type theorists say. Haskell will do
lots of type inference work for you, as long as you restrict your
type usage to a very limited range. And I choose not to
live with that restriction.
Thanks goes to Don Stewart for correcting a blatant error of mine and revealing once again my near total ignorance.
I said above that Haskell's type inference was hovering below first-order logic, when in fact, according to Don Stewart, it is equivalent to second order logic. I stand corrected.
*Comparative analysis is one. Typing style (static versus dynamic) is another.
*There is absolutely no static type safety here.
*Remember the good-old-days when we had 6k of memory? Yeah, that was great.
*And so annoyingly dangerous!
*See the id object system for an example of creating a prototype-based language with unified semantics for data and methods.
*I have gotten many enlightening comments as well as intolerable idiocy. It was not an easy decision.
*For example, the famous example argument: Socrates is a man. All men are mortal. Therefore, Socrates is mortal
*Google Goedel's Incompleteness Theorem