• Blog
  • Archive
  • Github
  • Research
  • About
  • RSS
  • GADTs in API Design

    April 30, 2013haskell

    Right after my hoopl experience in February, I meant to write more about GADTs and how I have found them to fit into API design. Obviously, I got a bit distracted. Broadly speaking, before I get into too many details, GADTs are very powerful, but seem most useful within a module and not exposed to clients of an API. I will try to detail a few reasons why I came to feel that way, though obviously I might change my mind one day.

    Introduction to GADTs

    Before I start, I will introduce the basic idea of GADTs, as far as I understand them. For more details, see the GHC manual, which also links to some relevant papers on the underlying type theory. GADTs are Generalized Algebraic Data Types – as the name implies, they are a generalization of normal Algebraic Data Types (ADTs). In what way are they more general? Consider a normal Haskell ADT:

    data Example = E1 Int Int
                 | E2 Double
                 | E3 String

    This declaration creates three functions (constructors):

    E1 :: Int -> Int -> Example
    E2 :: Double -> Example
    E3 :: String -> Example

    The important thing to note is that all three have the same return type: Example. The generalization of GADTs allows you to define data types whose constructors return different types. For example:

    {-# LANGUAGE GADTs, DataKinds #-}
    
    data ETag = Numeric | Stringlike
    
    data Example tag where
      E1 :: Int -> Int -> Example Numeric
      E2 :: Double -> Example Numeric
      E3 :: String -> Example StringLike

    Note that the first two constructors return one type, while the third constructor returns a different type. How is this useful? Consider a function like this:

    example1 :: Example Numeric -> Int
    example1 (E1 i1 i2) = i1 + i2
    example1 (E2 d) = floor d

    Compiled with -Wall, this function will produce no warnings. The compiler can prove that it has full case coverage because (1) Example is a GADT and (2) only the E1 and E2 constructors have the Numeric tag. Since example1 takes only Example instances with the Numeric tag, it covers all cases. Furthermore, adding a case for E3 to example1 would be an error. I believe this works because the act of pattern matching on a GADT constructor introduces new constraints (derived from the list of constructor return types in the GADT declaration) to the type checker. Consider:

    driver1 :: Example a -> Int
    driver1 e =
      case e of
        E1 _ _ -> example1 e
        E2 _ -> example 1 e
        E3 _ -> 0

    Obviously, this is a contrived example. But notice that driver1 is given only an Example a – the tag is unknown. In the first two cases, though, the pattern match introduces a new constraint, tag == Numeric, because the GADT declaration says that the only way to construct an E1 or E2 yields an Example Numeric. At their core, GADTs let you make your code more type safe. Consider that the alternative to example1 without GADTs is something like:

    example2 :: Example -> Int
    example2 (E1 i1 i2) = i1 + i2
    example2 (E2 d) = floor d
    example2 _ = error "Impossible"

    There are other variants:

    • You could omit the last case and just ignore the warning about an unhandled case.

    • You could replace the placeholder with an actual case for E3, still calling error.

    The problem with example2 is that, if you add another constructor E4 that should be handled, you get no warning. The catch-all case takes care of it by erroring out, whether or not you wanted that. The first alternative is annoying during development, but may help catch such errors. On the other hand, it may add, in aggregate, enough extra warnings that you simply miss one more when you add a new constructor. The second alternative is reasonable and can help catch forgotten cases when adding a new case. However, it can be extremely inconvenient for data types with many constructors (say, twenty or so).

    Disadvantages

    So GADTs let us make code more type safe. We always like that, so why not use them everywhere? There are a few conveniences you give up by turning your data type into a GADT:

    • You cannot use record selectors on GADT values. You can declare GADT records, but you must extract values through pattern matching.

    • Record fields cannot share names unless they also share a return type in the GADT constructor. This is not really an extra constraint, in a sense, because you could not do that with ADTs anyway (where all constructors shared a single return type. It is still annoying, though, and one reason I think I will avoid them in public APIs.

    • Pattern matching on a GADT (the only way to extract information) requires the -XGADTs extension to be enabled at the pattern match site. This means that the implementation detail (that you used GADTs) leaks out to all clients of the code. This is another reason that I am less than keen to design a public API around them.

    • GADTs can also complicate type inference and introduce more cases where explicit type signatures are required. I like adding type signatures anyway, but code typed into ghci could also be affected negatively.

    My GADT experiment

    That was a quick introduction to GADTs - there are more details in the GHC manual. Now I will explain a less contrived example, reduced from my experiment with GADTs in a larger API. This experiment was inspired by my experience with hoopl, which uses GADTs to classify CFG nodes as being open or closed on entry and exit. I covered some of those details in my hoopl post. The important point is that terminator instructions (i.e., instructions that can only come at the end of a basic block) have a type-level GADT tag. In hoopl, this had some nice properties and I saw a few places in the rest of my code. For background, most of my code analyzes programs represented in the LLVM IR, which is a three-address code in SSA form. It can be thought of as a simple abstract instruction set with arithmetic operations, memory operations, and control flow operations. For the sake of argument, assume a simplified LLVM IR ADT like this:

    data Instruction =
        AddInst Value Value
      | LoadInst Value
      | StoreInst Value Value
      | BranchInst BasicBlock

    Here, BranchInst is a terminator instruction. As in hoopl, I have a number of functions that operate only on terminator instructions (and also a few other general classes of instruction). As in the examples in part one of this post, I would like to write my functions that work only on terminator instructions without resorting to partial functions or catch-all cases. My experiment began with converting my Instruction data type to a GADT:

    data ITag = Terminator | Other
    data Instruction tag where
      AddInst :: Value -> Value -> Instruction Other
      LoadInst :: Value -> Instruction Other
      StoreInst :: Value -> Value -> Instruction Other
      BranchInst :: BasicBlock -> Instruction Terminator

    So far, so good. As before, this works fine and I can write convenient functions over just Terminator instructions with no catch-all cases or partial pattern matches.

    analyzeTerminator :: Instruction Terminator -> Result
    analyzeTerminator (BranchInst _) = ...
    
    analyzeInstruction :: Instruction a -> Result
    analyzeInstruction i =
      case i of
        t@BranchInst _ -> analyzeTerminator t
        _ -> ...
    
    analyzeFunction :: [Instruction a] -> Result
    analyzeFunction = foldr analyzeInstruction mempty

    Notice that I can pass any type of Instruction to analyzeInstruction and still recover the tag by pattern matching. analyzeFunction also type checks and I can analyze an entire function easily. However, analyzeFunction raises an important question: how can I create this [Instruction a]? The constructors for the Instruction type return one of two types: Instruction Other and Instruction Terminator. Neither of those is Instruction a. This would be easy to do with an existential wrapper around Instruction:

    data AnyInstruction = forall a . AI (Instruction a)

    Unfortunately, that is somewhat inconvenient. It adds an extra layer to pattern matches everywhere in the code just to support (poorly) the few cases where I would want to handle only a very specific subset. Being curious, I decided to experiment with a few GHC type system extensions to see if I could create a [Instruction a] somehow. That brought me to -XImpredicativeTypes (-XImpredicativeTypes). This is a pretty heavy extension that essentially allows you to put an existential quantifier anywhere (even inside of a data type – like []). This worked, after a fashion, but ended up breaking type inference pretty badly. Not quite the solution I was looking for.

    After that I got slightly desperate. I decided to try the ultimate hammer: unsafeCoerce. As the name implies, it is very unsafe, but it allows you to convert any Haskell type to any other Haskell type. In general, that makes no sense. However, there is a note in the documentation for unsafeCoerce that states that conversions between types with “the same representation” are safe (in GHC). The documentation explicitly mentions phantom types, but GADTs do not seem that different (they just introduce new constraints whenever pattern matched on). This was the intuition behind wanting a [Instruction a] in the first place. I do not know if the representations of GADT values with different return types are guaranteed to be the same, but constructing my list with unsafeCoerce actually worked and ran without crashing. So it works but feels kind of wrong.

    If that had been the only hangup, I think I would have bitten the bullet and converted my data type to use GADTs. However, a few minor problems convinced me that it probably was not worth the effort. First, I ended up needing two type tags, which just started making type signatures long and slightly unwieldy. Second, I was actually using record syntax to create fields that were shared between all constructors (for example, a unique identifier field). This ran afoul of the GADT restriction where record labels can be shared only between constructors of the same return type. That was no longer the case with my preponderance of type-level tags. This problem was not insurmountable, but it was tedious to work around. Finally, not being able to use record field names as selectors was fairly annoying, though it would have been less useful since I had to rename so many. I did not finish the conversion so I do not know how badly type inference would have been impacted, but I imagine I would have had to write a few more local type signatures.

    Conclusion

    Between this rather involved experiment and my experience with hoopl, I feel like GADTs are very powerful and useful, but perhaps best confined to a single module (or a reasonably self-contained API). The extra safety available from the type-level tags was useful, but having it exposed everywhere was problematic for me this time. I will certainly try it again, and hopefully I will find more cases where GADTs help more than they hurt.

    Comment
  • A New RSS Reader

    March 18, 2013linux

    Luckily, I did not write an RSS reader - I just started using a new one. I had been using newsbeuter (the mutt of new readers). It is actually pretty nice and I do not have any serious complaints about it. It has some speed issues when I compile it myself versus using debian packages, but presumably that is my fault somehow.

    The recent Google Reader kerfuffle got me interested in reviewing the other available options. One of my big complaints about most applications is that they do not have useful key bindings, so that was a prime requirement. newsbeuter did a fine job there, of necessity. It turns out that most of the web-based feed aggregators do this well, while many of the desktop-based ones do not. I decided that looking at web-based readers might not hurt very much. Having my articles synced between my devices and machines also held some minor appeal.

    Without actually looking very hard, I tried two: pulse and feedly. I had used pulse on my phone before and the UI there is actually very nice. I tried to set up an account and add some feeds and I found that to be fairly annoying. At least it did not require me to log in with facebook. It did, however, give me three feed categories with the same name but seemingly different contents that I couldn’t manage or delete. I stopped there with pulse. feedly was easy to log in to since it still uses Google as an oauth provider. Managing feeds was easier and it actually worked. There is an Android application, which is somewhat quirky. I think it is quirky in a good way - time will tell.

    While I normally dislike web applications, I feel like this is probably the most reasonable web application possible. If I do not have an internet connection handy, I cannot really use an RSS reader anyway. The main consequence of this experiment was unrelated to the reader I ended up with: I re-evaluated the list of sources I was checking. I used to typically check a few RSS feeds that I always checked and then a few web pages that I periodically checked for updates manually besides them. That never made much sense but I was too lazy to update my list of sources. Now I have a whole lot more in the RSS queue and a lot less reason to actually browse the internet randomly. We shall see how that goes.

    The only disadvantage of sourcing a larger number of interesting news sources is that I had to mark the same story about the EA CEO resigning about ten times. I suppose that is the price I have to pay for progress.

    Comment
  • Terminal Fonts and Vim

    March 8, 2013linux, vim

    It looks like my problem with bold terminal fonts was a false alarm. The vim theme I was testing with (Tomorrow) seems to only use bold keywords in gvim. Zenburn properly uses bold in the terminal.

    Comment
  • Font Problems

    March 6, 2013linux, fonts

    I’ve been using Adobe Source Code Pro as my primary font for a while. The other day I tried to update from 1.013 to 1.017 for no particular reason; this caused some Problems. As soon as I switched, the font looked awful in my terminals (no matter how many times I rebuilt the font cache). It still looked fine in GUI programs (e.g., gvim). I am not a font expert, but it seems like a new weight was added in this release: medium. Somehow this became the default weight, while the regular weight was much thinner. The default was more bold than I was used to.

    I ended up having to revert. We will see what happens with the next released version. This font is still one of my favorites - it has a distinctive yet clean look. My only complaint is that the zero is dotted and not slashed. I also like Consolas, but the license of Source Code Pro is much nicer.

    Speaking of fonts, I also seem to have trouble getting bold fonts in my terminal vim. I will probably have to spend an afternoon some day debugging everything font related.

    Comment
  • hasksyn: Haskell Syntax for Vim

    March 1, 2013vim, haskell

    During my (ongoing) vim experiment, I found myself missing some of the amenities of haskell-mode for emacs. Namely great syntax highlighting and pretty good indentation. vim2hs looked reasonably nice, but did not handle indentation. It also made scrolling very slow, perhaps because it just does too many fancy things. haskellmode-vim had slightly worse syntax highlighting and was also a bit slow; it also did not have much of an indentation story.

    In response, I wrote hasksyn, which has both good syntax highlighting and pretty good indentation. Indentation is tricky in vim, I can appreciate that now, but I think I did a reasonable job.

    Features

    • Align pipes, commas, and let bindings
    • Indent after trailing operators, where, and case ... of
    • Snap in to match its corresponding let
    • De-indent after catchall cases and returns (optional)

    Most importantly, comments and strings that happen to contain Haskell keywords will not confuse the indentation. I threw in a new cabal highlighting mode, too, because I got a bit annoyed that “keywords” were being highlighted in my package descriptions and module names.

    Comment