Getting started with Haskell… still
I can’t help but think there’s a bit of a gap in the market for introductory texts on Haskell. I say this in part because, at time of writing, if you google (hah! I’m using it as a verb! Trademark that!) “Getting Started Haskell”, you might end up here =/
I’d resolved to try getting started again on account of continuing to hear people rave about the language, so last night I did what I always do when learning something new - I googled “Getting Started X”. I found this awesome e-book / blog:
http://book.realworldhaskell.org/beta/index.html
It’s very well written (if a little rough round the edges - beta is the word), but I still think the learning curve presented is a _little_ steep for simpletons like myself. Bear with me while I expose my ignorance.
We’re using GHC. GHC is recommended by the book, it’s recommended by Don (who is indescribably leet: http://cgi.cse.unsw.edu.au/~dons/blog/), and it’s recommended by my n-sim colleagues (who mostly are, except for me: http://www.n-sim.com).
# apt-get install ghc6 # ghci GHCi, version 6.8.2: http://www.haskell.org/ghc/ : ? for help Loading package base ... linking ... done. Prelude>
aaah. Prelude. Don’t I feel at home. In fact I don’t - this is all pretty weird, but working through the first couple of chapters of the book was fun. Walk with me a while…
Type Porn
If types don’t excite you, this probably isn’t the language for you. But they should; they’re awesome.
Prelude> :set +t Prelude> 1337 1337 it :: Integer
In GHCI (interactive GHC interpreter), setting “+t” makes the interpreter print the type of whatever you’ve just evaluated. Technically, it prints the type of “it” - the special value in to which your last evaluated expression is loaded (there is no spoon, there are no variables). I know what “1337″ is, I know what “it” is, but what’s “Integer”?
Prelude> :info Integer
data Integer
= GHC.Num.S# GHC.Prim.Int#
| GHC.Num.J# GHC.Prim.Int# GHC.Prim.ByteArray#
-- Defined in GHC.Num
instance Enum Integer -- Defined in GHC.Num
instance Eq Integer -- Defined in GHC.Num
instance Integral Integer -- Defined in GHC.Real
instance Num Integer -- Defined in GHC.Num
instance Ord Integer -- Defined in GHC.Num
instance Read Integer -- Defined in GHC.Read
instance Real Integer -- Defined in GHC.Real
instance Show Integer -- Defined in GHC.Num
ooo… fancy. What does that all mean? Well, I’m only on chapter 3, but in my simplistic Object Oriented view of the world, we’re effectively saying that Integer implements the interfaces Enum, Eq, Integral, Num, Org, Read, Real and Show (but the truth is a little more involved).
Prelude> :info Enum
class Enum a where
succ :: a -> a
pred :: a -> a
toEnum :: Int -> a
fromEnum :: a -> Int
enumFrom :: a -> [a]
enumFromThen :: a -> a -> [a]
enumFromTo :: a -> a -> [a]
enumFromThenTo :: a -> a -> a -> [a]
— Defined in GHC.Enum
instance Enum Integer — Defined in GHC.Num
instance Enum Float — Defined in GHC.Float
instance Enum Double — Defined in GHC.Float
instance Enum Bool — Defined in GHC.Enum
instance Enum Ordering — Defined in GHC.Enum
instance Enum Char — Defined in GHC.Enum
instance Enum () — Defined in GHC.Enum
instance Enum Int — Defined in GHC.Enum
You kind of have to be comfortable with looking at types of the form:
a -> a -> a -> [a]
“enumFromThenTo” obviously takes three values and returns a list of values. (The joy of types, right? You know what it does by what its type is.) Moreover, we can see that it’s defined for the instances Integer, Float, Double, Bool, Ordering, Char, () and Int.
Prelude> enumFromThenTo 1 2 8 [1,2,3,4,5,6,7,8] Prelude> enumFromThenTo () () () [(),(),(),(),(),()^CInterrupted.
and we can type functions too:
Prelude> :type fst fst :: (a, b) -> a
What does that one do? There's only one thing it can do! Yes, I know, that's practically pornographic.
The exercise
"Write a function lastButOne, that returns the element before the last."
Trivial, right? A five year old could do it. Well, excuse me while I have a quick flashback to ML ticks (PDF) and rock gently back and forth in the corner. You have to bear in mind that, at this point in the book, we don't know there's a 'length' function in Prelude, we've not been taught pattern matching or case statements, and we're still simplistically minded imperative programmers. So naïvely, the best we might be able to do is:
-- in add.hs count n [] = n count n xs = count (n+1) (drop 1 xs) myLastButOne xs = head (drop ((count 0 xs) - 2) xs) Prelude> :load add.hs [1 of 1] Compiling Main ( add.hs, interpreted ) Ok, modules loaded: Main. *Main> myLastButOne [1,2..10] 9
Repeat after me… “ewww”. And even to do that, I’ve had to use mysterious pattern matching which hasn’t been explained at that point in the book. Now, we could assume that a resourceful reader might find the ‘length’ function:
myLastButOne xs = head (drop ((length xs) - 2) xs)
But that’s still pretty unsatisfactory. For all I know, length is O(n) in the length of the list, so I’ll be going down the list twice. I daren’t imagine what Don would say. Even if it’s O(1), it doesn’t feel right. After a bit of head scratching and syntax guessing, I came to:
lastButOne (h:t) = case t of
(a:[]) -> h
(a:b) -> lastButOne t
which, for me at least, feels a little better. But I don’t know it’s right. I’m welcoming any pointers here. Now obviously for the “Find the last but nth item”, going down the list twice is looking less unattractive:
myLastButN n xs = head (drop ((length xs) - (n+1)) xs)
It’s still not great though. Would that I could list[-n]. But that’s not the point.
Summary
I’m determined to learn more Haskell and continue to expose my ignorance on this blog. Any pointers to good docs are welcome - “Haskell for simpletons”, that sort of thing. Meantimes I’ll continue to read the book. My stretch goal is to understand the things written on Don’s blog and on the following:
Conal Elliott:
http://conal.net/blog/
Kenn Knowles:
http://www.kennknowles.com/blog/
C-rat says:
June 27th, 2008 at 8:53 am
How about:
lastButOne (a:b:[]) = a
lastButOne (_:tl) = lastButOne tl
lastButOne “biteme”
codders says:
June 27th, 2008 at 8:57 am
ooo. Didn’t know you could pattern match on lots of cons-es. Thanks
C-rat says:
June 27th, 2008 at 10:02 am
Of course, we all know that Paulson would have preferred:
lastButOne = fst . (foldl (\(x,y) -> \b -> (y,Just b)) (Nothing,Nothing))
or
fst $ (foldl (\(x,y) -> \b -> (y,Just b)) (Nothing,Nothing)) “biteme”
–>Just ‘m’
BTW, WinHugs is quite good for interactive work.
Brent Yorgey says:
June 30th, 2008 at 4:26 pm
Hi there! I hope you’re enjoying learning Haskell. Types are indeed sexy. =) A few more resources you might be interested in:
* The Haskell wikibook is generally quite good, and there are also links to other tutorial materials from the front page.
* The Haskell wiki also has lots of reference material. In general I think you’ll find that there’s no “one true” tutorial, you’re probably best off trying out several different ones, and you’ll pick up different things from each.
* Most importantly, I invite you to check out the #haskell IRC channel, which is full of friendly and knowledgable people (such as the authors of Real World Haskell—dons, bos, and CosmicRay), and where discussion ranges from the basic to the advanced. It’s a great place to be if you’re learning Haskell, either to ask questions or just to hang out and absorb knowledge.
Good luck!
codders says:
June 30th, 2008 at 8:08 pm
It’s treating me well - thanks for the resources. I nearly wrote a Haskell script today at work before I realised that I’m still a couple of chapters off doing any I/O
I’ll definitely give the IRC channel a go.
Thanks again,
codders
sffubs says:
July 2nd, 2008 at 1:22 pm
Ignoring the obvious problems with lists of zero or one element, there’s always the point-free solution:
lastbutone = head . tail . reverse
Ryan Ingram says:
July 2nd, 2008 at 9:55 pm
You can also use the full list notation in your pattern matches.
lastButOne [result, _] = result
lastButOne (_:xs) = lastButOne xs
lastButOne _ = error “lastButOne: invalid list”
Ryan Ingram says:
July 2nd, 2008 at 9:57 pm
Although my favorite is
lastButN n xs = head $ drop n $ reverse xs
codders says:
July 3rd, 2008 at 8:16 am
Ryan, sffubs: Is reverse an especially efficient operation, or is it just “If you’re going down this list multiple times anyway you may as well…”?
Stuart Dootson says:
July 3rd, 2008 at 10:47 am
Codders - reverse is O(n) (see http://research.microsoft.com/~simonpj/papers/haskell-tutorial/TasteOfHaskell.pdf, which are the slides of Simon P-J’s Haskell talk @ OSCON last year), so not especially efficient
However, you can find yourself using reverse to avoid doing ‘list ++ [element]‘ lots of times - consider these two versions of the same function - we trade one reverse against ‘n’ list joins - a win!
sums nums = reverse $ sums’ nums []
where
sums’ (n:ns) [] = sums’ ns [n]
sums’ (n:ns) acc = sums’ ns (n+head acc:acc)
sums’ [] acc = acc
sums2 nums = sums2′ nums []
where
sums2′ (n:ns) [] = sums2′ ns [n]
sums2′ (n:ns) acc = sums2′ ns (acc++[last acc+n])
sums2′ [] acc = acc
Ryan Ingram says:
July 4th, 2008 at 1:32 am
reverse xs is O(length xs) memory and O(length xs) time, so it’s not ideal, but “last” is going to be O(length xs) time regardless. A better implementation can possibly save some memory.
sffubs says:
July 4th, 2008 at 4:37 pm
Yep, as Ryan has already pointed out, reverse is O(n), but last-but-one is always going to be O(n) in time. You still only traverse the list once though in my solution, unless I’ve missed something.
lilac says:
July 4th, 2008 at 5:24 pm
I suggest you read through the Prelude. There’s a lot of good stuff there that you’ll end up reinventing if you don’t know it’s built-in.
lastButOne = last . init
codders says:
July 4th, 2008 at 5:45 pm
stuart: awesome. Love Simon’s work… I’ll check it out.
Ryan, sffubs: fair point. I guess I’m not going to do a lot better. Shame to use the memory though.
lilac: Good point. ‘init’ isn’t the most intuitively named function in the Prelude, but it certainly seems to do the job. D’you think I can do init . init . init . init… without being wasteful?
Thanks all
sffubs says:
July 9th, 2008 at 7:44 am
codders: yes, I believe laziness means you can do init . init . init without being wasteful. I think lilac wins! (I hadn’t seen last or init before - shame on me!)
C-rat says:
July 9th, 2008 at 3:06 pm
I better put the Prelude on my reading list too. I might use init as a good example of lazy evaluation:
Hugs> (init [1..]) !! 3
4