Haskell and Programming as Mathematics

Some thoughts about the language Haskell

Posted by 02/27/2007

Lately I've been trying to learn the <a href="http://haskell.org">Haskell</a> language because it meets several criteria I think all languages should have.

Mathematics

I've often thought that programming should be more similar to mathematics than it is now. Even in so far as notation. I should be able to use λ instead of the word lambda or the ∑ sign instead of some kind of constructed loop. Of course you can't type a mathematical symbol on the keyboard. That's one problem. The other problem is that they aren't supported by languages.

These are some symbols I would like to be able to use:

  • ƒ (function)
  • λ (lambda)
  • ∑ summation)
  • ⊂ (subset of)
  • ∴ (therefore)

Lately I've been looking at Haskell Which seems to have the underlying construct necessary to do what I have in mind - even if it doesn't work that way now. I'm just beginning to look at the language, but it looks like \x is the way to do lambda x:. But looking at the language and how it's discussed it looks friendlier to the notion of using things like the summation symbol then, say, Python or Ruby. Also, function composition using the ∘ symbol.

Functional Languages

It may be the functional languages in general are friendlier to the concept of mathematical notation, rather then specifically Haskell. O'Caml for instance. I looked at the following languages:

The syntax of Oz looks the best to my taste - and Io second. But I've ended up spending the most time with Haskell.

Problems Encountered

IO
I could not get this to compile on Windows™. And no binaries are provided. I know there is a Visual Studio project file - so I should be able to compile it myself, but it doesn't compile without knowledgeable modifications - knowledge I don't have.
Oz
I could not find any ability to connect to a database. Also the front page said "Version 1.3.2 released on June 15, 2006!" - which was not actually encouraging.
O'Caml
There is a disconnect between MingW version instead of the MSVC version when installing 3rd party utilities. I think there should be a standard build system to handle both scenarios, because I've had problems with both Ruby and Python in this regard in the past. So this killed my venture into O'Caml for now.
Erlang
Installed fine. Ran fine. I was impressed with it. You can write code quickly and get running. The only problem was finding a GUI toolikit, like WxWindows or Gtk to work on Windows.

Often the problem is Windows™

Actually, I'm not too crazy about Windows™. If I were king of the world I would make everyone stop using it. But everyone I know (that is not a programmer) uses it - and I myself use it because of my laptop - so despite my own personal feelings I still have to make sure what I use is workable on Windows™.

The Basic Problem

The overriding problem for me with a lot of these new languages is that I can't "get sh*t done" i.e. I can't use them to do exactly what I'm doing now. So all of my learning is perfunctory. You never learn a language until you get totally frustrated with it. And I cannot build data driven web and GUI applications with IO, O'Caml, Erlang, or Oz. Some of them come close, some of them seem like they could work with effort. But after fuddling around with all these languages for a week or so, only Haskell did what I wanted, which was to be able to connect to PostgreSQL and run GTK. All the other failed to deliver in one way or the other.

That's not to say it wasn't close. O'Caml and Erlang are very close, and Erlang could possibly be better for anything involving a server (such as a web server). But I wanted to put together a GUI quickly - and it didn't give me that. And while I was installing interpreters, finding libraries, running samples, reading tutorials etc... for all of these languages, I began formulating what I wanted from a language. I came up with the following incomplete list:

