[bof] context switch inside function: forbidden?

Started by hartrock, August 27, 2013, 07:49:14 PM

Previous topic - Next topic

hartrock

[Update: -> feature]

I'm wondering why switching contexts inside a function does not work in the example below.

Especially because switching inside a func from a non-MAIN context to MAIN already has worked.



The code:

; fails
(define (create-funcs)
  (println "in create-funcs")
  (context 'C)
  (define (f_1) 1)
  (context MAIN))
(create-funcs)
(symbols 'C)  "-> does not work: no sym defined"
;;
;; works
(context 'C)
(define (f_1) 1)
(context MAIN)
(symbols 'C) "-> OK: sym defined"

The session:

sr@free:~/NewLisp$ newlisp
newLISP v.10.5.4 64-bit on Linux IPv4/6 UTF-8 libffi, options: newlisp -h

>
;; fails
(define (create-funcs)
  (println "in create-funcs")
  (context 'C)
  (define (f_1) 1)
  (context MAIN))
(create-funcs)
(symbols 'C)  "-> does not work: no sym defined"
;;
;; works
(context 'C)
(define (f_1) 1)
(context MAIN)
(symbols 'C) "-> OK: sym defined"

(lambda () (println "in create-funcs") (context 'C)
 (define (f_1)
  1)
 (context MAIN))
in create-funcs
MAIN
()
"-> does not work: no sym defined"
C
(lambda () 1)
MAIN
(C:f_1)
"-> OK: sym defined"
>

It would be nice to be able to introduce (and later delete) contexts this way (testing is my current use-case triggering this problem).

Lutz

#1
Switching the context namespace has only an influence on subsequent code translation via load, eval-string and sym etc.



For that reason you find context switch statements typically on the top level of source code.



Once symbols are created during code interpretation, they stay in the context under which they were created.

hartrock

#2
I think best is to show a use-case.

There is the following (unfinished, but this does not affect this topic) code:

(context 'db)

(define (create (props '("trans" nil)))
  (println props)
  (++ dbCount)
  (context MAIN) ; commenting out this does not work
  (let ((db_sym (sym (append "DB_" (string dbCount))))
        (data_sym (sym (append "Data_" (string dbCount))))
        (meta_sym (sym (append "Meta_" (string dbCount))))
        (trans_sym (sym (append "Trans_" (string dbCount)))))
    (let (db_ctx (new db_proto db_sym)) ; transfer symbols from db_proto: to DB_*:
      (db_ctx:init ; call :init
       (new Tree data_sym)
       (new Tree meta_sym)
       (new Tree trans_sym))
      )))

(context MAIN)

Luckily (?) this works as expected.

If I'm commenting out (context MAIN) I get the error

ERR: symbol not in MAIN context in function new : DB_1
called from user defined function db:create

So is this kind of switching allowed or is there another approach?

Lutz

#3
The sym statements create synbols belonging to the MAIN context, because a context switch to  MAIN was executed before. When out-commenting the context switch, symbols are created in the db context.



All other statements are executed under the context they were originally translated in, which is db.



See also special rules for new.

hartrock

#4
Hello Lutz,



thanks for your blindingly fast reply!



I know, why I've switched to MAIN in db:create (because I want to create context symbols in MAIN).

But I have difficulties to understand, why this is allowed in this case, but makes problems in the other...

Or in other words: where is the problem in

; fails
(define (create-funcs)
  (println "in create-funcs")
  (context 'C)
  (define (f_1) 1)
  (context MAIN))
(create-funcs)
(symbols 'C)  "-> does not work: no sym defined"

? I just want to create func (and related) syms in context C here (and switch back thereafter).

Lutz

#5
Let's follow code translation and execution step by step in your example:



(1) read the top level expression (define (create-funcs) ...) and compile it to an internal representation. All the symbols created will be in MAIN because when the expression was read, MAIN was the current context. During this compilation process, new symbols create-funcs and f_1 are created.



(2) Evaluate the define expression translated in (1). Evaluating this define expression is basically assigning the body of the definition to the symbol create-funcs.



(3) Read in and compile the (create-funcs) expression



(4) Evaluate the (create-funcs) expression by calling the create-funcs function. Inside that function the current context is switched to C, but that has no influence of the evaluation of the (define (f_1) 1) expression. Evaluating (define (f_1) 1) leads to an assignment of a lambda expression returning 1 to the f_1 symbol which was put in MAIN during step (1). At the end of evaluation the context is switched back to MAIN.



A context switch only works for subsequent load, eval-string and sym statements. A context switch influences code compilation (symbol creation) and nothing else.



Traditional interpretes, where token translation and execution are completely interleaved, don't exists anymore since the 90's. Modern scripting languages translate / compile into an internal representation first, then evaluate that internal representation in some kind of VM (virtual machine). So during code execution, all symbols are already fixed.

hartrock

#6
Thanks for the thorough explanation: this really helps!



Now I'm thinking, that db:create just works, because of the creation of new symbols for contexts at evaluation (and not compile) time.

In this light I'm feeling more comfortable with my db:create code as before (as I've just stumbled over this issue).



For me checking the border cases is one of the best ways to learn something (this is not limited to learning of programming languages): especially if someone is knowing these border cases very well and is willing to share his knowledge.



Thanks,

Stephan

hartrock

#7
To illustrate Lutz' description of the read/compile/evaluate process some session:

newLISP v.10.5.4 64-bit on Linux IPv4/6 UTF-8 libffi, options: newlisp -h

>
;; # (1) (2) ..
(define (create-funcs)
  (println "in create-funcs")
  (context 'C)
  (define (f_1) 1)
  (context MAIN))
;; .. (1) (2) #
;;
"(dolist (s (symbols) (= (string s) "f_1")))"
(dolist (s (symbols) (= (string s) "f_1")))
"-> 'f_1 symbol exists in MAIN, but .."
;;
"f_1"
f_1
"-> .. is nil: function not defined yet"
;;
;; # (3) (4) ..
"(create-funcs)"(create-funcs)
;; .. (3) (4) #
;;
"(symbols 'C)"
(symbols 'C)
"-> no C:f_1, but .."
;;
"(f_1)"
(f_1)
"-> .. call in MAIN by (f_1) works!"

(lambda () (println "in create-funcs") (context 'C)
 (define (f_1)
  1)
 (context MAIN))
"(dolist (s (symbols) (= (string s) "f_1")))"
true
"-> 'f_1 symbol exists in MAIN, but .."
"f_1"
nil
"-> .. is nil: function not defined yet"
"(create-funcs)"
in create-funcs
MAIN
"(symbols 'C)"
()
"-> no C:f_1, but .."
"(f_1)"
1
"-> .. call in MAIN by (f_1) works!"
>

Some inferred rules regarding context switches:

[*] (top-level) outside lambdas/maccros:
     
[*] control symbol creation at compile time;[/list]

  • [*] (lower-level) inside lambdas/macros:
     
    [*] control creation of new symbols at evaluation time;

  • [*] do not affect symbol creation at compile time;

  • [*] do not change symbols already created at compile time.
  • [/list]
    [/list]
    The core point here: only top-level context switches will be honored at compile time, but not lower-level ones.

    An example for a top-level context switch controlled by some evaluation:

    (if flag
        (context 'C_1)
        (context 'C_2))
    (define (create-funcs)
      (define (f) 1))
    (context MAIN)

    The flag controls, in which context symbol f will be created at compile time.

    The following does not work as one might expect:

    (define (switch-context flag)
      (if flag
        (context 'C_1)
        (context 'C_2)))
    (switch-context nil)
    (define (create-funcs)
      (define (f) 1))
    (create-funcs)
    (context MAIN)

    Here f becomes MAIN:f instead of C_2:f (context C_2 will be created, though).



    Leaving a function goes back to the context of the caller; seen by evaluating the following:

    (define (goto-context-inner flag)
      (if flag
        (context 'C_2)
        (context 'C_1))
      (println "inner: " (context)))
    (define (goto-context flag)
      (if flag
        (context 'C_1)
        (context 'C_2))
      (goto-context-inner flag)
      (println "outer: " (context)))
    (goto-context true)
    (println "MAIN: " (context))

    So calling a function for context switches at caller side (e.g. top-level) does not work.

    In other words: context switches in callees do not propagate to callers (which is a good thing).

    Lutz

    #8
    All correct :)



    Here is something else to be aware of when using map and apply:  only when calling the functions of a different context by symbol, will the context be switched:


    newLISP v.10.5.3 64-bit on OSX IPv4/6 UTF-8 libffi, options: newlisp -h

    > (define (foo:bar) (context))
    (lambda () (context))
    > (foo:bar)
    foo
    > (apply foo:bar)
    MAIN
    > (apply 'foo:bar)
    foo
    >


    only in the first and last case, when foo:bar is called by symbol, the context switches to foo. In the second case, apply evaluates the argument foo:bar to the lambda expression (lambda () (context)) and applies/calls that lambda expression and no context switch is done.



    With map, the same thing will happen:


    > (map 'foo:bar '(1 2 3))
    (foo foo foo)
    > (map foo:bar '(1 2 3))
    (MAIN MAIN MAIN)
    >


    Just like in the apply example, map evaluates its arguments and in the second case only the lambda expression is applied and no context is switched.