简体   繁体   中英

How to model this recurisve structure in Haskell?

I am trying to model the kdb/q "atoms and lists" through Haskell type system.

In kdb/q all data is built from atoms. An atom is an irreducible value of a specific data type. Int, boolean and char are examples of atoms. Lists are ordered collections that are build from atoms. Since q is a vector language, most of the built-in operations are atomic, and therefore it recurses into the argument structure until it gets to atoms.

For example:

(1;2;3) is a simple list of integers 1, 2, 3

(1.0;2;(3;4;5)) is a general list of 1.0(float), 2(int), and a simple int list (3;4;5)

neg is a function that negates one number. For example:

neg 1 yields -1

neg -1.0 yields 1f

neg (1.0;2;(3;4;5)) yields (-1f;-2;(-3;-4;-5)).

This is what inspired me to try to model this behavior in Haskell types. The data type should consist of atom type, and a list.

The following is a simplified version of what I have so far. And I also went further to try to make it an instance of Foldable and Traversable.

data Atom = I Int
          | C Char
          | D Double 
          deriving Show

data Q a = QAtom a 
         | QList [Q a]
         deriving Show

instance Functor Q where
    fmap f (QAtom a) = QAtom (f a)
    fmap f (QList qs) = QList $ fmap (fmap f) qs

instance Foldable Q where
    foldMap f (QAtom a) = f a
    foldMap f (QList qs) = mconcat $ fmap (foldMap f) qs

instance Traversable Q where
    sequenceA (QAtom fa) = fmap QAtom fa
    sequenceA (QList []) = pure $ QList []
    sequenceA (QList (qfa:qfas)) = concatL <$> (sequenceA qfa) <*> (sequenceA (QList qfas))
        where
            concatL (QAtom a) (QList qas) = QList ((QAtom a):qas)

This is what I have and it compiles but I don't particularly like the concatL function that doesn't cover all the patterns according to the type. Once I start adding a new value constructor QDict [(Q Atom, Q a)] to Q, this just gets even worse.

Did I model the original data correctly? Should I even try to make it Traversable? However I thought Traversable is necessary if I need to use the data type with Maybe or Either to model errors.

Any advice is appreciated.

EDIT: edited q code formatting

The compiler knows how to automatically derive a Traversable instance for your types. If you do :set -ddump-deriv -dsuppress-all -XDeriveTraversable -XStandaloneDeriving and then deriving instance Traversable Q , you can see the "right" answer. If you take that knowledge and apply it to your instance, you'd get this:

instance Traversable Q where
    sequenceA (QAtom fa) = fmap QAtom fa
    sequenceA (QList qfas) = fmap QList (traverse sequenceA qfas)

Or if you want to avoid traverse in favor of sequenceA :

instance Traversable Q where
    sequenceA (QAtom fa) = fmap QAtom fa
    sequenceA (QList qfas) = fmap QList (sequenceA (fmap sequenceA qfas))

The key is that lists themselves are Traversable , so you can call sequenceA on them without having to re-wrap pieces of them in your own type.


Side note, in your Foldable instance, instead of chaining mconcat and fmap , just use foldMap again, since lists are Foldable too:

instance Foldable Q where
    foldMap f (QAtom a) = f a
    foldMap f (QList qs) = foldMap (foldMap f) qs

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM