Haskell and Programming as Mathematics
Some thoughts about the language Haskell
Posted by 02/27/2007
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.
- It should always be possible to compile to a binary - so I can distribute what I write without requiring installation of something else
- It should have something resembling literate programming - the ability to write text in the code and output those comments to another format
- 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
- It should have a command line interface of some sort. No purely visual coding like Visual Basic or executable UML or somesuch
- It should provide the ability to run interactive code (REPL)
- It should provide a simple way to build and distribute (a packaging system)
- It should have a fairly substantial library of useful things
- It should be as easy to use on Windows™ as a Posix based system
- 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
- 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)
- 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
- 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)
- created a Gtk GUI that connects to Postgresql
- programmatically generated a midi file
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
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)
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:
So Haskell wins Rob's Language of the Year award.
Comments
Post a comment