Showing posts with label haskell. Show all posts
Showing posts with label haskell. Show all posts

Sunday, March 3, 2013

binary 0.7

binary 0.7 has just been released and compared to the current version included in the Haskell Platform, binary 0.5.1.0, it brings a few new things. Most of this has also been available in the 0.6.* versions which already have been available on hackage for some time.

Incremental interface and improved error reporting

This is probably the most requested feature, and was introduced already in binary 0.6.
In older versions of binary, you could run the Get monad with the following function:
runGet :: Get a -> Lazy.ByteString -> a
It takes the monad you want to execute, together a with a lazy ByteString containing all the input it will need to finish. While this is straight forward and easy to use, it does not meet all needs:
  1. There is no error reporting (compare with a function returning Maybe a, or Either ErrorMessage a). If a decoder error happens, it'll just call error :: String -> a.
  2. What if you don't have all input when you start to decode, like for example if you're reading from a large file or a socket? Previously, lazy IO has been used to address this, but we will look into why this is not always suitable.
To address (1), we introduce the data type Decoder as the return type for our new run function.
-- | Incremental interface to run a decoder.
runGetIncremental :: Get a -> Decoder a
A Decoder can have successfully finished in which case it'll be the Done constructor and have the final value, or have encountered a problem, in which case it'll be the Fail constructor together with the error message. In both cases, you will also get the remaining unused input, and the number of bytes consumed when the decoder succeeded or failed. In the next section, we will see why the this new run function does not need input as a second argument.

For (2), the traditional answer has been to use lazy I/O to get a lazy stream of data, which will hide that I/O is going on, and we can pretend that we already have all the input in memory. While this is fine for some scenarios, we have given up control over error handling and file handle resources. For some applications, this is simply not good enough.

Here's where the incremental interface can help. It works in the same way in binary as seen in other libraries for parsing. Instead of taking any input, it immediately produces a Decoder.  In addition to Done and Fail mentioned above, it also has the Partial constructor, meaning that the Decoder needs more input in order to finish.
-- | A decoder produced by running a 'Get' monad.
data Decoder a
  = Done !B.ByteString !ByteOffset a
    -- ^ The decoder has successfully finished. Except for the
    -- output value you also get any unused input as well as the
    -- number of bytes consumed.
  | Fail !B.ByteString !ByteOffset String
    -- ^ The decoder ran into an error. The decoder either used
    -- 'fail' or was not provided enough input. Contains any
    -- unconsumed input and the number of bytes consumed.
  | Partial (Maybe B.ByteString -> Decoder a)
    -- ^ The decoder has consumed the available input and needs
    -- more to continue. Provide 'Just' if more input is available
    -- and 'Nothing' otherwise, and you will get a new 'Decoder'.

GHC Generics

