9

One can interpret the lambda calculus in Haskell:

data Expr = Var String | Lam String Expr | App Expr Expr

data Value a = V a | F (Value a -> Value a)

interpret :: [(String, Value a)] -> Expr -> Value a
interpret env (Var x) = case lookup x env of
  Nothing -> error "undefined variable"
  Just v -> v
interpret env (Lam x e) = F (\v -> interpret ((x, v):env) e)
interpret env (App e1 e2) = case interpret env e1 of
  V _ -> error "not a function"
  F f -> f (interpret env e2)

How could the above interpreter be extended to the lambda-mu calculus? My guess is that it should use continuations for interpreting the additional constructs in this calculus. (15) and (16) from the Bernardi&Moortgat paper are the kind of translations I expect.

It is possible since Haskell is Turing-complete, but how?

Hint: See the comment on page 197 on this research paper for the intuitive meaning of the mu binder.

Bob
  • 1,713
  • 10
  • 23
  • 1
    Is this a homework question? What have you tried for the lambda-mu calculus? – Cirdec Feb 26 '15 at 20:51
  • In the wikipedia article and the linked paper, what do the brackets `[x]` mean when `x` isn't a μ-variable or the brackets aren't followed by an unnamed term? – Cirdec Feb 26 '15 at 20:53
  • What does `v/x` mean when `v` is unnamed and `x` is an identifier? What does `/` mean when both are unnamed terms? When both are μ-variables? – Cirdec Feb 26 '15 at 20:56
  • 1
    It is not a homework question. It would be a terribly difficult homework, don't you think? I have unfortunately no idea on how to attack this question. – Bob Feb 26 '15 at 21:12
  • It would probably be appropriate homework for the course whose readings you linked to. – Cirdec Feb 26 '15 at 21:15
  • It is not a course, but it is a research paper published in 1992 in the [proceedings of the International Conference on Logic Programming and Automated Reasoning (LPAR '92)](http://dl.acm.org/citation.cfm?id=663989). – Bob Feb 26 '15 at 21:20
  • 1
    I've been thinking about bringing lambda-mu calculus to Haskell from time to time for a couple of years! It's great that there appear questions (at least, about modeling it), so that this topic becomes more widely known. I've always had the impression that lambda-mu calculus could be the base for a nice, fine-grained IO interface for pure functional stuff. (say, respecting commutativity or dependencies between actions) Cf. the issues from the recent discussion around [withSocketsDo](http://stackoverflow.com/q/22430686/94687) and [Extensible Effects](http://okmij.org/ftp/Haskell/extensible/). – imz -- Ivan Zakharyaschev Feb 27 '15 at 17:17
  • 1
    I'll continue on why I think it's appropriate for IO. The calculus has a dual nature, so that if you turn the programs inside-out and substitute lambdas with mu and mu with lambdas you must probably get the view from the environment on your program. So, mu lets you program effect-request handlers, and put them at the place they are naturally needed in the program, without the need to thread a state (world) through the code, or hide it in a monad. That's how I see it. I'd be glad to hear any comments or criticisms. I'm still very far (intellectually/because of time) from trying to implement it. – imz -- Ivan Zakharyaschev Feb 27 '15 at 17:23
  • 1
    "the view from the environment on your program" means: as if you are programming the environment (the OS) which must interface with your program. This must a very powerful, flexible thing which allows to interleave effects and computations in arbitrary complex ways (but still safe). – imz -- Ivan Zakharyaschev Feb 27 '15 at 17:32
  • @imz--IvanZakharyaschev: Your proposal is very interesting. Since you know so well the lambda-mu calculus, do you have any suggestion how to translate it into Haskell as I did for lambda-calculus in my question. The crux is to understand what are the denotational semantics for mu and square brackets (naming). – Bob Feb 27 '15 at 19:43
  • I got to know λμ-calculus through the work of [Bernardi&Moortgat](http://www.academia.edu/3004538/Moortgat_Continuation_semantics_for_symmetric_categorial_grammar) which is devoted to the foundation of the theory of grammar (linguistics). They cite&use the work by Curien/Herbelin on λμ-calculus with continuation semantics. Section 4 of the paper with the semantics is of most interest here. Reading the paper I'm referring to has the drawback for you that (extra stuff!) they work with another linguistically-motivated calculus, translate it into λμ and give an interpretation for it. – imz -- Ivan Zakharyaschev Feb 27 '15 at 20:29
  • 1
    (15) and (16) from the Bernardi&Moortgat paper are the kind of translations you are looking for. (λμ-terms are in the 2nd column there.) But, as you will see (the mu-tilde), the Curien/Herbelin system is formally a bit different from the initial λμ-calculus, but the idea is the same. Bernardi&Moortgat work on a calculus with left and right application, therefore you'll see unusual expressions in the 2nd column instead of normal application and abstraction. – imz -- Ivan Zakharyaschev Feb 27 '15 at 20:51
  • @imz--IvanZakharyaschev: Thank you for the reference. This is indeed the kind of translation I need. I will add the reference to my post. – Bob Feb 27 '15 at 21:40
  • 1
    BTW, there must be different interpretations of lambda-calculus as well: call-by-value vs call-by-name vs lazy etc. Your code is call-by-value: `F f -> f (interpret env e2)`. Normally, the result is the same. But in their work on grammar, Bernardi&Moortgat demonstrated that you could assign a meaning (in lamda-mu calculus) with the wanted behaviour to a word under cbn, but not under cbv. I'll try elaborate on what happens there. So, as for me, having an interest in this because of the linguistic applications, a cbn translation would be even more interesting (to experiment with grammars). – imz -- Ivan Zakharyaschev Feb 27 '15 at 21:52
  • 1
    This seems very relevant: http://homepages.inf.ed.ac.uk/wadler/papers/dual-reloaded/dual-reloaded.pdf – Cactus Feb 28 '15 at 13:20

3 Answers3

5

Here's a mindless transliteration of the reduction rules from the paper, using @user2407038's representation (as you'll see, when I say mindless, I really do mean mindless):

{-# LANGUAGE DataKinds, KindSignatures, GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}

import Control.Monad.Writer
import Control.Applicative
import Data.Monoid

data TermType = Named | Unnamed

type Var = String
type MuVar = String

data Expr (n :: TermType) where
  Var :: Var -> Expr Unnamed
  Lam :: Var -> Expr Unnamed -> Expr Unnamed
  App :: Expr Unnamed -> Expr Unnamed -> Expr Unnamed
  Freeze :: MuVar -> Expr Unnamed -> Expr Named
  Mu :: MuVar -> Expr Named -> Expr Unnamed
deriving instance Show (Expr n)

substU :: Var -> Expr Unnamed -> Expr n -> Expr n
substU x e = go
  where
    go :: Expr n -> Expr n
    go (Var y) = if y == x then e else Var y
    go (Lam y e) = Lam y $ if y == x then e else go e
    go (App f e) = App (go f) (go e)
    go (Freeze alpha e) = Freeze alpha (go e)
    go (Mu alpha u) = Mu alpha (go u)

renameN :: MuVar -> MuVar -> Expr n -> Expr n
renameN beta alpha = go
  where
    go :: Expr n -> Expr n
    go (Var x) = Var x
    go (Lam x e) = Lam x (go e)
    go (App f e) = App (go f) (go e)
    go (Freeze gamma e) = Freeze (if gamma == beta then alpha else gamma) (go e)
    go (Mu gamma u) = Mu gamma $ if gamma == beta then u else go u

appN :: MuVar -> Expr Unnamed -> Expr n -> Expr n
appN beta v = go
  where
    go :: Expr n -> Expr n
    go (Var x) = Var x
    go (Lam x e) = Lam x (go e)
    go (App f e) = App (go f) (go e)
    go (Freeze alpha w) = Freeze alpha $ if alpha == beta then App (go w) v else go w
    go (Mu alpha u) = Mu alpha $ if alpha /= beta then go u else u

reduceTo :: a -> Writer Any a
reduceTo x = tell (Any True) >> return x

reduce0 :: Expr n -> Writer Any (Expr n)
reduce0 (App (Lam x u) v) = reduceTo $ substU x v u
reduce0 (App (Mu beta u) v) = reduceTo $ Mu beta $ appN beta v u
reduce0 (Freeze alpha (Mu beta u)) = reduceTo $ renameN beta alpha u
reduce0 e = return e

reduce1 :: Expr n -> Writer Any (Expr n)
reduce1 (Var x) = return $ Var x
reduce1 (Lam x e) = reduce0 =<< (Lam x <$> reduce1 e)
reduce1 (App f e) = reduce0 =<< (App <$> reduce1 f <*> reduce1 e)
reduce1 (Freeze alpha e) = reduce0 =<< (Freeze alpha <$> reduce1 e)
reduce1 (Mu alpha u) = reduce0 =<< (Mu alpha <$> reduce1 u)

reduce :: Expr n -> Expr n
reduce e = case runWriter (reduce1 e) of
    (e', Any changed) -> if changed then reduce e' else e

It "works" for the example from the paper: with

example 0 = App (App t (Var "x")) (Var "y")
  where
    t = Lam "x" $ Lam "y" $ Mu "delta" $ Freeze "phi" $ App (Var "x") (Var "y")   
example n = App (example (n-1)) (Var ("z_" ++ show n))

I can reduce example n to the expected result:

*Main> reduce (example 10)
Mu "delta" (Freeze "phi" (App (Var "x") (Var "y")))

The reason I put scare quotes around "works" above is that I have no intuition about the λμ calculus so I don't know what it should do.

Cactus
  • 27,075
  • 9
  • 69
  • 149
  • 3
    That's quite an eloquent way to profess ignorance, Dr. Erdi. – dfeuer Feb 27 '15 at 07:28
  • 5
    This is a nice piece of work! But, if I understand well, you have implemented the operational semantics of the lambda-mu calculus. However I am asking a for a denotational semantics in the form of a translation of lambda-mu calculus into Haskell. That is to say as I did for lambda-calculus in my question where: the lambda of the lambda-mu calculus is translated with a lambda in Haskell, and the application of the lambda-mu calculus is translated with an application in Haskell. – Bob Feb 27 '15 at 19:39
  • 4
    @Bob I don't think this is so easy, because lambda-mu is more "powerful", Haskell is poorer in the set of basic notions. You see, if the task were to translate an imperative program into Haskell, you couldn't do that without inventing some notion as the world state or IO monad and making the imperative statements' semantics have a type which is "lifted" to to the level of threading world-states or of `IO a`. And this inventing can be big work. Isn't the semantics of lamda-mu calculus known to be translated quite naturally to programs with `call/cc`? So let's look for continuations&semantics... – imz -- Ivan Zakharyaschev Feb 27 '15 at 20:14
  • 1
    I indeed believe the interpreter for lambda-mu calculus should use continuations. – Bob Feb 27 '15 at 21:28
3

Note: this is only a partial answer since I'm not sure how to extend the interpreter.

This seems like a good use case for DataKinds. The Expr datatype is indexed on a type which is named or unnamed. The regular lambda constructs produce named terms only.

{-# LANGUAGE GADTs, DataKinds, KindSignatures #-} 

data TermType = Named | Unnamed 

type Var = String
type MuVar = String 

data Expr (n :: TermType) where 
  Var :: Var -> Expr Unnamed
  Lam :: Var -> Expr Unnamed -> Expr Unnamed
  App :: Expr Unnamed -> Expr Unnamed -> Expr Unnamed 

and the additional Mu and Name constructs can manipulate the TermType.

  ...
  Name :: MuVar -> Expr Unnamed -> Expr Named 
  Mu :: MuVar -> Expr Named -> Expr Unnamed
Cactus
  • 27,075
  • 9
  • 69
  • 149
user2407038
  • 14,400
  • 3
  • 29
  • 42
  • 3
    I don't think the `DataKinds` and `GADT` complexity is required for this problem. I think something as simple as adding a single `Mu` constructor to `Expr` (which is now all unnamed terms), `data Expr = Var String | Lam String Expr | App Expr Expr | Mu String Named `, and another type for unnamed terms, `data Named = Named String Expr` should be sufficient. I also find the descriptions of the semantics impenetrable. – Cirdec Feb 26 '15 at 21:10
  • 2
    I disagree with the down-vote. A data type for mu-expressions is a solid step forward from what was presented in the question. – Cirdec Feb 26 '15 at 21:12
  • @Cirdec I agree that a GADT is not needed. However, I initially thought that Lam and Var var should both be parametric in `n`, which I believe does require GADTs (can't be done with plain ADTs). I presented this solution anyways because it is more powerful in general. The semantics is the more interesting part, I believe, so hopefully the OP will clarify or be inspired by this start. – user2407038 Feb 26 '15 at 23:49
  • 1
    If the only way to get an `Expr Named` is from `Name`, and they can only be used as the argument to `Mu`, doesn't that mean we can collapse the two into `Mu :: MuVar -> MuVar -> Expr Unnamed -> Expr Unnamed`? (I haven't fully read the paper yet, maybe it introduces other `Named` stuff later on) – Cactus Feb 27 '15 at 07:26
  • @Cactus I expected that to be the case, but didn't suggest it because I don't understand the paper well enough to determine if we ever need to manipulate something we know to be named outside of knowing it is in a μ abstraction. – Cirdec Feb 27 '15 at 16:52
  • @user2407038 I've written down in the comments (at the top) some of my intuitions (possibly wrong) why lamda-mu calculus is of great interest for practical functional programming. (I could be wrong about some things.) – imz -- Ivan Zakharyaschev Feb 27 '15 at 17:28
  • @user2407038: If I could clarify by giving the denotational semantics of the lambda-mu calculus, then I could answer myself my question. – Bob Feb 27 '15 at 19:33
1

How about something like the below. I don't have a good idea on how to traverse Value a, but at least I can see it evaluates example n into MuV.

import Data.Maybe

type Var = String
type MuVar = String

data Expr = Var Var
          | Lam Var Expr
          | App Expr Expr
          | Mu MuVar MuVar Expr
          deriving Show

data Value a = ConV a
             | LamV (Value a -> Value a)
             | MuV (Value a -> Value a)

type Env a = [(Var, Value a)]
type MuEnv a = [(MuVar, Value a -> Value a)]

varScopeErr :: Var -> Value a
varScopeErr v = error $ unwords ["Out of scope λ variable:", show v]

appErr :: Value a
appErr = error "Trying to apply a non-lambda"

muVarScopeErr :: MuVar -> (Value a -> Value a)
muVarScopeErr alpha = id

app :: Value a -> Value a -> Value a
app (LamV f) x = f x
app (MuV f) x = MuV $ \y -> f x `app` y
app _ _ = appErr

eval :: Env a -> MuEnv a -> Expr -> Value a
eval env menv (Var v) = fromMaybe (varScopeErr v) $ lookup v env
eval env menv (Lam v e) = LamV $ \x -> eval ((v, x):env) menv e
eval env menv (Mu alpha beta e) = MuV $ \u ->
  let menv' = (alpha, (`app` u)):menv
      wrap = fromMaybe (muVarScopeErr beta) $ lookup beta menv'
  in wrap (eval env menv' e)
eval env menv (App f e) = eval env menv f `app` eval env menv e

example 0 = App (App t (Var "v")) (Var "w")
  where
    t = Lam "x" $ Lam "y" $ Mu "delta" "phi" $ App (Var "x") (Var "y")
example n = App (example (n-1)) (Var ("z_" ++ show n))
Cactus
  • 27,075
  • 9
  • 69
  • 149