Journal de Chaource's Journal
 
[Most Recent Entries] [Calendar View]

Sunday, August 7th, 2016

    Time Event
    6:43p
    Free functors, free applicatives, free monads II
    Part I
    Consider the definitions (1), (2), and (3) of the "free monad" from the previous post,

    Free1 F t = Fixs( t + F s )

    Free2 F t = FixS( t + F t + ∃a. S a * (a -> S t) )

    Free3 F t = Fixs( t + ∃a. F a * (a -> s) )

    Let us rewrite them in Haskell's "long form", that is, as representable functors, while giving the individual type constructors suggestive names:

    data M1 t where
    Pure :: t -> M1 t
    Join :: F (M1 t) -> M1 t

    data M2 t where
    Pure :: t -> M2 t
    Wrap :: F t -> M2 t
    Bind :: ∀a. ( M2 a * (a -> M2 t) ) -> M2 t -- we replace ∃a by ∀a since we are moving it out of a contravariant position

    data M3 t where
    Pure :: t -> M3 t
    Bind :: ∀a. ( F a * (a -> M3 t) ) -> M3 t

    Then we notice two curious things:

    - the type signatures of the "Pure" and "Bind" constructors are very similar to the types of the "pure" and "bind" methods of a monad:

    pure :: ∀t. t -> M t
    bind :: ∀t.∀a. M a -> (a -> M t) -> M t

    The definition of the "free monad" looks almost like listing these two method's type signatures!

    - however, the type "F a" is used in M3 instead of "M a", and, as if to compensate this, there is an extra type constructor I called "Wrap", that injects "F a" into the free monad.

    There is no doubt that all these definitions of the "free monad" are "correct" (i.e. both define a monadic functor). Questions remain: for instance, there are two places in the "bind" method where we use "M t" recursively. What if we wanted to use "F t" instead of "M t" in one of these places (or both)? There are four possibilities, of which two are not yet written down:

    data M4 t where
    Pure :: t -> M4 t
    Bind :: ∀a. ( F a * (a -> F t) ) -> M4 t

    data M5 t where
    Pure :: t -> M5 t
    Bind :: ∀a. ( M5 a * (a -> F t) ) -> M5 t

    Do these work, do they need additional type constructors to work, or do they fail even with additional constructors?

    A partial answer was given in this blog post: http://blog.higher-order.com/blog/2013/11/01/free-and-yoneda/

    The gist of that blog post is that M3 is obtained from M1 by using a "free functor" instead of a given functor F. So let us now look at how a "free functor" works.

    (The "free functor" is sometimes called a "co-Yoneda", but so far I don't think the connection to the Yoneda lemma is at all helpful for understanding the "free functor".)

    Free functors

    A functor is a type constructor F with an additional morphism called "fmap" in Haskell.

    fmap :: ∀t.∀a. F a -> (a -> t) -> F t

    If we have a type constructor F that is not a functor, we can add some more structure around it and define a functor G like this:

    G t = ∃a. F a * (a->t)

    The type constructor G is a functor since it is exp-poly in "t" and uses "t" only in a contravariant position.

    We call the functor G the "free F-functor".

    It is then easy to see that M1 applied to the free F-functor is the same as M3 applied to the type constructor F.

    The "Haskell long form" of the type definition is

    data Ff1 t where
    Ffmap :: ∀a. F a * (a -> t) -> Ff1 t

    Note again the similarity of this and the type signature of "fmap". We just used the product F a * (a->t) -> Ff t instead of writing F a -> (a->t) -> Ff t, so that the implementation of this data type becomes more obvious.

    Let us ask the same questions about the construction of the free F-functor as we asked about the construction of the free F-monad:

    - How to derive this construction systematically?
    - Can we replace "F s" by the recursive application of "Ff s" in the definition of "Ff" above? For instance, what about the alternative definition (inspired by comparing M2 and M3 above):

    data Ff2 t where
    Wrap :: F t -> Ff2 t
    Ffmap :: ∀a. Ff2 a * (a -> t) -> Ff2 t

    Is this definition equivalent to Ff1?

    To find answers, I found it helpful first to consider the concrete implementations of the two versions of the free F-functor according to definitions Ff and Ff2.

    Implementing the free functor

    The "fmap" operation of the free F-functor is defined like this: We have a value (fa*g) of type Ff1t, where fa : F a and g: a->t, and a function value h of type t -> s. We now define fmap on these two values in the only way possible:

    fmapFf1 (fa*g) h = fa * (h ∘ g).

    So the value "fa" inside the product F a * (a->t) remains unchanged, but the morphism g gets composed with h.

    Any value of type Ff1t is therefore either created from scratch, where we supply some value "fa" of type "F a", or is obtained using "fmap", where this value "fa" remains unchanged.

    It is natural to provide a "constructor" that injects a value of type F t into the free F-functor:

    wrap :: F t -> Ff1t
    wrap ft = ftF t * idt->t

    If values of type Ff1t are always made either by this constructor or by fmap, then the initial value "ft" injected into Ff1t will remain unchanged.

    Note that we cannot "unwrap" a value of type Ff1t because, even if we find that this value is of type F t * t->t, we cannot determine whether the function of type t->t is equal to identity.

    Now, consider the second definition of the free F-functor. Now "Wrap" is part of the definition.

    To define the function "fmap" as required for the functor instance, we now need to consider two cases. A value of Ff2t can be either a "wrapped" F t or a "recursive case" Ff2a * (a -> t). For a "wrapped" F t, we first convert it to Ff1t * (t->t), and then "fmap" is defined exactly as in Ff1t.

    If we now create a value of Ff2t using Wrap and then apply some "fmap" to it, we will get a value of the form

    Wrap(F a) * (a -> t).

    If we continue to apply "fmap" to this value, the part "Wrap(...)" will remain unchanged, just as in the case of Ff1t.

    However, now we can define "unwrap" that will distinguish between a value of Ff1t which has just been constructed using "Wrap", and a value of type Ff1t to which one or more "fmap"s have been applied. (The compiler might use this information to optimize the code.)

    The price for this is relatively small: the type Ff2t allows "nested" instances of Ffmap, such as
    Ffmap ( Ffmap (...))
    which cannot be a result of using Wrap or Fmap, but are nevertheless valid values of type Ff2t. In other words, the same value of type Ff2t can be encoded in many different and theoretically equivalent ways. This is a redundancy that, probably, does not actually affect any real-life applications of the free functor.

    As we see, the definitions Ff1t and Ff2t are not fully equivalent; Ff2t is a redundant encoding that in principle allows more optimization.

    To get the best of Ff1t and Ff2t, we can retain Wrap but define the recursive case so that nested instances are not allowed:

    data Ff3 t where
    Wrap :: F t -> Ff3 t
    Ffmap :: ∀a. F a * (a -> t) -> Ff3 t

    Now, Ff3 t has two variants: one, Wrap, to which no "fmap" has been applied, and another, Ffmap, which is a result of applying fmap. The implementation of fmap will have to pattern-match on these two cases, converting a Wrap to an Ffmap.

    -- Haskell
    fmap (Wrap ft) = Ffmap ft id
    fmap (Ffmap fa g)) h = Ffmap fa (h . g)
    unwrap :: Ff3 t -> Maybe (F t)
    unwrap (Wrap ft) = Just ft
    unwrap (Ffmap _ _) = Nothing

    (to be continued)

    << Previous Day 2016/08/07
    [Calendar]
    Next Day >>

Journal de Chaource   About LJ.Rossia.org