How do I use the Church encoding for Free Monads?

前端 未结 4 759
别跟我提以往
别跟我提以往 2021-02-08 10:49

I\'ve been using the Free datatype in Control.Monad.Free from the free package. Now I\'m trying to convert it to use F in

4条回答
  •  北海茫月
    2021-02-08 11:16

    It's a bit of a nasty one. This problem is a more general version of a puzzle everyone struggles with the first time they're exposed to it: defining the predecessor of a natural number encoded as a Church numeral (think: Nat ~ Free Id ()).

    I've split my module into a lot of intermediate definitions to highlight the solution's structure. I've also uploaded a self-contained gist for ease of use.

    I start with nothing exciting: redefining F given that I don't have this package installed at the moment.

    {-# LANGUAGE Rank2Types #-}
    module MatchFree where
    
    newtype F f a = F { runF :: forall r. (a -> r) -> (f r -> r) -> r }
    

    Now, even before considering pattern-matching, we can start by defining the counterpart of the usual datatype's constructors:

    pureF :: a -> F f a
    pureF a = F $ const . ($ a)
    
    freeF :: Functor f => f (F f a) -> F f a
    freeF f = F $ \ pr fr -> fr $ fmap (\ inner -> runF inner pr fr) f
    

    Next, I'm introducing two types: Open and Close. Close is simply the F type but Open corresponds to having observed the content of an element of F f a: it's Either a pure a or an f (F f a).

    type Open  f a = Either a (f (F f a))
    type Close f a = F f a
    

    As hinted by my hand-wavy description, these two types are actually equivalent and we can indeed write functions converting back and forth between them:

    close :: Functor f => Open f a -> Close f a
    close = either pureF freeF
    
    open :: Functor f => Close f a -> Open f a
    open f = runF f Left (Right . fmap close)
    

    Now, we can come back to your problem and the course of action should be pretty clear: open the F f a and then apply either kp or kf depending on what we got. And it indeed works:

    matchF
      :: Functor f
      => (a -> r)
      -> (f (F f a) -> r)
      -> F f a
      -> r
    matchF kp kf = either kp kf . open
    

    Coming back to the original comment about natural numbers: predecessor implemented using Church numeral is linear in the size of the natural number when we could reasonably expect a simple case analysis to be constant time. Well, just like for natural numbers, this case analysis is pretty expensive because, as show by the use of runF in the definition of open, the whole structure is traversed.

提交回复
热议问题