

data Foo = Foo String Bool | Bar Foo



instance BinaryDefer Foo where

bothDefer = defer

[\ ~(Foo a b) -> unit Foo << a << b

,\ ~(Bar a) -> unit Bar << a

]



must not

must



data Bool = False | True



instance BinaryDefer Bool where

bothDefer = defer

[\ ~(False) -> unit False

,\ ~(True) -> unit True

]





instance BinaryDefer Bool where

bothDefer = defer

[\ ~(x@False) -> unit False <<! x

,\ ~(x@True) -> unit True <<! x

]





data PendingNone

data PendingSome



unit :: a -> Pending a PendingNone

(<<) :: BinaryDefer a => Pending (a -> b) p -> a -> Pending b PendingSome

(<<~) :: BinaryDefer a => Pending (a -> b) p -> a -> Pending b PendingSome

(<<!) :: Pending a p -> a -> Pending a PendingSome



As part of my work on Hoogle 4 I've been bashing my deferred binary library quite a lot. I encountered one bug which I hadn't considered, and was able to come up with a solution, using phantom types. I've read about phantom types before, but never actually put them into practice!The idea of the deferred binary library is that writing instances should be as short as possible - they don't need to be massively high performance, and I wanted to see if I could do it with minimal boilerplate. Each instance needs to be able to both save and load a value - which means it needs to do some sort of pattern-match (to see which value it has), and have the constructor available (to create a new value). The shortest scheme I was able to come up with is:The idea is that to save a value, you step through the list trying to match each constructor, using Control.Exception.catch to see if the value does not match. Once you have matched, you save an index, and the components. To load a value, you take the index and pick the correct construction. This time, you deliberately do not mention any of the fields, and by remaining lazy on the fields, the pattern match is not invoked. This solution took quite a lot of thought to arrive at, but does minimize the size of the instance declarations.The instance relies crucially on the laziness/strictness of the functions - the load functionexamine any of its arguments (or you will pattern-match fail), the save functionexamine at least one argument (to provoke pattern-match failure). After writing a description such as this, I am sure some users will have spotted the issue!The problem here is 0-arity constructors, the load code works just fine, but the save code has no fields it can demand to ensure the correct constructor has been reached. I encountered this bug in Hoogle 4 - it is subtle, and in a pile of instance rules, these 0-arity cases can easily be overlooked. The solution is to introduce a new operator, (<