Haskell is nicer than Scheme
I’m taking a class on DSLs this semester, and the first assignment was to write some trivial programs in Scheme. Haskell is so much prettier. I mean, just compare (lambda (x) (* x x)) to \x->x*x (the square function).
Then, of course, there’s the function to sum a list. In Scheme:
(define (sum lst)
(if (null? lst)
0
(+ (car lst) (sum (cdr lst)))))
Did you follow the closing parentheses? In Haskell, pattern matching makes this extremely convenient:
sum [] = 0 sum (x:xs) = x + sum xs
Then there are the benefits of lazy evaluation. In Scheme, to be efficient, map has to be written like: (stolen from guile’s srfi-1)
(define (map1 f ls)
(if (null? ls)
ls
(let ((ret (list (f (car ls)))))
(let lp ((ls (cdr ls)) (p ret)) ; tail pointer
(if (null? ls)
ret
(begin
(set-cdr! p (list (f (car ls))))
(lp (cdr ls) (cdr p))))))))
while in Haskell, it can be written naturally as
map f [] = [] map f (x:xs) = f x : map f xs
This has to do with lazy evaluation, since the remainder of the Haskell list isn’t evaluated until it is needed and so doesn’t require any extra stack space. It also allows us to map a function over an infinite list, which would run forever in Scheme. To be fair, lazy evaluation has its costs: if we actually need to evaluate the whole list, Haskell’s map will run slower than Scheme’s.
Some of my favorite uses of Haskell involve infinite lists:
fibonacci = 0 : 1 : zipWith (+) fibonacci (tail fibonacci) take 20 fibonacci == [0,1,1,2,3,5,8,13,21,34,55,89, 144,233,377,610,987,1597,2584,4181] — Using the Sieve of Eratosthenes sieve (p:ns) = p : sieve [n | n <- ns, n `mod` p /= 0] primes = sieve [2..] take 20 primes == [2,3,5,7,11,13,17,19,23,29,31,37, 41,43,47,53,59,61,67,71]
Of course, neither of these are possible in Scheme.
Recall the use of let in the definition of map: (let ((ret (list (f (car ls))))) …). In Haskell, we have syntax to avoid the excessive use of parentheses:
let ret = [f,head ls] in …
Scheme does have a few benefits. Because it is dynamically typed, you can apply a function to any number of arguments: (zip '(1 2 3) '(2 3 4) '(3 4 5) '(4 5 6)) == ((1 2 3 4) (2 3 4 5) (3 4 5 6)). In Haskell, you have to give the number of arguments explicitly: zip4 [1,2,3] [2,3,4] [3,4,5] [4,5,6] == [(1,2,3,4),(2,3,4,5),(3,4,5,6)]. On the other hand, all of my complaints (which I haven’t published yet) about dynamic typing still apply.

One advantage of Scheme is the sexpr syntax. This isn’t just useful for macros: it allows you to give a concise representation of just about any kind of data you can imagine. For example, a nice trick in Lispy languages is to write a simple procedure for converting lists such as ‘(p ((class . “foo”)) “Blah blah”) into nicely formatted HTML. This makes it really easy to generate HTML content. In Haskell you have to take a more direct approach, writing a combinator library which allows you to combine HTML generating functions. The combinator library approach often works very well, but sometimes it’s nice to have a real solid data structure to manipulate. Especially if you want to store this data in a file rather than hard-coding it into your program. Of course, you could define a datatype for HTML in Haskell, but it would have a rather less pleasing syntax and there would be no pre-defined functions with which to manipulate it.
There’s an interesting relationship between Scheme’s type system (or lack thereof) and its eager evaluation strategy. You’d think that Scheme (having no static type system) ought to be able to express any function expressible in lambda calculus, and therefore have something of an advantage over Haskell. However, this often is not the case in practice. For example, the Y combinator cannot be given its standard lambda calculus definition in Haskell because it has an infinite type, but in Scheme the standard definition leads to nontermination.
Comment by Alex — 5/27/2005 @ 11:58 am UTC
If we want to follow the sexpr style in Haskell, we can write your example as
("p", [("class","foo")], "Blah blah"). The only advantage Scheme gives us now is thesymboltype, which lets us drop some quotation marks. If we translate it to more idiomatic (and type-safe) Haskell, we could getp [class "foo"] "Blah blah", givenpandclassas helper functions. The helper functions could generate the HTML datatype just as easily as raw HTML. The lack of pre-defined functions is a problem, but the Scrap Your Boilerplate project is working on it.In summary, Haskell can handle s-expressions, using tuples to handle non-uniform lists, but it allows you to go beyond them when you want.
Comment by Jeffrey Yasskin — 5/27/2005 @ 3:56 pm UTC
Your first suggestion doesn’t really work. If you use tuples to handle the nesting, you have no way of iterating over the elements of a tuple (and this is pretty much essential if you want to actually do something with the data). You can’t use nested tuples as a replacement for nested lists.
As I said, you can create a custom HTML datatype, and if you like you can create helper functions for creating instances of this type. However, under this approach you either need to have lots of quoting and other line noise (e.g. (makeTag “foo” [(”class”, “foo”)])), or you have to define separate functions for each tag, which is rather a waste of time (although I suppose you could just do it for 10 or 20 of the most common tags). Still, the bolierplate thingy you link to is very interesting and would certainly make things easier.
Comment by Alex — 5/27/2005 @ 4:32 pm UTC
Hmm, reading through my last comment it sounds a bit stronger than I meant it to. I basically take your point that there are pretty good Haskell alternatives to s-expr syntax.
Comment by Alex — 5/27/2005 @ 4:36 pm UTC
It could be argued that this Scheme version of zip is actually just a heterogeneous transpose. And, of course, transpose can easily be defined in Haskell as well, albeit homogeneously. (Generalizing it to work on hetereogeneous list as well, may very well be possible, but, admittedly, this will surely take some type class trickery. Extensions that add support for generic programming, for instance through structural polymorphism, like Generic Haskell, allow you to write binary zips that operate on a variety of types, not just lists and pairs.)
Comment by Stefan Holdermans — 9/29/2005 @ 2:06 am UTC
I don’t think this comparison of guile’s map1 to haskell’s map is entirely fair, since in scheme I would just directly convert the purely functional map primitive into a tail recursive version without using set-car! and such.
eg. using
(define (map f lst) (let loop ((lst lst) (res '())) (if (null? lst) (reverse res) (loop (cdr lst) (cons (f (car lst)) res)))))instead of:
(define (map f lst) (if (null? lst) lst (cons (f (car lst)) (map f (cdr lst)))))I know it’s non idiomatic, but I would also consider defining lazy-map in scheme, (I use a purely functional implementation of delay and force) and I would be open to using foldr to implement map, one of those would be preferable.
I would be interested to learn more about sexpr-like prefix syntax in haskell, maybe I am crazy but I really don’t care for complex syntaxes.
Comment by Bob — 7/25/2006 @ 6:31 pm UTC