How to enumerate a recursive datatype in Haskell?

后端 未结 4 1405
北荒
北荒 2020-12-01 12:57

This blog post has an interesting explanation of how to use the Omega monad to enumerate an arbitrary grammar diagonally. He offers an example of how to do so, resulting in

4条回答
  •  执念已碎
    2020-12-01 13:57

    Below is a terrible solution, but perhaps an interesting one.


    We might consider the idea of adding "one more layer"

    grow :: T -> Omega T
    grow t = each [A, B t, C t t]
    

    which is close to correct but has a flaw—in particular, in the C branch, we end up having both of the arguments take the exact same values instead of being able to vary independently. We can fix this by computing the "base functor" of T which looks like this

    data T    = A  | B  T | C  T T
    data Tf x = Af | Bf x | Cf x x deriving Functor
    

    In particular, Tf is just a copy of T where the recursive calls are functor "holes" instead of direct recursive calls. Now we can write:

    grow :: Omega T -> Omega (Tf (Omega T))
    grow ot = each [ Af, Bf ot, Cf ot ot ]
    

    which has a whole computation of a new set of T in each hole. If we could somehow "flatten" the Omega (Tf (Omega T)) into Omega T then we'd have a computation which adds "one new layer" to our Omega computation correctly.

    flatten :: Omega (Tf (Omega T)) -> Omega T
    flatten = ...
    

    and we could take the fixed point of this layering with fix

    fix :: (a -> a) -> a
    
    every :: Omega T
    every = fix (flatten . grow)
    

    So the only trick is to figure out flatten. To do this we need to notice two features of Tf. First, it's Traversable so we can use sequenceA to "flip" the order of Tf and Omega

    flatten = ?f . fmap (?g . sequenceA)
    

    where ?f :: Omega (Omega T) -> Omega T is just join. The final tricky bit is figuring out ?g :: Omega (Tf T) -> Omega T. Obviously, we don't care about the Omega layer so we should just fmap a function of type Tf T -> T.

    And this function is very close to the defining notion for the relationship between Tf and T: we can always compress a layer of Tf on the top of T.

    compress :: Tf T -> T
    compress Af         = A
    compress (Bf t)     = B t
    compress (Cf t1 t2) = C t1 t2
    

    All together we have

    flatten :: Omega (Tf (Omega T)) -> Omega T
    flatten = join . fmap (fmap compress . sequenceA)
    

    Ugly, but all together functional.

提交回复
热议问题