« home

Haskell for Racketeers

01 January 2017

My primary experience with functional programming is my college’s CS151 course, which is taught in Typed Racket. Recently, I’ve done a bit of exploring into Haskell; so far, I’ve read a bit of Learn You A Haskell, and I’ve reimplemented some of CS151’s homework assignments in Haskell. It’s been an interesting experience, so I’ve written up my initial thoughts on the differences between the two languages. I’m planning on turning this into a series as I learn more Haskell, so stay tuned!.

Disclaimer: this post will contain solutions to some of the problems from UChicago’s 2016 winter-quarter CS151. If you are in a future version of this class, please click away.

Season 1: Homebrew Peano Numbers

One of our assignments was to write a simple implementation of Peano numbers, complete with conversion, addition, saturating subtraction, multiplication, and comparison functions. But before we do any of those, let’s first define the type we’ll be working with. In Racket:

(define-type Nat (U 'Zero Suc))
(define-struct Suc ([prev : Nat]))

And in Haskell:

data Nat = Zero | Suc Nat deriving (Show, Eq)

Conversions

First, let’s look at the simple conversion functions between our Nats and regular integer representations. In Racket:

(: nat->num : Nat -> Integer)
(define (nat->num n) (cond
  [(Suc? n) (+ 1 (nat->num (Suc-prev n)))]
  [else 0]))

(: num->nat : Integer -> Nat)
(define (num->nat n) (cond
  [(not (zero? n)) (Suc (num->nat (sub1 n)))]
  [else 'Zero]))

And in Haskell:

nat_to_num :: Nat -> Natural
nat_to_num Zero = 0
nat_to_num (Suc n) = 1 + nat_to_num n

num_to_nat :: Natural -> Nat
num_to_nat 0 = Zero
num_to_nat n = Suc (num_to_nat (n - 1))

Easy enough. These were the first two functions I wrote in Haskell, so a bunch of things immediately jumped out at me.

  1. I love how you can just have your base case be an additional line. I like how it’s analogous to solving a system of equations. Very cool syntax, and probably much more powerful than I know right now. Also eliminates lots of conds and matches in Racket that only check one edge case.
  2. Why does Haskell have such restrictive rules on symbols in function names? From the Haskell wiki:

Variable identifiers start with a lowercase letter, constructor identifiers with an uppercase letter and both can contain underscores, single quotes, letters and digits. Operators are formed from one or more of '!#$%&\*+./<=>?@\^|-~'. Constructors can be operator names, so long as they start with a ‘:’ (e.g., :+ for Data.Complex).

That seems markedly worse than Racket’s “you can call your function A+->=_*^% for all I care” approach. 3. I love Racket’s treatment of symbols (i.e. 'Zero). There doesn’t seem to be an analog for that in Haskell.

Arithmetic

Let’s examine the addition and multiplication functions. In Racket:

(: nat+ : Nat Nat -> Nat)
(define (nat+ i j) (match* (i j)
  [(_ 'Zero) i]
  [('Zero _) j]
  [((Suc _) (Suc jprev)) (nat+ (Suc i) jprev)]))

(: nat* : Nat Nat -> Nat)
(define (nat* i j) (match* (i j)
  [('Zero _) 'Zero]
  [(_ 'Zero) 'Zero]
  [((Suc _) (Suc jprev)) (nat+ i (nat* i jprev))]))

And in Haskell:

nat_add :: Nat -> Nat -> Nat
nat_add n1 Zero = n1
nat_add Zero n2 = n2
nat_add n1 (Suc n2) = Suc (nat_add n1 n2)

nat_prod :: Nat -> Nat -> Nat
nat_prod _ Zero = Zero
nat_prod Zero _ = Zero
nat_prod n1 (Suc n2) = nat_add n1 (nat_prod n1 n2)

Again, my Racket code translates easily to Haskell. I’m again struck by how much easier to read the Haskell code is. I haven’t yet had to indent anything, which is impressive. I just hope that the whole spaces-as-function-application thing doesn’t become ambiguous down the line, but I feel like it will.

Parting notes

Is it too much to ask for either of these languages to have a normal comment string? Racket’s ; I kinda got used to, but it’s still clunky. There’s also no way to block-comment, which is a bit annoying, but not too bad with vim-commentary. But Haskell, why? Oh god, why? -- is just so unreadable. Good thing I have vim set to italicize and dim the colors on all comments, or I’d gloss right over them. And the block comments too…

Okay, /rant. For now, the conclusion is this: if you’re coming from really any Lisp dialect and you want to learn Haskell, go for it. The syntax is bound to be weird, but you’ll get used to it, and everything should be on conceptually familiar ground, at least to start. Also, Learn You A Haskell is great.

Alright, I guess that’s it for season 1! Roll credits! Season 1, boom!

Correction: 07 February 2017

My friend Isha informed me that Racket does in fact have block comments with #| ... |#. However, I don’t think that detracts from my point of both languages having terrible comment strings.