binary has also got support for automatically generating instances of the Binary class for your data types, using GHC's generics.
{-# LANGUAGE DeriveGeneric #-}

import Data.Binary
import GHC.Generics (Generic)

data Foo = Foo
         deriving (Generic)

-- GHC will automatically fill out the instance
instance Binary Foo
The was committed to binary by Bryan O'Sullivan, though pieces copied from the cereal package where it was written by Bas van Dijk.

Control.Alternative

binary now implements the Alternative class, and you can use the <|> combinator to try a decoder and fallback to another if the first failed. Although many binary formats don't strictly require backtracking in the decoder, it can be used for convenience or if it leads to code which is easier to read. In the example below, we parse frames in some binary format, trying first one kind of frames, then the other. If it's neither kind, we fail altogether.

data Frame = FrameA {- some fields specific to A frames -}
           | FrameB {- some fields specific to B frames -}

decodeFrame :: Get Frame
decodeFrame = decodeFrameA <|> decodeFrameB <|> fail "not a frame!"

decodeFrameA :: Get Frame
decodeFrameA = do
  word <- getWord8
  unless (isFrameAMagicWord word) $ fail "not an A-frame!"
  {- continue decoding... -}
  return (FrameA {..})


decodeFrameB :: Get Frame
decodeFrameB = do
  word <- getWord8
  unless (isFrameBMagicWord word) $ fail "not an B-frame!"
  {- continue decoding... -}
  return (FrameB {..})

In this particular example, though, it's not required to have backtracking, as we could have first decoded the first Word8, and then chosen to continue with either FrameA or FrameB. This continues to be the better choice for performance and should be preferred when possible, but one can imagine a more complicated binary format where using <|> is required or simply leads to cleaner code.

Control.Applicative

For each use of a primitive decoder, such as getWord8, getWord32le, and so on, binary needs to check whether there is sufficient input left, or if it needs to demand more input from the caller (by returning a Partial, as discussed above).

decodeWordTuple :: Get (Word32, Word16)
decodeWordTuple = do
  a <- getWord32le
  b <- getWord16le
  return (a,b)


Here, binary will check twice whether there is enough input. First for the 4 bytes of the Word32, and a second time whether there is 2 bytes left for the Word16. Once could think this is wasteful, and that we should only check once at the beginning for the 6 bytes that we know will be required. Unfortunately, with a monadic API this is not possible (maybe possible with some new fancy kind of rewrite rules?).
When using the applicative class though, binary tries to be clever and join these checks.

decodeWordTuple :: Get (Word32, Word16)
decodeWordTuple = (,) <$> getWord32le <*> getWord16le

It does this by using GHC's rewrite rules to rewrite the expression into a single decoder. Note that for this to make a worthwhile difference to the performance of your application, your application needs to already be quite tuned. The rewriting also relies heavily on that all decoder functions gets inlined, and can therefore be a bit hard to trigger.

Backwards compatibility

I'm pleased to say that while the internals of binary has undergone major changes, it still largely provides the same API to the developer. A few convenience functions have been added, and a few functions that doesn't make sense any more have been removed. Read the haddocks for the full API here.

Data.Binary:
decodeFileOrFail :: Binary a => FilePath
                 -> IO (Either (ByteOffset, String) a)
decodeOrFail :: Binary a =>
  Lazy.ByteString -> Either (Lazy.ByteString, ByteOffset, String)
                            (Lazy.ByteString, ByteOffset, a)

Data.Binary.Get:
runGetIncremental :: Get a -> Decoder a
runGetOrFail :: Get a -> Lazy.ByteString
  -> Either (Lazy.ByteString, ByteOffset, String)
            (Lazy.ByteString, ByteOffset, a)
pushChunk :: Decoder a -> ByteString -> Decoder a
pushChunks :: Decoder a -> Lazy.ByteString -> Decoder a
pushEndOfInput :: Decoder a -> Decoder a
uncheckedLookAhead :: Int64 -> Get Lazy.ByteString
uncheckedSkip :: Int64 -> Get ()

The functions lookAhead and lookAheadM which disappeared in binary-0.6.* and have been brought back in this release, making it easier to transition from binary-0.5.

Code

The package is available on hackage.
The code is available on github.

Contributors since the last Haskell Platform version:

  • Lennart Kolmodin
  • Johan Tibell
  • Bryan O'Sullivan
  • Bas van Dijk
  • Paolo Capriotti
  • Eyal Lotem
  • Gabor Greif

Sunday, January 9, 2011

About binary

About a year ago I started hacking on a project to change the Get monad of the binary package. I've been working off and on since... :) The change will allow us to incrementally provide the Get monad with input, as well as allow backtracking and a more graceful error handling. The code was public, but got moved with the haskell.org infrastructure changes. It's now available again;
darcs get http://code.haskell.org/~kolmodin/code/binary/
Johan Tibell writes about the API update in his recent blog post. Developers familiar with attoparsec code will largely be familiar with the new binary Get monad too, as it's been a heavy influence. The type for the parse function would essentially be something like this:
data Result r =
    Fail ByteString [ByteString] -- an error msg and a trace
  | Partial (ByteString -> Result r) -- incremental input
  | Done r -- finished!
A few forks of binary tries to address this too, all in their own way. Currently I know of cereal and binary-strict.

Performance

When benchmarking cereal and the new binary package, cereal comes out on top, at the expense of not being able to consume the input in incremental chunks. I couldn't find the benchmark suit for binary-strict. The reason for cereal being faster, I think, is due to that its simpler code when having a simpler state, essentially only a single strict ByteString (the input). In binary (and attoparsec) it's a bit more complicated, due to the incremental input (in combination with supporting MonadPlus):
data S =
  S { input      :: !B.ByteString -- the current input chunk
    , next_input :: !B.ByteString -- saved input to be used when backtracking
    , read_all   :: !Bool -- have we requested all input available to parse?
    } deriving Show

newtype Get a =
  C { runCont :: forall r.
                 S ->
                 Failure   r ->
                 Success a r ->
                 Result    r }

type Failure   r = S -> [String] -> String -> Result r
type Success a r = S -> a -> Result r

bindG :: Get a -> (a -> Get b) -> Get b
bindG (C c) f = C $ \st0 kf ks -> c st0 kf (\st1 a -> runCont (f a) st1 kf ks)
Unfortunately, this results in bad performance. I'm guessing that's it's because it's reconstructing the state value (of type S), and the remaining ByteString input for each value consumed, thus a lot of allocations. So, as an experiment, I manually unpacked S;
-- No longer using S
{-
data S = S { input      :: !B.ByteString
           , next_input :: !B.ByteString
           , read_all   :: !Bool
           } deriving Show
-}

newtype Get a =
  C { runCont :: forall r.
                 -- these three were part of S, now they are separate arguments
                 B.ByteString -> -- 1
                 B.ByteString -> -- 2
                 Bool ->         -- 3
                 Failure   r ->
                 Success a r ->
                 Result    r }

type Failure   r = B.ByteString -> B.ByteString -> Bool -> [String] ->String -> Result r
type Success a r = B.ByteString -> B.ByteString -> Bool -> a -> Result r

bindG :: Get a -> (a -> Get b) -> Get b
bindG (C c) f = C $ \inp next eof kf ks ->
                             c inp next eof kf
                               (\inp' next' eof' a -> runCont (f a) inp' next' eof' kf ks)
With ghc-7, this yields a huge speed boost and reaches about half the speed of the old binary library (and ~30% faster than cereal). Unfortunately, I find the code is less readable and harder to maintain. Maybe it's worth it though. I got a hint to see ghc ticket #1349. Duncan Coutts summarizes the issue, this time about the Put monad. There are a lot of details and examples in those mails, suggesting an extension to GHC to control strictness in the arguments of higher order components. It'd allow us to write the prettier version, yet enjoying the nicer properties of the unpacked version. It's unlikely that we'll see the proposal implemented soon, though. It seems there are four options;
  • Go with the manually unpacked code
  • Drop support for backtracking
  • Drop support for incremental input
  • Find something even better, you're all invited :)

Sunday, January 20, 2008

The Beginning of a New Era

I still remember the event Jobs in Functional Programming, held here in Göteborg, Sweden, the 14th December last year. It was the first event of its kind, an event to attract hackers to employers, paying for you to hack in functional programming languages! Not only were 8 companies represented in some way, 6 of them had people there explaining why you should go to their company and not someone else's :) With over 100 people registered, and about as many as came, it was a joy. Professor John Hughes, well known for his work with Haskell, was the host for the evening. John ended the event with a speech, I got reminded of it by SPJ's mail. Congratulations, btw! :) They both conclude that Haskell has become much more popular, and that it has happened very fast. Five or ten years ago we would not have dreamed of having such an event as we did. Some students found it humorous when John spoke very enthusiastically about Haskell's new won popularity. But think of it this way, twenty years ago few companies would make a fuzz about FP at all. Today C#, one of the most popular languages out there from one of the (the?) biggest computer companies out there, has lots of ideas from FP. There is also F#: Microsoft's newest addition to the .NET family, a somewhat more evolved version of OCaml, adjusted to fit into the .NET world. It's not difficult to make the prediction that Haskell will get even more attention in the near future. The times are really changing. As Hughes said in his speech, this is the beginning of a new era.

