I'm using Haskell to interpret a dynamically-typed language. I have a sum type called Value
which can represent some basic objects:
data Value
= Integer Integer
| Float Double
| Char Char
| List [Value]
deriving Show
(For now I'm not adding a special case for strings - they'll just be lists of characters.)
I have instantiated Read
for it, so that I can parse string inputs into Value
s as though they're normal Haskell values.
While for this demo I just derived Show
using the default implementation, I had to define Read
manually, because I don't want the input to need the data constructors prefixed before every element. (I would have to write input as Integer 43
instead of just 43
, for example)
I implement readsPrec
(which seems a needlessly complex method to have as the minimal typeclass implementation, but it's what Haskell requires...) in terms of Haskell's default implementations for the underlying data types.
readsPrec
takes a precedence value, which I just pass on to the readsPrec
of other types; it returns a list of possible parses as tuples of (value, restOfString)
.
The basic algorithm I use is to attempt to parse the input into each type, and choose the first result from each. Unfortunately there's quite a lot of plumbing required to deal with the list of tuples produced by readsPrec
.
instance Read Value where
readsPrec precedence s =
-- `justs` stops the parsing once all of the read attempts are failing
justs $ map (foldl orElse Nothing) $ transpose attempts
where
-- try to parse the input into each of these types, one at a time
attempts = [u Integer, u Float, u Char, u List]
u constructor = maybes [(constructor val, rest) | (val, rest) <- readsPrec precedence s]
This implementation also uses a couple of utility functions:
-- | Create an infinite list of Maybes, where the elements in the input
-- | are `Just`s, and everything after that is a `Nothing`
maybes xs = map Just xs ++ repeat Nothing
-- | Collect the values of all Justs at the start of a list
justs (Just x : xs) = x : justs xs
justs _ = []
I want to know if my code, especially the plumbing around readsPrec
, can be made more idiomatic, perhaps using some more builtin functions. Maybe the whole algorithm can be simplified.
I also don't like the fact I'm using a constant-sized list for the attempts. I'd like to be able to just use a tuple, but if I ever wanted to add more data types, I'd have to change things like unzip4
to unzip5
, because I can't write it polymorphically over tuple size. (where unzip
would be used for tuples, in place of transpose
for lists)
I'm not really interested in whether the minutiae of my syntax style are "correct".