Sorry, I said I'd include an example of actives in my previous message,
then forgot to include it.
Post by Kjell GodoAn active variable is a variable that expands when it is referenced or
assigned to. Now where would that be useful? Any examples?
I didn't explain it well, see below for another attempt, and some examples.
Post by Kjell GodoWhat happens to the value of the variable when this expansion takes place?
Is code what goes into the variable on a reference?
I think I answer these two question below.
Post by Kjell GodoIs this like in Scheme where variables can contain functions?
No, though Coke/Jolt variables are exactly like Scheme variables in this
respect.
Post by Kjell GodoCan you give an example of how active variables are defined and used?
Or a link.
I looked up active variable but didn't get anything.
Actives are not documented in the main coke paper (ie.
http://piumarta.com/software/cola/coke.html). I found them while
exploring the implementation of jolt, in Compiler.st. In trying to
understand how actives work, it's useful to know how Coke/Jolt syntax's
(for lack of a better name) work. Syntax's _are_ documented in that
paper, and there are working examples in the file syntax.k in the jolt
implementation. It is also helpful to understand how backquote works.
Backquote is also documented in that paper.
Syntax's are function-like in the sense that they only get expanded when
the appear at the front of a list. So if we define a bit of syntax like:
(syntax begin
(lambda (node compiler)
`(let () ,@[node copyFrom: '1])))
We can only use "begin" in the places where function call can go (at the
beginning of a form):
(begin
(printf "%d" 10)
(printf "and %d\n" 20)
)
Putting "begin" someplace else will result in "undefined: trans:begin"
error.
Actives, on the other hand, are variable-like in that can be placed
anywhere variables can be placed.
Actives are defined with an "active" special form. For example:
(active A
(lambda (name compiler) 'DUMMY)
(lambda (name compiler) '(addrof DUMMY))
)
(define DUMMY 0)
(set A 100)
(printf "%d\n" A)
; prints: 100
(set A 4)
(printf "%d\n" A)
; prints: 4
This (not very useful) active special form creates an active "A" which
simply defines a wrapper around "DUMMY". That is, assigments to A are
assignments to DUMMY, reads from A are reads from DUMMY.
The active special form takes one or two lambdas (functions) as arguments.
The _second_ lambda is called (at compile time) when the active is the
target of a set special form. It is called with the name of the active
and the current compiler, and it must return an expression who's result
after evaluation is the address of the memory location that the set
special form will change. If the active special form doesn't have a
second lambda, the active can't be used as the target of a set (it's
read only).
The _first_ lamba is called (at compile time) when the active is used in
any other place (is read/fetched from). It is also called with the name
of the active and the current compiler, but it must return an expression
whose value is used as the value of the active.
This next example is the same as the last with two printf's stuck into
each lambda to see when each lambda is executed, and when the code
returned by each lambda is executed:
(active B
(lambda (name compiler)
(begin
(printf "compile time read of %s\n" [[name printString]
_stringValue])
`(begin
(printf "exec time read of %s\n" [[(quote ,name) printString]
_stringValue])
DUMMY)))
(lambda (name compiler)
(begin
(printf "compile time set of %s\n" [[name printString] _stringValue])
`(begin
(printf "exec time set of %s\n" [[(quote ,name) printString]
_stringValue])
(addrof DUMMY))))
)
(set B 100)
; prints: compile time set of #B
; and prints: exec time set of #B
(printf "%d\n" B)
; prints: compile time read of #B
; and prints: exec time read of #B
; and prints: 100
(set B 4)
; etc
(printf "%d\n" B)
; etc
It's important to understant that these printf can be arbitrary code,
code which examines the current compiler, perhaps makes changes to it,
allocates memory, what ever. The following example creates "variables"
in malloc'ed memory, keeps track of this memory in a dictionary, and
uses an active to make the "variables" look like regular variables to
the rest of the code:
(define IdentityDictionary (import "IdentityDictionary"))
(define DICT [IdentityDictionary new])
[DICT at: 'A put: (malloc 4)]
(active A
(lambda (name compiler) '(long@ [DICT at: 'A]))
(lambda (name compiler) '[DICT at: 'A])
)
(set A 100)
[DICT at: 'B put: (malloc 4)]
(active B
(lambda (name compiler) '(long@ [DICT at: 'B]))
(lambda (name compiler) '[DICT at: 'B])
)
(set B 200)
(printf "A in DICT=%d\n" A)
(printf "B in DICT=%d\n" B)
(set A 40)
(set B 50)
(printf "A in DICT=%d\n" A)
(printf "B in DICT=%d\n" B)
This final example wraps the previous example up in a new syntax, so
that createing variables in malloc'ed memory is as simple as creating
normal variables:
(define DICT [IdentityDictionary new])
(syntax define-in-DICT
(lambda (node compiler)
(let ((active-name [node second])
(initial-value-expr [node third]))
`(all
[DICT at: (quote ,active-name) put: (malloc 4)]
(active ,active-name
(lambda (name compiler) '(long@ [DICT at: (quote
,active-name)]))
(lambda (name compiler) '[DICT at: (quote ,active-name)])
)
(set ,active-name ,initial-value-expr)
)
)))
;; all -- begin without creating a new scope
;; (not the best implementation)
(define _all
(lambda (node idx)
(if [idx >= [node size]]
`()
`((or ,[node at: idx] 1)
,@(_all node [idx + '1])))))
(syntax all
(lambda (node compiler)
`(and ,@(_all node '1))))
(define-in-DICT A 100)
(define-in-DICT B 200)
(printf "%d\n" A)
(printf "%d\n" B)
(set A 40)
(set B 50)
(printf "%d\n" A)
(printf "%d\n" B)
Hope this is helpfull.
-gavin...