Friday, June 1, 2007

Code vs. Coder

I read a blog this morning, which got a few thoughts started (oh noes, not again!). Rich Skrenta argued that code is our enemy while Jeff Atwood replied that the code itself isn't the problem, but that the real problem is what you see in the mirror. This reminded me of the good professor John Hughes (who taught me Haskell 5 years ago!), who has along with Koen Classen and others, been writing on QuickCheck the past years. It has been rewritten a number of times, each time with more features but fewer LOCs (lines of codes). Bearing in mind that counting LOCs is regarded as a measurement of productivity, Hughes asks,
Have we had negative productivity? We do more with less code!
Another example would be in the newly released xmonad 0.2 where Don (dons) Stewart recently blogged about how he with fewer lines of code did more by moving some of the logic into the data structures. He used a technique called zipper (references in the end of his blog entry) and could thus merge two data structures into one, eliminating the problem of keeping them concise. Easier to write the code and less risk of writing bugs. Did he have negative productivity too? I would have to say no. Written code has to be maintained by someone, depending on what it was written to do. When GHC 6.6 was released the Gentoo Haskell Herd got busy as roughly half of the Haskell packages only compiled with GHC 6.4.2 (about 16 broken out of 40). Naturally we wanted them to work with GHC 6.6 too. It is hard to foresee what will change, no matter how you write the code, thus giving Skrenta a point. Code is the enemy. Reason for the broken packages (2) mostly was (in no particular order)
  • deprecated modules and classes
  • functionality moved to other modules
  • changed type signatures
  • changes in the type system?
  • changes in the build system
  • UTF8 trouble
