Chessguy pointed out that it's currently hard to play along with the monad wars code.
It would be nice for the posts to be “literate haskell”, where sections preceded by “>“ characters are valid Haskell. The idea is great - that you can mix sections of introduction and description with sections of actual code, ending up with an article that is also executable code! Which is very much the style of these posts, but right now I'm being too lazy to go that extra step:
- sometimes I show multiple version of the same function, (some of them might not work)
- I tend to introduce imports as needed but (I believe) they need to be at the top of the file
- I don't always repeat functions from earlier posts but just refer to them
So I've posted the current source to my subversion repo. As you can see they're currently related to the number of the associated post, and contain different areas of functionality: this is actually how I'm working on it for the moment - I'm hoping to put together some of the pieces in part 5 or so (Blog Driven Development is a rather odd way to structure your work but there you go...)
Update: Vincenz on #haskell convinced me that I really should try literate haskell - watch this space...
I'm now probably going to fall off the internet for a week while I move country and job. I'll be at the London Perl Workshop this Saturday and will talk (for a whole 5 minutes!) about Monad Wars. Maybe see you there :D
On #moose, the channel for a popular modern dialect of Object Oriented Perl, Debolaz asked:
<Debolaz> Is there a simple way to produce an array of array
like [[1..4],[1..4]] without having to specify the
[1..4] multiple times?
Matt Trout's reply confused me at first
<@mst> map { [ 1..4 ] } (1, 2)
because I thought there was a more straight-forward obvious answer:
([1..4]) x 2
Of course, as it happens, I was wrong... there is a problem with my version, and it's clear if you print the structure out using Data::Dumper
$VAR1 = [
[ 1, 2, 3, 4 ],
$VAR1->[0]
];
The same reference is cloned. This wouldn't be a problem in Haskell of course — being a pure language, there's no way reusing the same memory address could cause a problem. So you can just write:
> replicate 2 [1..4]
Peregrin suggested
(eval { [1..4] }) x2
but this also reused the array. My best solution was
use Storable qw(dclone);
map { dclone $_ } ([1..4]) x 2
which is also quite yucky. Debolaz ended up using a variant of Matt's solution:
map [ 1..4 ], (1,2)
I never use map EXPR, LIST style (probably I read that it could be confusing at an impressionable age...) but that does seem fairly legible.
After the last post, we have parser actions that can recognise an integer or an item of merchandise. Now we need to be able to process a command, like “jet bronx” or “buy 4 lambdas”. Let's start off with this basis:
> parseCommand = parseMap commandMap
> commandMap = getPrefixMap [
> ( "buy", cmdBuy ),
> ( "sell", cmdSell ),
> ( "jet", cmdJet ),
> ( "quit", cmdQuit )
> ]
Now, what we roughly want to do is:
- Tokenise the line
- Check if the first token maps to a command
- Check if the rest of the tokens can be handled by that command.
- The result (if applicable) is a function that maps an original GameState into a new state.
We might come up with something like this:
> parseLine s = do let (cmd:pars) = tokenizeLine s
> c <- parseCommand cmd
> c' <- c pars
> return c'
But I'd promised that we'd check if the parsing worked! Can you see any checks like that above?
Possibly Maybe
If we check the type of parseCommand, we'll notice it returns a Maybe
parseCommand :: [Char]
-> Maybe ([String] -> Maybe (GameState -> Maybe GameState))
This means that the do expression starts with a Maybe (we don't count the let expression) and so is in the Maybe monad. If any of the sequence fails, parseLine will return Nothing, without us having to specify anything! This is quite cute and, once you get used to it, rather intuitive (the definition above fell naturally out of my text editor).
Here's another example of Maybe - parsing the expressions like “buy 4 curry” or “sell 10 stm”. First of all, we notice that cmdBuy and cmdSell both have the same form, so we'll share the code in a common parser called cmdMerchandise.
> cmdBuy = cmdMerchandise doBuy
> cmdSell = cmdMerchandise doSell
This parser looks at the first 2 parameters, and tries to parse them respectively as an Int or an item of Merchandise.
If either of the parses fails, it will magically return Nothing.
If it succeeds, it will return the result of, for example doBuy 10 lambdas. (The result is of course a function that takes a GameState in input, and returns a GameState that is the result of having bought 10 lambdas. Very meta.)
> cmdMerchandise f (n:m:_) = do n' <- parseInt n
> m' <- parseMerchandise m
> Just $ f n' m'
> cmdMerchandise _ _ = Nothing
Let's play
This is getting a little abstract if we can't test it. Right now our GameState record doesn't have a “list of merchandise” structure, so let's keep it simple for the sake of argument and add a debug string instead.
> data GameState = GameState {
> turn :: Integer,
> score :: Integer,
> location :: Location,
> debug :: String
> } deriving Show
>
> type Location = Int
(and add a debug = ““ to the startState declaration.)
We'll make the doBuy and doSell functions just modify the debug string:
> doBuy n m gs = return gs {
> debug = "You bought " ++
> (show n) ++ " " ++
> (name m)
> }
> doSell n m gs = return gs {
> debug = "You sold " ++
> (show n) ++ " " ++
> (name m)
> }
OK, we could factor these out as an exercise, but we'll be replacing them soon. Now, what we really want to do is to test it! I'll look at plugging this into the prompt structure next time, for now let's just create a test function. This just takes a GameState and a line, and returns the new state if it all worked out.
> test gs s = do c <- parseLine s
> c gs
We can play with this to see if it worked:
*Main> test startState "sell 3 la"
Just (GameState {turn = 1, score = 0, location = 0,
debug = "You sold 3 Lambdas"})
*Main> test startState "panic"
Nothing
The other actions are similar. We'll use the record mutators modScore and nextTurn that we saw last time.
> -- Just a stub: We'll probably want to set an "endflag" or similar.
> cmdQuit _ = Just doQuit
> doQuit gs = return $ modScore (-10) gs {
> debug = "Quitter!" }
>
> cmdJet (n:_) = do n' <- parseInt n
> Just $ doJet n'
> cmdJet _ = Nothing
>
> doJet n gs | n == location gs
> = return $ gs {
> debug = "You are already in location "
> ++ show n }
> | otherwise
> -- Jetting increments the turn counter
> = return $ nextTurn gs {
> location = n,
> debug = "You have moved to "
> ++ show n }
And we can now test the remaining actions:
*Main> test startState "jet"
Nothing
*Main> test startState "jet 1"
Just (GameState {turn = 2, score = 0, location = 1,
debug = "You have moved to 1"})
*Main> test startState "jet 0"
Just (GameState {turn = 1, score = 0, location = 0,
debug = "You are already in location 0"})
*Main> test startState "qu"
Just (GameState {turn = 1, score = -10, location = 0,
debug = "Quitter!"})
Next time around, we'll plug these actions into our prompt, and we'll work on representing the game state
One of the advantages of demonstrating your ignorance in public is that you may receive useful corrections... thanks to everyone who replied on these recent posts, I found the comments very instructive, and thought it was worth writing up as a new post.
Strict records
ddarius got in touch to mention that I might want to use “strict fields”. This might be an issue if I'm incrementing, say, turn, but not actually using the value. I'd end up building up a “thunk” (an unevaluated expression) like 1+1+1+1+1+1+1, which will get evaluated later (and if too much later, it could cause some problems like stack overflow). Actually, I don't think this will happen in this particular case (I'll be printing the turn count every time) but it's not hard to implement (just need to put a “!” before the strict fields)
> data GameState = GameState {
> turn :: !Integer,
> score :: Integer,
> location :: Location
> } deriving Show
Also, as nominolo suggested, in conjunction with -funbox-strict-fields, it can open up some possible optimizations.
Not Just Maybe
Now this is an interesting one. I was whining in my last post about Data.Map.lookup
but which monad is it in, and more to the point, why?
As you might imagine, I really wasn't getting it... and the code I wrote around it rather reflects that...
Vincenz, Rich Neswold, and “rm” all pointed out in rapid succession that the function I'd created for parseMap was completely redundant. Here, for comparison, is my first version.
> parseMap m s | M.member s m = do v <- M.lookup s m
> Just v
> | otherwise = Nothing
I wrote this because, from the ghci command line, it looked like M.lookup threw an error if it couldn't find the key. The suggestion, which is rather briefer is as follows:
> parseMap = flip M.lookup
The flip is only there because parseMap and Data.Map.lookup take their arguments in opposite orders. Otherwise lookup and parseMap are identical!
But how does this return a “Just” or a “Nothing” appropriately? Apparently, on success, it returns a Monad of the appropriate type by default. If on the other hand it doesn't work, it will fail.
The IO monad maps a fail to an error (which is why I saw the exception I mentioned in the post!) But Maybe will map it to Nothing.
So from the ghci command line, we can create a small test Data.Map and run some lookups against it “in” various monads.
Prelude Data.Map> let m = Data.Map.fromList ("one", 1)
-- success
Prelude Data.Map> Data.Map.lookup "one" m :: Maybe String
Just "uno"
Prelude Data.Map> Data.Map.lookup "one" m :: [String]
["uno"]
Prelude Data.Map> Data.Map.lookup "one" m :: IO String
"uno"
-- fail
Prelude Data.Map> Data.Map.lookup "two" m :: Maybe String
Nothing
Prelude Data.Map> Data.Map.lookup "two" m :: [String]
[]
Prelude Data.Map> Data.Map.lookup "two" m :: IO String
*** Exception: user error (Data.Map.lookup: Key not found)
ddarius gave a name to this technique, “Not Just Maybe”. That is, if you were going to write a function that returns Maybe “Just 1“ and Maybe “Nothing”, then you might as well just write it as a generic monad. This will then be usable within Maybe, as planned, but also in IO and List too.
This sparked an interesting discussion about “Common Idioms” in Haskell. Apparently the pages that used to exist on this topic haven't yet been migrated to the new wiki. But there are some notes, for example on this snapshot of the NotJustMaybe page.
ddarius also suggested I could rewrite parseInt similarly
> import Control.Monad
>
> parseInt :: MonadPlus m => String -> m Int
> parseInt s | all isDigit s = return $ read s
> | otherwise = mzero
Using MonadPlus, 1) requires the Control.Monad import. 2) seems to require the type signature. 3) it looks like IO doesn't have an mzero, so you can't now type
*Main> parseInt "4"
<interactive>:1:0:
Ambiguous type variable `m' in the constraint:
`MonadPlus m'
at the command line and have it Do The Right Thing. I'd read that fail is considered bad style (for some reason), but it seems to be rather more convenient on these 3 counts at least:
> -- type will be inferred if omitted
> parseInt :: Monad m => String -> m Int
> parseInt s | all isDigit s = return $ read s
> | otherwise = fail "not an int"
This time around, we're going to look at how we'll turn user input into commands in Monad Wars.
I think that the easiest option to implement will also be very convenient to play with: a command line where we issue commands like:
$ buy 4 foo
$ sell 20 bar
$ jet bronx
or with abbreviations
$ b 4 f
and where it's unambiguous, collapse spaces:
$ b4f
Tokenising
We'll start by tokenising the command line string. This is almost as easy as using the Prelude function words. The only complication is that we want to tokenise alternate letters and numbers separately, like the “b4f” example above.
We'll use groupBy from Data.List, and isLetter from Data.Char.
> import Data.List
> import Data.Char
>
> tokenizeLine :: [Char] -> [[Char]]
> tokenizeLine = concatMap
> (groupBy ((==) `on` isLetter))
> . words
Annoyingly, on (in the Prelude in GHC 6.8) doesn't exist in my 6.6.1 installation, so we'll have to define it:
> op `on` p = (\a b -> p a `op` p b)
Let's just see how groupBy works:
*Main> groupBy (==) "aabbbcdd"
["aa","bbb","c","dd"]
*Main> groupBy (\a b -> isLetter a == isLetter b) "abc123def"
["abc","123","def"]
The on expression is a nicer way of writing the second case. We then map this grouping over each of the tokens, getting our final list.
*Main> tokenizeLine "jet quuxville"
["jet","quuxville"]
*Main> tokenizeLine "b3p"
["b","3","p"]
Parsing
Now we'll want to do things with the tokens. Yes, there are libraries to do this (Parsec etc.) I know that in the Perl world I'd often tell other people not to reinvent the wheel and to use CPAN, so I do feel a little naughty that I'm going to ignore these and handroll something myself. In my defence m'ludd,
- I'm only parsing very simple commands
- Learning a new library requires cognitive effort. I have limited time for this task, and I believe (possibly wrongly) that I will be able to “roll my own” more quickly.
- Reimplementing functionality can be instructive in and of itself
- It also makes you appreciate how simple the “official” solution really is, when you finally get around to learning it.
In an expression like buy 4 foo I'm imagining that “buy” will map to some command. Then we'll need to parse “4“ as a number, and “foo” as a some merchandise. The first case is the simplest:
> parseInt :: String -> Maybe Int
> parseInt s | all isDigit s = Just $ read s
> | otherwise = Nothing
This reads tantalisingly close to English: If all the string is made up of digits, we just read it as an Integer. Otherwise we return nothing. OK, I'm glossing rather over the “Just” and “Nothing” which indicate whether the parse succeeded using the “Maybe” monad.
*Main> parseInt "42"
Just 42
*Main> parseInt "wibble"
Nothing
Now for parsing the merchandise: if there is an item called “foo”, we'd want to match “foo”, “fo”, “f”. By amazing coincidence, I recently wrote about a function that does exactly what we need:
> import qualified Data.Map as M
> import Control.Arrow
> getPrefixes :: [([a], t)] -> [([a], t)]
> getPrefixes = concatMap $ uncurry zip .
> (tail . inits . fst &&& repeat . snd)
> getPrefixMap :: (Ord a) => [([a], t)] -> M.Map [a] t
> getPrefixMap = M.fromList . getPrefixes
So we'd need a list of merchandise. Which means we should think a little about what the datatype will look like. I'm going with a record type again, because I know there is more information that we'll need to store:
> data Merchandise = Merchandise {
> name :: String,
> min :: Int, -- minimum price
> max :: Int -- maximum price
> }
> deriving Show
Next, we define the products on offer, by looking at the Dope Wars configuration pages, we can copy the prices, but of course we have to theme the names...
> merchandise :: [Merchandise]
> merchandise = [
> Merchandise "Arrows" 1000 4400,
> Merchandise "Curry" 15000 29000,
> Merchandise "Kleisli" 480 1280,
> Merchandise "Haskell" 5500 13000,
> Merchandise "Lambdas" 11 60,
> Merchandise "STM" 1500 4400,
> Merchandise "Monads" 540 1250,
> Merchandise "GHC" 1000 2500,
> Merchandise "Peyton" 220 700,
> Merchandise "Fundeps" 630 1300,
> Merchandise "Zipper" 90 250,
> Merchandise "Endo" 315 890
> ]
Now this isn't in the form we need it yet. First of all, we'll map this list to a list of tuples of [(string, thing),...], which is exactly what getPrefixMap is expecting:
> merchandiseMap = getPrefixMap $
> map (\i -> (toLowerS $ name i, i))
> merchandise
> -- toLowerS over a string isn't defined by default, but it's just:
> toLowerS = map toLower
OK, for a bit of fun, we could write the map as:
> -- map (toLowerS . name &&& id)
(I don't yet understand why “arrows” are supposed to be an “even more generic model of computation than monads”, but they're certainly good for putting things in tuples :-)
Now we can build the parser parseMerchandise. Or rather (as we'll probably use this technique again, for example for the names of locations, and even the commands like “buy” and “jet”), we'll create parseMap
Interestingly Data.Map's lookup function throws an exception (as a “user error”!) if you try to look up a key that doesn't exist. (This is rather different from Perl hashes, but it makes sense in a strongly typed language - there is no “undef” value which is of the requested type!) So that I can avoid having to learn exceptions just yet, I'm going to check first of all if the key exists using member
> parseMap m s | M.member s m = do v <- M.lookup s m
> Just v
> | otherwise = Nothing
I spent about half an hour trying to get the above to work. After a number of rather unhelpful error messages about monads, I changed from my original attempt v = M.lookup s m to the do-notation form. This is rather odd, as it implies that lookup is monadic. And its type does indeed suggest that the result is in some monad...
*Main> :t M.lookup
M.lookup :: (Ord k, Monad m) => k -> M.Map k a -> m a
but which monad is it in, and more to the point, why?
In any case, now it's easy as pie(*) to create our merchandise parser as a specialization of our general function:
> parseMerchandise :: String -> Maybe Merchandise
> parseMerchandise = parseMap merchandiseMap
and we can now recognize the names and abbreviations of elements in the list!
*Main> parseMerchandise "pey"
Just (Merchandise {name = "Peyton", min = 220, max = 700})
*Main> parseMerchandise "vb"
Nothing
Next time around, we'll create “buy” and “sell” handlers that parse the whole command line, and stub in the actual interaction with the game state!
(*) Well, I say easy, but at this point (and ongoing) we get bitten by the “monomorphism restriction”, whatever that is. The error message suggests that you add explicit types to all the functions involved, but when I try that, I regularly get even stranger error messages about rigid variables and monads. The easier solution seems to be to add -fno-monomorphism-restriction to your ghci call. (I don't know enough to know whether this is a Bad Thing). Of course, now this error message doesn't come up. Pah!
A lot of learning projects involve writing games: people have written clones of Tetris, Asteroids, Space Invaders, and even first person shooters (Frag) in Haskell. As I'm far less clever than these people, I thought I'd start with something a bit simpler: Dope Wars.
Dope Wars is basically a trading game. In 30 turns, you move from one location to another, buying and selling, er, drugs on the streets of New York. It's a fairly simple concept, but one which includes elements like:
- Input and Output
- Game state
- Random numbers
all of which seem like a good way to learn Monads and the other building blocks that you need to actually do anything useful in a functional programming language like Haskell.
So I had the idea, wrote up a couple of datatypes and some functions, and then forgot about it. Then, when Greg McCarroll mentioned that he'd accept talks about other languages for the London Perl Workshop on December 1st, I thought it would be a great opportunity to push myself to do it by proposing a lightning talk.
Only problem: I now have to actually write the game in order to talk about it (*). So... here goes. In this post, I'm going to show a first draft for the game prompt.
State
OK, people often find it most convenient to do State using “Monads”. I think I'm going to leave that for now, and just thread state explicitly. Mainly because I haven't yet got around to learning how to use the State Monad. Hopefully this will eventually become annoying enough that it will give me impetus to learn the monadic version.
Anyway, the idea here is that we'd have some function playTurn that will look a bit like this:
> playTurn :: GameState -> IO ()
> playTurn gs = let gs' = doSomething gs
> in playTurn gs'
(There is an interesting post on haskell-cafe about a more sophisticated monadic representation of the prompt).
So we just need to work out how to represent this GameState object. To start off with, we'll want to store information like
- Which turn it is
- What our score is (how much money we have)
- Where we are on the game map
We could create a normal tuple:
> data GameState = GameState Integer Integer Location
> -- turn score location
and then pattern match on this, but it's going to get horrible if we add any fields later on! In Perl I'd just use a hash, but remember that Haskell Data.Map objects map from one type to another, and we might well have values of various types.
When I asked on #haskell, dons and firefly told me about the record syntax:
> data GameState = GameState {
> turn :: Integer,
> score :: Integer,
> location :: Location
> } deriving Show
>
> -- for now:
> type Location = Integer
Defining the original state is easy:
> startState = GameState {
> turn = 1,
> score = 0,
> location = 0
> }
And to “modify” it (or rather to clone it, overriding certain fields) there is a convenient syntax that just lets us declare those fields which have changed:
> movedToBronx = gs { location = bronx }
Setting a field to a value is easy, but we might want to define some mutators to change the field relative to its current value:
> nextTurn :: GameState -> GameState
> nextTurn gs = gs { turn = succ $ turn gs }
>
> modScore :: Integer -> GameState -> GameState
> modScore d gs = gs { score = score gs + d }
The record syntax will work even when we inevitably add new fields later. Yay!
Prompt
Now, the game cycle is a bit more complicated than the version I suggested above, as it will allow IO actions in it. Something perhaps like this:
> playTurn gs = do showStatus gs
> putStr prompt
> s <- getLine
> let f = parseLine s
> let gs' = f gs
> if isEnd gs'
> then endGame gs'
> else playTurn gs'
For now we'll just stub some of the declarations we need. showStatus can just show the GameState record (which is why we derived the Show class).
> showStatus gs = putStrLn $ show gs
We may as well set the prompt to the dollar sign, appropriately:
> prompt = "$ "
Though we'll need to parse the line read from standard input to one of the commands like “Buy 4 X” or “Goto location 3“, right now, we'll just stub in a function that increments the score and the turn counter:
> parseLine s = nextTurn
> . modScore 10
We need to know if we're at the end of the game, and take action appropriately.
> isEnd gs = turn gs > maxTurns
>
> maxTurns = 3
>
> endGame gs = do putStrLn "Game over!"
> putStrLn $ "Your score was " ++ (show $ score gs)
> return ()
And here's a transcript
*Main> playTurn startState
GameState {turn = 1, score = 10, location = 0}
> buy 2 foo
GameState {turn = 2, score = 20, location = 0}
> sell 4 bar
GameState {turn = 3, score = 30, location = 0}
> goto quuxtown
Game over!
Your score was 40
(*) to be fair, it's only a 5 minute “Lightning Talk”, so I could probably get away without even writing the whole game, but I'll feel better if I actually know what I'm talking about...
This morning, Ranguard asked an interesting question on #london.pm:
11:27 <@ranguard> What's the best way of finding the common root of two paths,
e.g. for /a/b/c/d/e, /a/b/c/1/2/3 I want /a/b/c/ returned,
Path::Class::Dir has 'contains', but I'm not sure that's quite right?
In my copious free lunchtime, I thought I'd write a version of it in Haskell. Though Ranguard was very sensibly using Path::Class in Perl, I decided to just work on a list of values (in this case, characters).
First of all, let's zip the list of paths, together with a boolean indicating whether we matched or not:
> zipWith (\a b -> (a==b, a))
We'll want to only take the ones from the beginning for which the tuple contains True in its first element:
> takeWhile fst
And we only want the second element
> map snd
So we just build a pipeline of these by composing the functions with the dot operator:
> commonPrefix = map snd .
> takeWhile fst .:
> zipWith (\a b -> (a==b, a))
Of course I've cheated here. The map and the takeWhile are expecting just one argument, so can be composed fine in this pipeline, but zipWith takes two arguments. I've seen people rewrite their pipelines to accomodate this, but I think it's horrible and derived a function to compose a 1-arg function with a 2-arg one. Saizan suggested the conventional name (.:) and roconnor gave a cuter implementation than mine:
> infixr 8 .:
> (.:) = (.).(.)
(The infixr declaration is because (.) is infixr 9, but I couldn't get it to compile like that so played around with it at random. Yes, very scientific.)
> x = commonPrefix "abcde" "abc123"
I asked lambdabot for a points-free version of the lambda expression I pass to zipWith, but it came up with utter horrors. EvilTerran suggested the very clever looking:
> flip ((&&& id) . (==))
> -- or alternatively:
> ((,) =<<) . (==)
Vincenz came up with another, perhaps more sensible option.
> commonPrefix = map fst . takeWhile (uncurry (==)) .: zip
This just zips all the elements together, and takes only the equal ones.
Meanwhile, back in the Perl world, Ranguard posted a solution, which Ilmari modified to use the lovely List::MoreUtils:
#!/usr/bin/perl -l
use strict;
use warnings;
use File::Spec::Functions qw(catdir splitdir);
use List::MoreUtils qw(all each_arrayref);
my $paths = each_arrayref map { [ splitdir $_ ] } (
'/v/f/d/index.html',
'/v/f/d/i/a/s/s.gif',
'/v/f/a/s/s.gif',
);
my @root;
while (my ($part, @rest) = $paths->()) {
last unless all { $_ eq $part } @rest;
push @root, $part;
}
print catdir(@root);
Unlike the Haskell version, it takes any number of paths.
Which suggests an additional exercise: modify or create a Haskell function to find the longest common root of an arbitrary number of lists. Do let me know if you try this, and post your code (if you can get it past Vox's comments system ;-).
Greg confessed on #london.pm to having written a average function in Perl in FP style... recursively.
I asked him about this, as a perfectly good average function (using List::Util would be:
sub avg { sum(@_) / @_ }
which is perfectly fine FP style too. As far as I could understand it, he was reducing a tuple of sum and count. Though he said he wrote it just-for-fun, it's not entirely unreasonable if you're using a list-based language, where you'd otherwise have to traverse the list twice (in Perl, arrays know how long they are).
Out of curiosity, I tried to check how Haskell defined avg, but it seems that it doesn't.
LoganCapaldo suggested the following definition:
> import Data.List
> avg xs = sum xs / genericLength xs
We use genericLength because the normal length function insists on returning an integer, while genericLength returns a generic Number, which can be divided appropriately. (We'd otherwise have to write sum xs / (fromIntegral $ length xs))
The recursive function would work by summing a tuple, so that
avgs [1,3,5] => (9,3)
Once we have that we could divide the tuple using uncurry.
> avg' = uncurry (/) . avgs
As we're going to fold the number values into a tuple, we need a function like:
> sum_count s (t,c) = (t+s, c+1)
after which our definition is easily written as a fold
> avgs = foldr sum_count (0,0)
Which works just fine. But seeing the tuples, I thought of the comments from Joao and doserj, and thought of (&&&).
> import Control.Arrow
> sum_count s = (+s).fst &&& succ.snd
And byorgey commented that the .fst and .snd pair can be abstracted away using (***). So I tried
> sum_count s = (+s) *** succ
and was really rather surprised that it worked! Admittedly, the definition is now something that scares me and whose intent is far less clear than the first naive definition above:
> avg2 = uncurry (/) . foldr (\s -> (+s) *** succ) (0,0)
For extra bonus obfuscation points, making the lambda expression points-free is left as an exercise to the reader ;-)
Update: mauke pointed out that my Perl avg sub didn't work... added parens to sum (@_). Looks like naughty me didn't bother testing that code...
Some interesting comments on yesterday's Haskell post. I thought I'd write this in Perl to compare. Of course, we don't have an "inits" function in the standard library, but that's easily written:
#!/usr/bin/perl
use strict; use warnings;
use Data::Dumper;
my %hash = (
"key1" => 1,
"key2" => 2,
"other" => 3,
);
sub inits {
my $string = shift or return;
return ($string, inits( substr($string, 0, -1)) );
}
sub getPrefixMap {
my %hash = @_;
return map {
my $k = $_; my $v = $hash{$_};
map { ($_ => $v) } inits $k;
} keys %hash;
}
my %map = getPrefixMap( %hash );
print Dumper( \%map );
This is a bit noisier, but perhaps more straight-forward than the final haskell version...
Update: broquaint pointed out that the code listing above was missing the zero in the substr, breaking it. Thanks!
Here's a little snippet I worked on yesterday, while preparing my talk for the London Perl Workshop.
It takes a list of tuples ("string", whatever) and maps all the prefixes of the string
("string", "strin", "stri", etc.) to the whatever.
I was quite impressed at how easily this came together. The functional composition (pipelines connected with ".") is coming a bit more easily, but the heavy lifting is all done really by the "inits" function from Data.List.
Some of this is made "prettier" (but harder to understand) by various features. "uncurry" changes a function that takes a tuple (a,b) into one that takes 2 arguments "a b". The (flip (,) r) is actually harder to read than the equivalent lambda, (\s -> (s,r)). (Annoyingly, you can't section the comma operator to do "(,r)" because tuples are "special"...)
--
import Data.List
import qualified Data.Map as M
-- can contain duplicates!
getPrefixes :: [([a], t)] -> [([a], t)]
getPrefixes = concatMap (uncurry gps)
where gps s r = map (flip (,) r) -- make a tuple (s',r)
. reverse -- shortest prefixes last
. tail -- ignore the empty prefix ""
. inits -- get all the prefixes
$ s
getPrefixMap :: (Ord a) => [([a], t)] -> M.Map [a] t
getPrefixMap = M.fromList . getPrefixes
-- without the type, we'd have to write like so (because of the
-- monomorphism restriction)
-- > getPrefixMap xs = M.fromList $ getPrefixes xs
Update: after Joao's comments below, I got the version
> getPrefixes :: [([a], t)] -> [([a], t)]
> getPrefixes = concatMap gps
> where gps (s, r) = let ss = tail . inits $ s
> rs = repeat $ r
> in zip ss rs
Joao meanwhile mentioned the cross product, which I didn't
immediately get the point of.
> infix 1 ><
> (><) f g a = (f a, g a)
As you can see, this applies the remaining parameter "a"
to two functions. So it's useful in this case to curry
the "(s,r)" parameter. doserj pointed out that this is
spelt (&&&) so that we just need to:
> import Control.Arrow
> getPrefixes = concatMap $ uncurry zip .
> (tail . inits . fst &&& repeat . snd)
As often happens in Haskell you could argue whether this is
actually any clearer, but it's cute!