    module Haskore.General.LetRec where

    import Data.Tuple.HT (mapFst, mapSnd, )
    import qualified Data.Map as Map

    data Expr =
        Const String
      | Append Expr Expr
      | Var Var
       deriving (Show)

    type Var = Int
    type Count = Int

    knot ::
       (Count, ([Expr], (Expr, a)) -> ([Expr], (Expr, b))) ->
       (Count, ([Expr], a) -> ([Expr], b))
    knot (count, f) =
       (succ count,
        \(equs0, a) ->
           let (equs1, (rhs, b)) = f (equs0, (Var count, a))
           in  (rhs : equs1, b))

    beginKnot ::
       (a -> b) ->
       (Count, ([Expr], a) -> ([Expr], b))
    beginKnot f =
       (0, mapSnd f)

    endKnot ::
       (Count, ([Expr], a) -> ([Expr], b)) ->
       (a -> ([Expr], b))
    endKnot f a = snd f ([], a)

    exampleLet ::
       (Expr, (Expr, ())) ->
       (Expr, (Expr, Expr))
    exampleLet (a,(b,())) =
       (Append (Const "ab") b,
        (Append (Const "c") a,
         a))

    {-
    Maybe we can replace manual repeated application of 'knot'
    by a type class method.
    -}
    exampleEqus :: ([Expr], Expr)
    exampleEqus =
       mapFst reverse $
       endKnot (knot (knot (beginKnot exampleLet))) ()

    exampleResult :: String
    exampleResult =
       let mapExpr = Map.fromAscList $ zip [0..] $ fst exampleEqus
           resolve x =
              case x of
                 Const str -> str
                 Append a b ->
                    resolve a ++ resolve b
                 Var n -> Map.findWithDefault
                    (error $ "unknown variable id " ++ show n ++ " - bug in 'knot'?")
                    n mapRes
           mapRes = fmap resolve mapExpr
       in  resolve $ snd exampleEqus