The following is some Haskell code in which it is shown by way of an example how substitution in the presence of variable binding operators can be defined using only structural recursion. I haven't got a description of what is going on categorically. However, the right-hand side of the definition of `L' below is essentially a endofunctor on a certain category of set-valued functions, and `L' is its initial algebra. The domain of these functions can be any family of sets closed under `Maybe' - for example the von-Neumann natural numbers. The question of picking the right morphisms to make the members of this family into the objects of a category (and L into the initial object for a functor on the presheaves over that category) is a bit too abstruse for me at the moment. I leave it as an exercise for the categorically obsessed. The simple constructions here are strikingly close to the notions formalised in Pierre Lescanne's $\lambda\upsilon$ calculus of explicit substitutions. See: ||. > module Lambda_calculus (L(Var,App,Abs), > i_term,b_term,c_term,w_term,k_term,s_term,y_term,fix_term) where A data-type for lambda-expressions, de-Bruijn style. The parameter is a type of names for variables. > data L x = Var x > | App (L x) (L x) > | Abs (L (Maybe x)) > deriving (Show,Read) -- just for testing Like any other sum-of-products positive data-type, L is a functor in the sense of acting on arrows. To define this, we need polymorphic recursion, as supported in Haskell. Basically, the functor renames free variables. > instance Functor L where map = mL > mL :: (x -> y) -> L x -> L y -- need an explicit type assertion > mL f z = case z of > Var x -> Var (f x) > App e1 e2 -> App (mL f e1) (mL f e2) > Abs e -> Abs (mL f' e) > where -- note how bound variables are protected > f' = map f -- Maybe is itself a functor Like any other leafy data-type, L is a monad. The bind operator is simultaneous substitution. (`Leafy data type': |f x = mu (\ y -> x + g y )|. See Bird and de Moor, Algebra of Programming, exercise 2.40.) > instance Monad L where > return a = Var a > e >>= f = case e of > Var x -> f x > App e1 e2 -> App (e1 >>= f) (e2 >>= f) > Abs e -> Abs (e >>= f') > where -- note how capture of frees is avoided > f' w = case w of Nothing -> Var Nothing > Just x -> map Just (f x) Conventional one-point substitution. As we have a monad, we can exploit Haskell's "do" notation. > subst1 :: L (Maybe x) -> L x -> L x > subst1 c e = do z <- c > case z of Nothing -> e > Just x -> Var x - An application We define a partial function which reduces lambda-calculus expressions to WHNF. (In 4 lines of code!) A helper. > app z = case z of Abs e -> evL . subst1 e > _ -> App z Compute WHNF > evL z = case z of App e1 e2 -> app (evL e1) e2 > _ -> z Unfortunately, de Bruijn machinery seems too costly for use in a practical reduction machine. - Some expressions for experimenting with > v0 = Var Nothing > v1 = Var (Just Nothing) > v2 = Var (Just (Just Nothing)) > b_term = Abs (Abs (Abs (App v2 (App v1 v0)))) > c_term = Abs (Abs (Abs (App (App v2 v0) v1))) > w_term = Abs (Abs (App (App v1 v0) v0)) > k_term = Abs (Abs v1) > i_term = Abs v0 > s_term = Abs (Abs (Abs (App (App v2 v0) (App v1 v0)))) > y_term = Abs (App x x) where x = Abs (App v1 (App v0 v0)) > sap = Abs (App v0 v0) > dont_evaluate_me = App sap sap > fix_term = Abs (let x = App v0 x in x) > fix_term, w_term, i_term, k_term, s_term, c_term :: L String