#580: scrAddAction does not support closures

Freeform discussion about anything related to modding Transcendence.
Post Reply
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

http://wiki.neurohack.com/transcendence/trac/ticket/580

This is actually hard to fix cleanly, so I thought I would request some ideas. Here's the problem:

Imagine the normal case:

Code: Select all

EXAMPLE 1:

(scrAddAction gScreen 'myAction 0 "My Action" (dbgOutput "Test"))
The above code adds a new action to the screen. The last argument to the function specifies the code that should run when the user invokes the action. In this case, when the user invokes it, we output "Test" to the console.

But imagine that I want a level of indirection:

Code: Select all

EXAMPLE 2:

(block (myFunc)
   (setq myFunc (lambda () (dbgOutput "Test"))
   (scrAddAction gScreen 'myAction 0 "My Action" myFunc)
   )
Instead of specifying the code directly, we assigned the code to a variable and passed the variable in. Unfortunately that doesn't work.

The semantics of scrAddAction are:

1. Take the last argument and store it as the action code.
2. When the user invokes the action, evaluate the action code.

Importantly, step 1 does not evaluate the code at the time that you call scrAddAction. Otherwise, when you added the action, you would actually invoke it. Imagine, in EXAMPLE 1, what would happen if you evaluated the last argument when scrAddAction was called: you would output "Test" right then and there, which is something that you don't want.

But now look at EXAMPLE 2. In that case, you *want* the last argument to be evaluated. "myFunc" is just a variable (not code). What you want to do is *evaluate* myFunc and pass in the result of the evaluation (which is a function).

If evaluation is the problem, one might try this:

Code: Select all

EXAMPLE 3:

   ...
   (scrAddAction gScreen 'myAction 0 "My Action" (eval myFunc))
   ...
But that doesn't work either. Why? Because the last argument is not evaluated! The eval function is not evaluated until the user invokes the action, not at the point that scrAddAction is called.

So that's the problem.

If scrAddAction evaluates the last argument, then EXAMPLE 1 will break.
But if scrAddAction doesn't evaluate the last argument, then EXAMPLE 2 will not work.

The only hack solution that I can think of is to have two flavors of scrAddAction:

scrAddAction
scrAddActionIndirect (or something like that)

scrAddActionIndirect would evaluate the last argument.

Does anyone have any other suggestions?
User avatar
alterecco
Fleet Officer
Fleet Officer
Posts: 1658
Joined: Wed Jan 14, 2009 3:08 am
Location: Previously enslaved by the Iocrym

I see a couple of solutions.

1) Always evaluate and use literals ("quote" the code) if you need to write code directly. Example:

Code: Select all

(scrAddAction gScreen 'myAction 0 "My Action" '(dbgOutput "Test"))
2) Never evaluate by default and add a forced evaluation syntax, á la lisp (I know this is not how lisp uses ',', but meh :):

Code: Select all

(scrAddAction gScreen 'myAction 0 "My Action" ,(lambda Nil (dbgOutput "Test")))
In both cases above the result of running "My Action" would be the output of "Test".

Re. ticket 580. I would really like to request one thing, whichever solution we end up with. That you set gActionPos and a gActionID whenever you run an action. gActionPos would be the position of the action, and gActionID would be either the ID of the action or Nil if the action does not have an ID (if it is hardcoded in the xml)
User avatar
Prophet
Militia Captain
Militia Captain
Posts: 826
Joined: Tue Nov 18, 2008 6:09 pm

1) Always evaluate and use literals ("quote" the code) if you need to write code directly. Example:


Code:
(scrAddAction gScreen 'myAction 0 "My Action" '(dbgOutput "Test"))

I vote for this solution, it's more tLisp-ish, where as, solution #2 is more common-Lisp-ish (read: foreign to us)
Coming soon: The Syrtian War adventure mod!
A Turret defense genre mod exploring the worst era in Earth's history.
Can you defend the Earth from the Syrtian invaders?
Stay tuned for updates!
george moromisato
Developer
Developer
Posts: 2998
Joined: Thu Jul 24, 2003 9:53 pm
Contact:

I agree that solution #1 is probably best. [Also, solution #2 is much harder to implement.]

I was originally concerned that solution #1 would be inconsistent with other functions. For example, the objEnumItems function takes an expression. I don't want to have to change that to require a quoted expression.

But I think we can follow this rule:

For a function, foo, of the form:

(foo exp)

1. If exp will be evaluated before foo returns, then exp *must not* be quoted.
2. If exp will be evaluated outside foo's scope, then exp *must* be quoted.

objEnumItems falls under rule #1, so we don't have to quote its argument.

scrAddAction falls under rule #2, so it needs to have a quoted argument.

[BTW, shpSetCommandCode probably needs to change since it would fall under #2. Any other functions?]

Thanks to both of you!
User avatar
alterecco
Fleet Officer
Fleet Officer
Posts: 1658
Joined: Wed Jan 14, 2009 3:08 am
Location: Previously enslaved by the Iocrym

Great. I can't see any other functions that would need to be changed.
Post Reply