Search code examples
haskellmonadscategory-theoryleancomonad

How to define free monads and cofree comonads in Lean4?


in Haskell we can define both of these in this way:

data Free (f :: Type -> Type) (a :: Type) = Pure a | Free (f (Free f a))
data Cofree (f :: Type -> Type) (a :: Type) = Cofree a (f (Cofree f a))

But attempting the same definition in lean4 gives some kernel errors which I don't understand. I suspect it has something to do with the termination checker but can't be sure because the errors are not clear:

-- error: (kernel) arg #3 of 'Free.free' contains a non valid occurrence of the datatypes being declared
inductive Free (f : Type → Type) (α : Type) where
  | pure : α → Free f α
  | free : f (Free f α) → Free f α

-- (kernel) arg #4 of 'Cofree.cofree' contains a non valid occurrence of the datatypes being declaredLean 4
inductive Cofree (f : Type → Type) (α : Type) where
  | cofree : α → f (Cofree f α) → Cofree f α

Is there something similar to Agda's TERMINATING pragma? Or is this error related to something else?


Solution

  • A variant of the free monad expressible in terminating languages is the following, aka. the operational monad or the freer monad:

    inductive Free (f : Type → Type) (α : Type) where
      | pure : α → Free f α
      | free : ∀ x, f x -> (x -> Free f α) → Free f α
    

    Free comonad variant using the same trick:

    inductive Cofree (f : Type → Type) (α : Type) where
      | cofree : ∀ x, α → f x -> (x -> Cofree f α) → Cofree f α
    

    The problem with the original definition is that its well-formedness requires f to be a functor (which is more restrictive than any Type -> Type) satisfying a "strict positivity" condition (which cannot even be expressed in the type system).

    If that definition were allowed, you could instantiate Free with the type function f a = a -> a, so that Free f empty is isomorphic to the following illegal type (using Haskell syntax below):

    data Bad where
      Bad :: (Bad -> Bad) -> Bad
    

    which is bad because it contains the untyped lambda calculus. It can encode non-normalizable terms such as (\x. x x) (\x. x x):

    selfapply :: Bad -> Bad
    selfapply (Bad x) = x (Bad x)
    
    omega :: Bad
    omega = selfapply (Bad selfapply)
    

    The legal variant above doesn't require f to be a functor. It composes the free monad construction with a free functor (also known in Haskell as Coyoneda), which satisfies the strict positivity condition required of inductive types.

    data FreeFunctor (f :: Type -> Type) (a :: Type) where
      Free :: f x -> (x -> a) -> FreeFunctor f a