Victor Nazarov wrote:
The goal of the code is to implement the evaluator for untyped lambda-calculus.
The main problem is how to display each step of reduction? More
detailed questions follows in the end of the code.

Terms of lambda-calculus is the following data-type:
data (Binder bndr) =>
  Term bndr =
    Var bndr
    | Lam bndr (Term bndr)
    | App (Term bndr) (Term bndr)


Next we'll define the evaluator. Evaluator returns the WHNF for each
well-formed term, or _|_ if there is no WHNF

Straight forward version:

whnf :: Term bndr -> Term bndr
whnf = reduce []
 where reduce (a:as) (Lam b i) = reduce as $ subst b a i
       reduce as (App f a) = reduce (a:as) f
       reduce as term = foldl term App as

But our goal is to perform only one step of reduction. And to display term
after each reduction

Is there a more effective way to perform this? In printReductions I have
to rescan the syntax tree over and over again to find the redex. Is there a
way to save the position in the tree, to print the tree and then to resume
from saved position?

Yes, there is: you can use a zipper

  http://en.wikibooks.org/wiki/Haskell/Zippers

Here's how:

  data Branch v = AppL (Term v)
                | AppR (Term v)
                | Lamb v
  type Context v = [Branch v]
  type Zipper v  = (Context v, Term v)

  whnf :: Term v -> Term v
  whnf t = whnf' ([],t)
     where
     whnf' (AppL e':cxt, Lam x e) = whnf' (cxt       , subst x e' e)
     whnf' (cxt        , App f e) = whnf' (AppL e:cxt, f)
     whnf' z                      = unwind z

  unwind :: Zipper v -> Term v
  unwind ([]        , t) = t
  unwind (AppL e:cxt, f) = unwind (cxt, App f e)
  unwind (AppR f:cxt, e) = unwind (cxt, App f e)
  unwind (Lamb x:cxt, e) = unwind (cxt, Lam x e)

Note how this whnf and your whnf are pretty much exactly the same. In fact, your stack of pending applications (the as in reduce' as ) is already a zipper. The only difference here is that we are no longer limited to only walk down the left spine and can now implement other reduction strategies like nf or wnf too.


Concerning the problem of printing intermediate steps, I don't quite understand your approach. I'd simply use a Writer monad to keep track of intermediate terms

  import Control.Monad.Writer

     -- use a difference list or something for better performance
  type Trace v = [Zipper v]

  whnf :: Term v -> Writer (Trace v) (Term v)
  whnf t = whnf' ([],t)
     where
     whnf' (AppL e':cxt, Lam x e) = tell  (cxt, App (Lam x e) e') >>
                                    whnf' (cxt       , subst x e' e)
     whnf' (cxt        , App f e) = whnf' (AppL e:cxt, f)
     whnf' z                      = return $ unwind z

The definition of whnf is basically left unchanged, except that a redex is recorded via tell whenever a beta-reduction is about to be performed. The recorded terms can be printed afterwards

  printTrace :: Writer (Trace v) (Term v) -> IO ()
  printTrace w = let (t, ts) = runWriter t ts
    putStrLn . unlines . map show $ ts

Note that "zipped" terms are recorded, i.e. the redex enclosed in its context and you can even highlight the redex while printing.


Last but not least, there is a nice introductory paper about the many possible reduction strategies for lambda calculus

  http://www.itu.dk/people/sestoft/papers/sestoft-lamreduce.pdf


Regards,
apfelmus

_______________________________________________
Haskell-Cafe mailing list
[email protected]
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to