All this makes me extra jumpy when Erik Hellman, a Java Consultant in Göteborg, blogs about when a 1400 lines game written in 50 min (in Swedish) which was later presented at the JavaOne conference. 500 lines are auto generated getters/setters, along with 900 manually written lines. Assuming writing the manual lines took 40 min, he wrote a line about every three seconds non-stop. I hardly think though that I even could write 900 lines in 40 min even by heart. He agrees, and notes that this is not possible without an IDE, in which case he recommends IntelliJ IDEA 6.0. Maybe Hellman is an extremely talented hacker and that his tools indeed are excellent, thus it's not possible to write the code in less than 1400 lines (in Java) and that it really should have taken some other programmer the whole day. Possibly the job has been solved and no one has to touch the code ever again. Personally though, I would be more interested to see how the game would evolve in 50 hours, days, or weeks. I guess it depends on what you want your app to do. Still impressive to write a game like that in 50 min, but 1400 LOCs? Hellman replies by saying that we are on the same track, and that we should see the other guy's code. How much logic can you write in 40 min? The resulting massive amount of code does not seem to be a problem. I guess that we just have different backgrounds. I have not yet had a look at the code, which recently was put online, but I do hope that the IDE will help to debug and maintain it. This raises the question of how many lines it would be in Haskell, given the corresponding libraries exists. Anyone want to give it a try?

Monday, April 2, 2007

xmonad

If you've followed #haskell@freenode the last month, you've had to try hard to not hear about heard about xmonad. It's a lightweight window manager, a clone of dwm, similar to ion, ratpoison, wmii and larswm. To be more precise it's a tiling window manager which means that it makes sure that no windows are overlapping. It can take a while to get used to, but it's well worth the effort. While xmonad still is relatively easy to learn, the Ion manifest still applies. It will help you to put down the mouse. The impressive part is that while one of dwm's goals is to be <2000 lines of C, xmonad has similar features in <400 lines of Haskell. It also does a couple of things dwm doesn't, namely automated testing of the internal window manager properties (with QuickCheck) and support for xinerama. (The lack of xinerama is, otoh, listed as a feature of dwm). See a screenshot here. It's not yet released, so you'll have to "darcs get" the sources, instructions on the homepage. Unless you're running Gentoo Linux with the haskell overlay, in which case a "emerge xmonad-darcs" will do. Give it a try and give feedback on #haskell!