Rob's Language Rules

  1. It should always be possible to compile to a binary - so I can distribute what I write without requiring installation of something else
  2. It should have something resembling literate programming - the ability to write text in the code and output those comments to another format
  3. It should be possible to create unit tests in code, or code that tests itself. That way the tests move and change with the code so there is not such a disconnect between the two
  4. It should have a command line interface of some sort. No purely visual coding like Visual Basic or executable UML or somesuch
  5. It should provide the ability to run interactive code (REPL)
  6. It should provide a simple way to build and distribute (a packaging system)
  7. It should have a fairly substantial library of useful things
  8. It should be as easy to use on Windows™ as a Posix based system
  9. It should offer optional static typing - so that CPU intensive actions can be compiled down to efficient machine code. This would elimate the need to drop down to C just for performance sake
  10. At the same time, it should have a simple Foreign Function Interface so that it is easy to call DLLs - or easy to drop down to C for performance sake (despite or instead of #9)
  11. It should minimize complexity at every turn. It should be easy to get started and get things done without learning a bunch of new concepts
  12. It should be very easy to install libraries - and add those libraries to the languages path. You shouldn't have to create an environmental variable to get started. This is an advanced option thing (see #11)

Haskell meets a lot of these requirements, although not all of them. It compiles to native code, has literate programming, encourages testable units, has a command line interface, has a REPL interface, has a distribution and build system (Cabal), has an easy FFI, has a good standard library, and a lot of stuff available on the internet. The only trouble I see with Haskell is that it's a little abstruse. It requires some inside knowledge before you can really do anything. For example, the typical hello world program in Python is this:

  print "hello, world"
  

You can create a new text file, type that text, save it as hello.py, run it, and something happens.

The Haskell equivalent file (Hello.hs) might be like this:

    main = putStrLn "hello, world"
  
Although we've sort of glossed over some concepts doing that. It might also be like this:
    module Main where
    main :: IO()
    main = putStrLn "hello, world"
which is starting to look suspiciously like the old Java chestnut
  public class Hello {
      public static void main (String args[]) {
         System.out.println("Hello, World");
      }
  }
The problem with that is it requires understanding 4 or 5 concepts just to get started. At the same time, Java doesn't have any kind of interpreter by default - so in the Haskell interpreter you can type
    putStrLn "Hello, World"
   
And see results - without needing to understand modules or Monads or anything.

And speaking of print. If you think about it print in Python is sort of an anachronism. It's not 'printing' - like you 'print' a receipt, it's outputting. But Ruby's puts and Haskell's putStrLn are a little bit of programmer shorthand. I like Scheme's display idea. In scheme you call (display "Hello, World") and the default output port is the console. But you can change that to a file and the same display code outputs to a file. This is great.

There is probably a way to do the same thing with Haskell, but I don't know how. I've just read some tutorials and written very little code. But what I've read is very interesting.

Pattern Matching

One interesting concept in Haskell is the idea of 'Pattern matching' which is where you can write base cases for a function as separate 'special cases' of that function. The fibonacci series is a perfect example. If you could do pattern matching in python you could do this:

      def fib(0) = 1
      def fib(1) = 1
      def fib(x):
          fib(x-1) + fib(x-2) 
   

Which is a clear way of writing that. It's also a stupid example but I see some potential in it - without needing to see an actually useful example.

Back to Haskore

So with this new interest in Haskell I took another look at Haskore - which I looked at briefly in my rather long-winded post In Search of Music Software

The best way to explain Haskore it to show a sample. Here is a very short, boring, minimal song:

 1 module Main where
 2 
 3 import           Haskore.Melody.Standard   as Melody
 4 import           Haskore.Music.GeneralMIDI as MidiMusic
 5 import qualified Haskore.Music             as Music
 6 import qualified Haskore.Basic.Drum        as Drum
 7 import qualified Haskore.Basic.Chord       as Chord
 8 import           Haskore.Basic.Pitch (Class(..))
 9 import qualified Haskore.Interface.MIDI.Render       as Render
10 import qualified Haskore.Interface.MIDI.General      as GeneralMidi
11 import qualified Haskore.Interface.MIDI.Save         as SaveMidi
12 
13 
14 bassdrum, snare, hihat :: Dur -> MidiMusic.T
15 bassdrum durat = Drum.toMusic MidiMusic.AcousticBassDrum durat [Velocity 2]
16 snare    durat = Drum.toMusic MidiMusic.AcousticSnare    durat [Velocity 1]
17 hihat    durat = Drum.toMusic MidiMusic.ClosedHiHat        durat [Velocity 1.5]
18 
19 vel :: [NoteAttribute]
20 vel = [Velocity 1.5]
21 
22 progression1 :: [Chord.Generic [Melody.NoteAttribute]]
23 progression1 = [
24        Chord.generic E Chord.minorInt           dhn vel,
25        Chord.generic D Chord.minorInt           dhn vel,
26        Chord.generic C Chord.majorInt           dhn vel,
27        Chord.generic D Chord.minorInt           dhn vel
28      ]
29 
30 chords = line (map chord
31             (Chord.leastVaryingInversions
32                ((1,C),(1,C))
33                (progression1)))
34 
35 rhythm :: MidiMusic.T
36 rhythm =
37    line [bassdrum en =:= hihat en, qnr,
38          snare sn =:= hihat sn,    enr,
39          bassdrum sn, enr,
40          bassdrum en =:= hihat en, qnr,
41          snare en =:= hihat en,    qnr]
42 
43 chordProgression = (MidiMusic.fromStdMelody MidiMusic.AcousticGrandPiano chords)
44 
45 song :: MidiMusic.T
46 song = MidiMusic.changeTempo 3.2 $
47    Music.line (replicate 8 chordProgression) =:=
48    Music.line (replicate 16 rhythm)
49 
50 
51 main :: IO ()
52 main = SaveMidi.toFile "test.mid" (Render.generalMidiDeflt song)
53 

There is a lot to explain there. But it reads fairly well. First off - Haskell is strongly typed. So that means everytime I create a variable or a function I have to specify what kind of thing it is. I am trying to create a simple drum track and chord progression. So first I have to set up the drums:

14 bassdrum, snare, hihat :: Dur -> MidiMusic.T
15 bassdrum durat = Drum.toMusic MidiMusic.AcousticBassDrum durat [Velocity 2]
16 snare    durat = Drum.toMusic MidiMusic.AcousticSnare    durat [Velocity 1]
17 hihat    durat = Drum.toMusic MidiMusic.ClosedHiHat      durat [Velocity 1.5]

and later

35 rhythm :: MidiMusic.T
36 rhythm =
37    line [bassdrum en =:= hihat en, qnr,
38          snare sn =:= hihat sn,    enr,
39          bassdrum sn, enr,
40          bassdrum en =:= hihat en, qnr,
41          snare en =:= hihat en,    qnr]

The mysterious qnr, en stuff is just shorthand for note lengths or Duration. en stands for eight-note, sn stands for sixteenth-note and qnr stands for quarter-note rest. You get the idea. One cool thing about Haskell is that you can define your own operators. So here we see the the library provides a =:= operator which denotes notes should be played at the same time. The [] operator is used for arrays. So this short little line of code is creating an array of notes - and calling the line function to combine them all as a unit called rhythm.

It is not easy discerning the type system from the documentation. Partly because Haskell itself is not intuitively obvious. There is something called Haddock that is the JavaDoc for Haskell. It's okay. But it helps to know the language first

Next I wanted to chords so I make a progression:

19 vel :: [NoteAttribute]
20 vel = [Velocity 1.5]
21 
22 progression1 :: [Chord.Generic [Melody.NoteAttribute]]
23 progression1 = [
24        Chord.generic E Chord.minorInt           dhn vel,
25        Chord.generic D Chord.minorInt           dhn vel,
26        Chord.generic C Chord.majorInt           dhn vel,
27        Chord.generic D Chord.minorInt           dhn vel
28      ]
29 
30 chords = line (map chord
31             (Chord.leastVaryingInversions
32                ((1,C),(1,C))
33                (progression1)))

This last line is a bit of mystery to me. I pieced it together from the "White Christmas" sample. I wanted to say something like this:

chords = line (map chord (progression1))
Because I don't need to invert anything and I don't really understand what Chord.leastVaryingInversions is supposed to be doing. But It is harder than you would think to extricate this out if you are a beginner at Haskell. The typing system sets up the requirements and wrapping things up with the Chord.leastVaryingInversions seems to make the following lines work (which, unlike the previous line, make sense to me):
43 chordProgression = (MidiMusic.fromStdMelody MidiMusic.AcousticGrandPiano chords)
which maps my chords to an instrument and makes it possible to merge it with the rhythm track to create a 'song' (and set the tempo):
45 song :: MidiMusic.T
46 song = MidiMusic.changeTempo 3.2 $
47    Music.line (replicate 8 chordProgression) =:=
48    Music.line (replicate 16 rhythm)

which is in the format I need to generate a midi file. So I can finally achieve my goal of generating a Midi file.

51 main :: IO ()
52 main = SaveMidi.toFile "test.mid" (Render.generalMidiDeflt song)

And finally, just the basic stuff needed at the top of the file. You have to declare this as a module and import a bunch of the haskore modules.

 1 module Main where
 2 
 3 import           Haskore.Melody.Standard   as Melody
 4 import           Haskore.Music.GeneralMIDI as MidiMusic
 5 import qualified Haskore.Music             as Music
 6 import qualified Haskore.Basic.Drum        as Drum
 7 import qualified Haskore.Basic.Chord       as Chord
 8 import           Haskore.Basic.Pitch (Class(..))
 9 import qualified Haskore.Interface.MIDI.Render       as Render
10 import qualified Haskore.Interface.MIDI.General      as GeneralMidi
11 import qualified Haskore.Interface.MIDI.Save         as SaveMidi

This is great

So, this is very interesting. It's more feature-rich than the Ruby version, which was better than the Python version. It does everything I want and more. It does strange things with the =:= operator that make it easy to merge notes. It has chords already created that I can reference like this Chord.generic E Chord.minorInt instead of having to create my own chord library. And it compiles down to native code.

But...

I can't say the language is simple or intuitive. I wouldn't, for instance, encourage someone dabbling in programming to use Haskell. However I could see using it to build something that someone could use to create songs quickly. Which is precisely what a lot of other people have already done. But if Java (and Object-Oriented Programming in general) is such a complexity stretch for the programming community that it took 10-15 years to get it down, something like Haskell is just not going to fly. Not unless programmers are dwelling in the same realm as mathematicians. And I'm not sure yet whether programming can be distilled down to mathematics or not. Maybe. Maybe not.

Plus, I'm thinking the maxim "Use runtime interpreted dynamic languages at all times - unless you can't" (I made it up myself) may be a good rule to follow. A good use for Haskell would be to write the code for the "runtime interpreter" - or to factor out CPU intensive and/or parallel tasks. Instead of doing it in C, as is the current methodology. Unfortunately C and especially C++ may have a lesson for Haskell which is: "if it's complicated, it's error prone" (my own maxim I think). I'm not convinced yet that writing Haskell code means less errors.

Conclusion

So here is a language with a lot of interesting projects, that compiles to native code, makes you think about programming differently and works on Windows™. Also, I was able to install components using the runhaskell Setup.hs convention - and I actually achieved a few things already:

  1. created a Gtk GUI that connects to Postgresql
  2. programmatically generated a midi file

So Haskell wins Rob's Language of the Year award.

Comments

Post a comment


Total: 0.35 Python: 0.33 DB: 0.02 Queries: 31