// Useful type aliases
type Actor = MailboxProcessor<obj>
type Ident = string
type Cond = Actor
type Env = Actor
 
 
let (<!>) (actor : Actor) (msg : 'T) = actor.Post msg
 
// run forever - template
let start<'T> (work : 'T -> unit) : Actor =
    MailboxProcessor<obj>.Start(fun mb ->
        let rec loop () =
            async {
                let! msg = mb.Receive()
                match msg with
                | :? 'T as msg' -> work msg'
                | _ -> () // oops... undefined behaviour
                return! loop ()
            }
        loop () )
 
// Helper print expression
let printExp = start<obj>(fun value -> printfn "Print: %A" value)
 
// Enviroment encoding functions
let emptyEnv =
    start<Cond * Ident>(fun _ -> ()(*oops... undefined behaviour*))
let buildEnv ident value env =
    start<Cond * Ident>(fun (cond, ident') -> if ident = ident' then cond <!> value else env <!> (cond, ident'))
 
// Closures are also actors
let closure ident body env =
    start<Cond * obj>(fun (cond, arg) -> let env' = buildEnv ident arg env in body <!> (cond, env'))
 
// Lambda Calculus expressions
let constExp value = start<Cond * Env>(fun (cond, _) -> cond <!> value)
let identExp ident = start<Cond * Env>(fun (cond, env) -> env <!> (cond, ident))
 
let lambdaExp ident body =
    start<Cond * Env>(fun (cond, env) -> cond <!> closure ident body env)
 
let appExp exp argExp =
    start<Cond * Env>(fun (cond, env) ->
        let closureCond =
            start<Cond>(fun closure ->
                let argCond = start<obj>(fun value -> closure <!> (cond, value))
                argExp <!> (argCond, env))
        exp <!> (closureCond, env))
 
// Examples
 
let idExp = lambdaExp "x" (identExp "x")
let example = appExp idExp (constExp 42)
 
example <!> (printExp, emptyEnv) // Print: 42
 
let selfAppExp = lambdaExp "x" (appExp (identExp "x") (identExp "x"))
let omega = appExp selfAppExp selfAppExp
omega <!> (printExp, emptyEnv) // run forever