I have this coroutine example
(define p1
(lambda (continuation)
(display "1")
(newline)
(p1 (call/cc continuation))))
(define p2
(lambda (continuation)
(display "2")
(newline)
(p2 (call/cc continuation))))
(p1 p2)
And I would like to change it in CPS so that I can use the CPS call/cc :
(define (call/cc-cps f continuation)
(define (exit value actual-continuation)
(continuation value))
(f exit continuation))
I know that to transform a function in CPS I need to add a continuation to the fonction but I'm quite confused and I don't really know how to do it.
I think that it would look like that :
(define p1
(lambda (continuation)
(display "2")
(newline)
(call/cc-cps
(lambda (continuation actual-continuation)
continuation) ;; or actual-continuation ?
(lambda (value)
(p1 value)))))
(p1 p2)
But It's probably wrong. Can someone help me understand how to do it correctly ?
Thank you
If you do something in CPS only one calculation is done in tail position in every function. It's kind of the way an interpreter evaluates the code:
(define (hyp a b)
(sqrt (+ (square a) (square b))))
The whole point is that CPS does the calculation in the needed order and never do more than one thing at a time. This in CPS becomes:
(define (hyp& a b cont)
(define (cont-square-a sqa)
(define (cont-square-b sqb)
(define (cont-sum sum)
(sqrt& sum cont))
(+& sqa sqb cont-sum))
(square& b cont-square-b))
(square& a cont-square-a))
Or if you prefer anonymous continuations:
(define (hyp& a b cont)
(square& a
(lambda (sqa)
(square& b
(lambda (sqb)
(+& sqa
sqb
(lambda (sum)
(sqrt& sum cont))))))))
All the &-functions are just CPS-versions of the actual function, thus they have a continuation argument in addition to the original function. p1 would look like this:
(define (p1& continuation& cont)
(define (cont-display-2 undefined-value-1)
(define (cont-newline undefined-value-2)
(define (cont-call-cc-continuation& continuation-value)
(p1& continuation-value cont))
(call/cc& continuation& cont-call-cc-continuation&))
(newline& cont-newline))
(display& "2" cont-display-2))
You might be interested in the articles of Matt Might. Take a loot at everything around continuations and compilations as they are related. I also recommend watching the SICP videos and perhaps try solving the exercises in the SICP book. By the time you are making interpreters and compilers it should be a walk in the park making generators.
Related
TL;DR: What does call/cc do, (semi-)formally speaking?
The long version: I'm vaguely familiar with continuations and call/cc, but I don't have a strong formal understanding. I would like one.
In the SICP video lectures we are presented with the substitution model and a metacircular interpreter. In Shriram Krishnamurti's programming language course we are shown an environment-and-store-passing style. In the compiler course I took in uni I evaluated Java expressions by manipulating a stack.
What's the simplest evaluation model in which call/cc can be expressed, and how do you express call/cc in it?
TL;DR call/cc lets you tap into Schemes internal code so that you can use continuations without writing your code in continuation passing style. The best evaluation model is the substitution model given that you don't use set and view it from CPS code
Imagine this little program.
(define (sum-list lst)
(cond ((null? lst) 0)
((number? (car lst)) (+ (car lst) (sum-list (cdr lsr))))
(else (sum-list (cdr lsr)))))
(display (sum-list '(1 2 3 4))) ; displays 10
Imagine you want the result to be 1337 if you hit the else term. How would you go about that without rewriting the whole thing? You can't. However most Scheme implementation does CPS conversion to your code where changing it is trivial:
(define (sum-list& lst k)
(null?& lst
(lambda (nlst?)
(if& nlst?
(lambda ()
(k 0))
(lambda ()
(car& lst
(lambda (car-lst)
(number?& car-lst
(lambda (ncl?)
(if& ncl?
(lambda ()
(cdr& lst
(lambda (clst)
(sum-list& clst
(lambda (sclst)
(+& car-lst sclst k))))))
(lambda ()
(cdr& lst
(lambda (clst)
(sum-list& clst k))))))))))))))
(sum-list& '(1 2 3 4)
(lambda (sum)
(display& sum halt)))
cond is a macro so it is the if& calls you see. I know what you're thinking. Why not tell the programmers to do it like this in the first place? Just kidding!
If you change the second continuation in the second if& to (lambda () (k 1337)) you're done. As beautiful as CPS is it is horrible to read and write, but since the compiler does this anyway couldn't there be a way to be able to write your code in the first way and still get to code control flow? The best of two worlds is enabled with call/cc. call/cc is this in CPS:
(define (call/cc& f& continuation)
(define (exit& value actual-continuation)
(continuation value))
(f& exit& continuation))
It doesn't do much at all. It passes exit& which ignores the real continuation of the program when called and calls the value with the original continuation of the call/cc& call. With the list '(1 2 3 #f) you'd have 3 nested continuations waiting to add with the result but all of that needs to be cancelled. If you get to choose the value of the continuation before that calculation it automatically is cancelled.
And that is it. When you write your code with it you don't need to do full CPS, only the continuation you want thought call/cc like this:
(define (sum-list lst)
(call/cc
(lambda (k)
(define (helper lst)
(cond ((null? lst) 0)
((number? (car lst))
(+ (car lst) (helper (cdr lst))))
(else (k 1337))))
(helper lst))))
This gets transformed to this in CPS:
(define (sum-list& lst k)
(call/cc&
(lambda (k& real-k)
(define (helper& lst k)
(null?& lst
(lambda (nlst?)
(if& nlst?
(lambda ()
(k 0))
(lambda ()
(car& lst
(lambda (car-lst)
(number?& car-lst
(lambda (ncl?)
(if& ncl?
(lambda ()
(cdr& lst
(lambda (clst)
(helper& clst
(lambda (sclst)
(+& car-lst sclst k))))))
(lambda ()
(k& 1337 real-k))))))))))))
(helper& lst real-k))
k))
(sum-list& '(1 2 3 4)
(lambda (sum)
(display& sum halt)))
call/cc can always be avoided. Our example could have been rewritten to return 1337 like this:
(define (sum-list lst)
(define (helper lst sum)
(cond ((null? lst) sum)
((number? (car lst)) (helper (cdr lst) (+ sum (car lst))))
(else 1337)))
(helper lst 0))
This is tail recursive and instead of creating continuations it accumulates. For completeness here is the CPS version of it:
(define (helper& lst sum k)
(null?& lst
(lambda (nlst)
(if& nlst
(lambda () (k sum))
(lambda ()
(car& lst
(lambda (cl)
(number?& cl
(lambda (ncl?)
(if& ncl?
(lambda ()
(cdr& lst
(lambda (cdrl)
(+& sum
cl
(lambda (scl)
(helper& cdrl
scl
k))))))
(lambda () (k 1337))))))))))))
(define (sum-list& lst k)
(helper& lst 0 k))
I found this excellent presentation (in German), which answered my question: https://www.youtube.com/watch?v=iuaM9-PX1ls
To evaluate the lambda calculus with call/CC, you pass around an evaluation context consisting of an environment (as usual) and a call stack. A call to call/cc creates a special function-like continuation object which stores the evaluation context. The result of applying the special object to an expression expr is the result of interpreting expr in the evaluation context captured in the continuation object.
TL;DR: you can implement call/cc with an environment-and-call-stack-passing interpreter.
If you want to also thread around a mutable store the continuation objects should not capture it. Rather, when invoking a continuation you pass the store as an argument to the interpretation in the restored evaluation context. (The store can be of a linear type.)
Here is one such implementation in Haskell. Here's a sample expression you may want to evaluate: interpret 0 (Application (Lambda (1, (CallCC (Lambda (2, (Application (Variable 2) (Lambda (3, (Variable 4))))))))) (Lambda (4, (Variable 5)))).
(The numbers are just plain old names, not (e.g.) De Bruijn indices. If you wish to use characters or strings, change type Name = Integer to type Name = Char.)
Note especially interp applied to CallCC and InvokeContinuation as well as continue applied to ContinuationArgument.
import qualified Data.Map as Map
type Name = Integer
type NameAndBody = (Name, LambdaCallCC)
data LambdaCallCC
= Lambda NameAndBody
| Variable Name
| InvokeContinuation EvaluationContext LambdaCallCC
| CallCC LambdaCallCC
| Application LambdaCallCC LambdaCallCC
deriving Show
type Environment = Map.Map Name NameAndBody
type EvaluationContext = (CallStack, Environment)
type CallStack = [Frame]
data Frame
= FunctionPosition LambdaCallCC
| ArgumentPosition NameAndBody
| ContinuationArgument EvaluationContext
deriving Show
type Fail = (Name, EvaluationContext)
interpret :: Name -> LambdaCallCC -> Either Fail NameAndBody
interpret thunkVarName expression = interp [] Map.empty expression
where interp stk env (Lambda nameAndBody)
= continue stk env nameAndBody
interp stk env (Variable name)
= case Map.lookup name env of
Nothing -> Left (name, (stk, env))
Just e -> continue stk env e
interp stk env (Application e1 e2)
= interp (FunctionPosition e2 : stk) env e1
interp stk env (CallCC expr)
= interp stk env (Application expr
(Lambda (thunkVarName,
(InvokeContinuation
(stk, env)
(Variable thunkVarName)))))
interp stk env (InvokeContinuation capturedContext expr)
= interp [ContinuationArgument capturedContext] env expr
continue [] env value
= Right value
continue ((FunctionPosition expr) : frames) env value
= interp ((ArgumentPosition value) : frames) env expr
continue ((ArgumentPosition (name, body)) : frames) env value
= interp frames (Map.insert name value env) body
continue ((ContinuationArgument (stk, env)) : nil) _ value
= interp stk env (Lambda value)
Does call/cc in Scheme the same thing with yield in Python and JavaScript?
I am not clear about generators. In my opinion, yield gives a language the ability to generate iterators without pain. But I am not sure whether I am right.
Does call/cc in Scheme has something related to yield in other languages? If so, are they the same thing, or what is the difference?
Thanks!
call/cc is a much more general language feature than generators. Thus you can make generators with call/cc, but you cannot make call/cc with generators.
If you have a program that computes values and use those values in other places its basically a step machine.. One might think of it as a program that have one function for each step and a continuation for the rest of the steps. Thus:
(+ (* 3 4) (* 5 6))
Can be interpreted as:
((lambda (k)
(k* 3 4 (lambda (v34)
(k* 5 6 (lambda (v56)
(k+ v34 v56 k)))))
halt)
The k-prefix just indicate that it's a CPS-version of the primitives. Thus they call the last argument as a function with the result. Notice also that the order of evaluation, which is not defined in Scheme, is in fact chosen in this rewrite. In this beautiful language call/cc is just this:
(define (kcall/cc kfn k)
(kfn (lambda (value ignored-continuation)
(k value))
k))
So when you do:
(+ (* 3 4) (call/cc (lambda (exit) (* 5 (exit 6)))))
; ==> 18
Under the hood this happens:
((lambda (k)
(k* 3 4 (lambda (v34)
(kcall/cc (lambda (exit k)
(exit 6 (lambda (v6)
(k* 5 v6 k)))
k))))
halt)
By using the substitution we can prove that this actually does exactly as intended. Since the exit function is invoked the original continuation is never called and thus the computation cancelled. In contrast to call/cc giving us this continuation that doesn't seem obvious it's no magic in CPS. Thus much of the magic of call/cc is in the compiler stage.
(define (make-generator procedure)
(define last-return values)
(define last-value #f)
(define (last-continuation _)
(let ((result (procedure yield)))
(last-return result)))
(define (yield value)
(call/cc (lambda (continuation)
(set! last-continuation continuation)
(set! last-value value)
(last-return value))))
(lambda args
(call/cc (lambda (return)
(set! last-return return)
(if (null? args)
(last-continuation last-value)
(apply last-continuation args))))))
(define test
(make-generator
(lambda (collect)
(collect 1)
(collect 5)
(collect 10)
#f)))
(test) ; ==> 1
(test) ; ==> 5
(test) ; ==> 10
(test) ; ==> #f (procedure finished)
One might make a macro to make the syntax more similar, but it's just sugar on top of this.
For more examples I love Matt Mights page with lots of examples on how to use continuations.
Here's code to define a generator:
(define-syntax define-generator
(lambda (x)
(syntax-case x (lambda)
((stx name (lambda formals e0 e1 ...))
(with-syntax ((yield (datum->syntax (syntax stx) 'yield)))
(syntax (define name
(lambda formals
(let ((resume #f) (return #f))
(define yield
(lambda args
(call-with-current-continuation
(lambda (cont)
(set! resume cont)
(apply return args)))))
(lambda ()
(call-with-current-continuation
(lambda (cont)
(set! return cont)
(cond (resume (resume))
(else (let () e0 e1 ...)
(error 'name "unexpected return"))))))))))))
((stx (name . formals) e0 e1 ...)
(syntax (stx name (lambda formals e0 e1 ...)))))))
There are examples of the use of generators at my blog. Generators use call-with-current-continuation, in a manner similar to yield in Python, but are much more general.
You can implement generators with call/cc. Here is an example:
coroutines.ss
They work in a similar way to python and ECMAScript generators.
I started to study the Scheme and curious of how the built-in procedure "pair?" works. I mean the code obviously, since I couldn't find a way to see the code of the built-in procedures and don't know how to write it I'm here.
Had the same question with the "List?" procedure but managed to write it myself, but in case of "pair?" have no idea. Thx!
I think you're looking for the implementation of the 'pair?' primitive in Racket. If so: it's currently in list.c:
https://github.com/racket/racket/blob/master/racket/src/racket/src/list.c
Specifically, look at the definition of pair_p_prim.
Hope this helps!
EDIT: why isn't it written in Racket?
Answer: pair? is a primitive in Racket and Scheme. This means that in Racket, it's not implemented in Racket, it's implemented in the language that Racket is implemented in. For this part of the language, that's C. Keep in mind that this can change; if the Racket implementation gets updated to provide a lower-level set of primitives, then the pair? function might no longer be a primitive. Finally, it's worth noting that for some languages, implementors leverage the existence of an older compiler in order to provide a 'bootstrapping' implementation where the implementation language is the same as the language
being developed.
Hope this helps!
pair? can be implemented in Scheme; Scheme is Turing complete, my friend!
But instead of doing your hw for you and to get your head spinning I will encode pair? in the lambda calculus using Scheme; from here, follow the white rabbit!
cpair? = λm. m (λx. λy. tru) fls
In Scheme:
(define c-pair?
(lambda (m)
((m (lambda (x) (lambda (y) tru))) (lambda (x) fls))))
(define tru
(lambda (t) (lambda (f) t)))
(define fls
(lambda (t) (lambda (f) f)))
Procedures for testing:
(define kons
(lambda (h)
(lambda (t)
(lambda (c)
(lambda (n)
((c h) ((t c) n)))))))
(define c-equal?
(lambda (m)
(lambda (n)
((c-and (iszero ((m prd) n)))
(iszero ((n prd) m))))))
(define c-and
(lambda (b)
(lambda (c)
((b c) fls))))
(define iszero (lambda (m) ((m (lambda (x) fls)) tru)))
(define prd (lambda (m) (fst ((m ss) zz))))
(define fst (lambda (p) (p tru)))
;; church-boolean -> real boolean
(define real-bool (lambda (b) ((b true) false)))
(define nil (lambda (c) (lambda (n) n)))
;; representation of number 1
(define c1 (lambda (s) (lambda (z) (s z))))
Test:
;; some list - in Scheme this would be: (cons 1 '())
(define d ((kons c1) nil)
(real-bool ((c-equal? (c-pair? d)) tru)) ;; -> #t
(real-bool ((c-equal? (c-pair? c1)) fls)) ;; -> #f
In other words; you can definitely write pair? in Scheme even if pair? is a primitive.
I'm currently writing metacircular evaluator in Scheme, following the SICP book's steps.
In the exercise I am asked to implement letrec, which I do in the following way:
(define (letrec->let exp)
(define (make-unassigned var)
(list var '*unassigned*))
(define (make-assign binding)
(list 'set! (letrec-binding-var binding)
(letrec-binding-val binding)))
(let ((orig-bindings (letrec-bindings exp)))
(make-let
(map make-unassigned
(map letrec-binding-var orig-bindings))
(sequence->exp
(append
(map make-assign orig-bindings)
(letrec-body exp))))))
However, when I evaluate the expression as follows, it goes into infinite loop:
(letrec
((a (lambda () 1)))
(+ 1 (a)))
Do I miss anything?
(Full Source Code at GitHub).
I checked result transformation result of (let ((x 1)) x) and got:
((lambda (x) (x)) 1)
instead of:
((lambda (x) x) 1)
obviously problem is in let body processing. If I were you, I would use utility function:
(define (implicit-begin exps)
(if (= 1 (length x))
(car x)
(cons 'begin x)))
By the way: I would say, that your letrec implementation is not very correct. Your transformation returns:
(let ((x1 *unassigned*>) ... (xn *unassigned*))
(set! x1 ...)
...
(set! xn ...)
body)
It much more resembles letrec* which evaluates expressions for variable bindings in left-ro-right order (Scheme itself doesn't specify arguments evaluation order):
syntax: letrec* bindings body
Similar to ‘letrec’, except the INIT expressions are bound to their
variables in order.
‘letrec*’ thus relaxes the letrec restriction, in that later INIT
expressions may refer to the values of previously bound variables.
The more correct code would be:
(let ((x1 *unassigned*) ... (xn *unassigned*))
(let ((t1 ...) ... (tn ...))
(set! x1 t1)
...
(set! xn tn))
body)
(define-record-type car-ivars
(fields efficiency (mutable gas-in-tank)))
(define-record-type car-methods
(fields drive! get-gas-in-tank refuel))
(define refuel (lambda (c g) ((car-methods-refuel c) g)))
(define get-gas-level
(lambda (c) ((car-methods-get-gas-in-tank c))))
(define drive!
(lambda (c distance) ((car-methods-drive! c) distance)))
(define make-car
(lambda (efficiency)
(let ([car1 (make-car-ivars efficiency 0)])
(let ([set-gas-level!
(lambda (gas) (car-ivars-gas-in-tank-set! car1 gas))]
[gas-level
(lambda () ((car-ivars-gas-in-tank car1)))])
(make-car-methods
;;drive!
(lambda (distance)
(set-gas-level!
(- (get-gas-level)
(/ efficiency distance)))
;;get-gas-level
(get-gas-levels)
;;refuel
(lambda (gas1)
(set-gas-level!
(+ (get-gas-level) gas1)))))))))
Ok, so I have this code. When i try to run my test case of (define hybrid (make-car 50))
I get an Exception: incorrect number of arguments to #procedure constructor
error. And I'm not quite sure where it is coming from.
Your definition of the drive part in make-car is missing a right parenthesis, which means that make-car-methods only gets one argument.
Re-indenting in Emacs made this pretty obvious:
(define make-car
(lambda (efficiency)
(let ([car1 (make-car-ivars efficiency 0)])
(let ([set-gas-level!
(lambda (gas) (car-ivars-gas-in-tank-set! car1 gas))]
[gas-level
(lambda () ((car-ivars-gas-in-tank car1)))])
(make-car-methods
;;drive!
(lambda (distance)
(set-gas-level!
(- (get-gas-level)
(/ efficiency distance)))
;;get-gas-level
(get-gas-levels)
;;refuel
(lambda (gas1)
(set-gas-level!
(+ (get-gas-level) gas1)))))))))