code examples

This is a moderated forum that collects tutorials, guides, and references for creating Transcendence extensions and scripts.
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

Just a thread to help out with some coding concepts. Please give your own or give concepts that you want to have an example of.

Making a function

Code: Select all

(setq testFunction (lambda Nil
	(dbgOutput "I made a function")
	))
Returning a value

this will return the string "Five" when you call it

Code: Select all

(setq returnFive (lambda Nil
	"Five"
	)) 
a more advanced thing recursion :D

This example will output to the debug console.
Unzip: 6 number: 6
Unzip: 5 number: 6
Unzip: 4 number: 6
Unzip: 3 number: 6
Unzip: 2 number: 6
Unzip: 1 number: 6
Unzip: 0 number: 6
6

A function can call itself. This is called recursion. Many things can be done with this just be careful if you are not careful as you can recurse forever. Look at the code and try to understand why it outputted what it did. The last 6 was because it returns number.

Code: Select all

(setq recursionExample (lambda (number)
	(block (Unzip)
		(setq Unzip number)
		(if (ls number 6)
			(setq number (recursionExample (add number 1)))
			)
		(dbgOutput (cat "Unzip: " Unzip " number: " number))
		number
		)
	))
Crying is not a proper retort!
User avatar
Ttech
Fleet Admiral
Fleet Admiral
Posts: 2767
Joined: Tue Nov 06, 2007 12:03 am
Location: Traveling in the TARDIS
Contact:

Betelgeuse wrote:Just a thread to help out with some coding concepts. Please give your own or give concepts that you want to have an example of.

Making a function

Code: Select all

(setq testFunction (lambda Nil
	(dbgOutput "I made a function")
	))
Returning a value

this will return the string "Five" when you call it

Code: Select all

(setq returnFive (lambda Nil
	"Five"
	)) 
a more advanced thing recursion :D

This example will output to the debug console.
Unzip: 6 number: 6
Unzip: 5 number: 6
Unzip: 4 number: 6
Unzip: 3 number: 6
Unzip: 2 number: 6
Unzip: 1 number: 6
Unzip: 0 number: 6
6

A function can call itself. This is called recursion. Many things can be done with this just be careful if you are not careful as you can recurse forever. Look at the code and try to understand why it outputted what it did. The last 6 was because it returns number.

Code: Select all

(setq recursionExample (lambda (number)
	(block (Unzip)
		(setq Unzip number)
		(if (ls number 6)
			(setq number (recursionExample (add number 1)))
			)
		(dbgOutput (cat "Unzip: " Unzip " number: " number))
		number
		)
	))
See this type of thing is what I ask about about before on that topic.
Image
Image
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

variables

A variable is something that contains something else. It can be a list, number, string, function, or many other things. There are three basic types of variables globals, temporary variables, and object attributes.

Globals are always accessible in code. In transcendence they are often prefixed by g. They can be set to Nil so don't assume they always have useful info in them.

Temporary variables are variables that are only valid in the code block they are created in. The most common examples of this are the block and lambda. In block they are not set right away and in lambda functions they are passed in as arguments. These variables are only valid in the block or lambda they are created and if the block or lambda is entered again it does not remember the previous values.

Object attributes (I just made that name up) are variables attached to objects such as ships or the player or stations. There are three types global (all objects of the same type share the same variable), normal (each object has its own set of normal variables and they don't share between objects of the same class), and static variables are variables that can only be gotten (such as the name).


can someone explain order of operations for the script? I am not exactly clear on that for functional lang. :oops:
Crying is not a proper retort!
User avatar
Ttech
Fleet Admiral
Fleet Admiral
Posts: 2767
Joined: Tue Nov 06, 2007 12:03 am
Location: Traveling in the TARDIS
Contact:

Betelgeuse wrote:variables

A variable is something that contains something else. It can be a list, number, string, function, or many other things. There are three basic types of variables globals, temporary variables, and object attributes.

Globals are always accessible in code. In transcendence they are often prefixed by g. They can be set to Nil so don't assume they always have useful info in them.

Temporary variables are variables that are only valid in the code block they are created in. The most common examples of this are the block and lambda. In block they are not set right away and in lambda functions they are passed in as arguments. These variables are only valid in the block or lambda they are created and if the block or lambda is entered again it does not remember the previous values.

Object attributes (I just made that name up) are variables attached to objects such as ships or the player or stations. There are three types global (all objects of the same type share the same variable), normal (each object has its own set of normal variables and they don't share between objects of the same class), and static variables are variables that can only be gotten (such as the name).


can someone explain order of operations for the script? I am not exactly clear on that for functional lang. :oops:
I ASKED THIS TOO. :P
Image
Image
Apemant
Commonwealth Pilot
Commonwealth Pilot
Posts: 94
Joined: Mon Dec 03, 2007 12:51 pm

Betelgeuse wrote:can someone explain order of operations for the script? I am not exactly clear on that for functional lang. :oops:
How do you mean order of operations? Like in 'operator priority'? There's no such thing here, it always evaluates left to right, and since it's prefix notation and not infix, priority doesn't apply. You don't have problems like in 2 + 3 * 4, since here it would be
(add 2 (multiply 3 4)) and it's pretty clear, because of parentheses, how it is evaluated.
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

I mean the order functions are run in. It is not always left to right or right to left. Its not even the innermost parentheses sometimes.
Crying is not a proper retort!
Apemant
Commonwealth Pilot
Commonwealth Pilot
Posts: 94
Joined: Mon Dec 03, 2007 12:51 pm

Betelgeuse wrote:I mean the order functions are run in. It is not always left to right or right to left. Its not even the innermost parentheses sometimes.
Can you give me an example of a code which doesn't work as someone would expect?
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

any lambda function or any code that is used in argument lists of code. Is the biggest one that I know of. There maybe other times where it comes into play.

Code: Select all

(setq func (lambda Nil
	(dbgOutPut "this will not output before setq is run")
	)
Contrast that with what you might expect (left to right innermost parens)

Code: Select all

(add 3 (multiply 2 2))
Crying is not a proper retort!
Apemant
Commonwealth Pilot
Commonwealth Pilot
Posts: 94
Joined: Mon Dec 03, 2007 12:51 pm

Betelgeuse wrote:any lambda function or any code that is used in argument lists of code. Is the biggest one that I know of. There maybe other times where it comes into play.

Code: Select all

(setq func (lambda Nil
	(dbgOutPut "this will not output before setq is run")
	)
Hmm, well I guess it might look odd to someone, but to me it looks perfectly natural. Lambda functions purpose is to define a function which should be run LATER, when it's invoked. So of course it won't run dbgOutput right then, but when you do a (func), i.e. invoke the lambda function.

But it still doesn't violate the left to right rule. It goes like this: setq command puts an argument into a variable. In this case, it puts a definition of a lambda function into a 'func' variable. Perfectly left to right :), of course, when you take parentheses into account; you can't put an argument into a variable before you actually figure out what the argument is. So parentheses are the thing that governs what gets evaluated first; other than that, it's left to right, taking into account WHAT a function really does. 'lambda' doesn't invoke its body right away, but when it's invoked, either directly or with an 'apply'.
Betelgeuse wrote: Contrast that with what you might expect (left to right innermost parens)

Code: Select all

(add 3 (multiply 2 2))
I expect this to return 7, is it not correct? Left to right it goes like this: add 3 to something, that something being the result of multiply 2 2. It still IS left to right, because 3 is evaluated before (multiply 2 2). To see what I mean, consider this: (add (multiply 2 3) (multiply 2 2)). Multiply 2 3 will always get executed before multiply 2 2, precisely because of the left-to right rule. But of course, left to right doesn't mean you can ignore parentheses, of course that you have to evaluate something within parentheses as soon as you get to it. It just feels completely natural to me. The language is probably heavily dependant on some sort of stack which holds everything not accounted for yet.
Last edited by Apemant on Tue Dec 11, 2007 10:44 am, edited 1 time in total.
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

Looking perfectly natural isn't good enough. You must come up with a set of rules that people can understand even if it doesn't look "perfectly natural".
The reason we need these rules is because people might not get it. Saying it is perfectly natural seems a bit insulting. Also you can not say a functions purpose as part of the rules sometimes the purpose maybe marred by bugs but the rules would still apply.
Crying is not a proper retort!
Apemant
Commonwealth Pilot
Commonwealth Pilot
Posts: 94
Joined: Mon Dec 03, 2007 12:51 pm

Betelgeuse wrote:Looking perfectly natural isn't good enough. You must come up with a set of rules that people can understand even if it doesn't look "perfectly natural".
The reason we need these rules is because people might not get it. Saying it is perfectly natural seems a bit insulting. Also you can not say a functions purpose as part of the rules sometimes the purpose maybe marred by bugs but the rules would still apply.
Al right, how about this: let's ask people what they would expect a (add 3 (multiply 2 2)) should do. Give them just one basic rule: everything gets evaluated left to right. :)

I really believe that parentheses thing IS natural, not just to a coder but everyone who attended math in school. A person might not know that 2*3+4 doesn't get evaluated the same way as 2+3*4 if they forgot about operator priority; but I really don't believe that anyone could interpret 2+(3*4) or (2+3)*4 in a wrong way. Don't see what is insulting about it.

So, the game always evaluates things left to right, one arg after another. It is the simplest rule you can think of; lambda's aren't an exception; when you explain to someone what lambda keyword does, it is clear that it doesn't violate that rule either.

(lambda argList body) DEFINES an unnamed function. It's very purpose is to allow an user function to be made, so of course its body is not evaluated immediately, but upon the invoking of that lambda function. Since it's unnamed, you wouldn't be able to invoke it later, if you didn't place it inside a variable. Hence it's always used in conjuction with setq.

And neither are parentheses an exception; everything inside them just belongs to its own 'subcontext' in which left to right also applies; but when you look at it as a 'black-box' it just doesn't violate that rule either. But of course that the rule itself is not enough to understand just ANY bit of code (there's no such language with such an easy rule); you also need to understand what every command does, esp. those structural commands like if, switch, lambda, enum, etc.

Here is a nice incremental example which outlines the basic idea of this lisp-like script language:

Every 'script' in this language contains of just ONE command/function. Exactly one, there is no script with more than one function. Of course, that ONE function might be a 'block' function which, in turn, lumps several other function together. But in that case, the block function is that one function that each script is made of.

Here is a nice example of a short script:

Code: Select all

2
This is a perfectly valid script; it does nothing except evaluating the number 2, but the game won't complain. It's legal. Now let's take a look at slightly more 'advanced' example:

Code: Select all

(objSendMessage gPlayerShip Nil "Hello, world!")
This is also a legal script, but this one would be illegal:

Code: Select all

(objSendMessage gPlayerShip Nil "Hello, world!")
(objSendMessage gPlayerShip Nil "Goodbye, cruel world!")
Actually, I believe the game would just ignore the second objSendMessage and display only the first. Why? Because a script should contain only 1 'command' (argument/function/whatever) in the outermost context, and this example contains 2. This also introduces the concept of parentheses: whenever a script expects something single, you can give it a bit of code surrounded by parentheses, and the script language will treat it as a 'single' thing. Therefore, (objSendMessage gPlayerShip Nil "Hello, world!") is still just ONE command, the language treats it like (...) , i.e. this thing inside is a black box when looked from the outside context. Only when we evaluate the stuff inside parentheses we care what it is actually made of.

Another example of this 'single command/function' is this:

(if condition command elsecommand)

For example, this would be valid:
(if 1 3 4). From the outside, everything inside parentheses would just evaluate as 3. Why? Because '1' would be the 'condition', 3 would be 'command' and 4 would be 'elsecommand'; and if works like this: evaluate the 'condition'; if it is non-Nil, invoke 'command', and if it is Nil, invoke 'elsecommand'. Since 1 is non-Nil, 3 would be invoked, and 3 does nothing except evaluating as 3.

So try it yourself:
(objSendMessage gPlayerShip Nil (if 1 3 4))

or try
(objSendMessage gPlayerShip Nil (if Nil 3 4))

It is important to notice that 'if' command expects a SINGLE argument as both 'condition' and 'command/elsecommand'. If you need something more useful than just direct numbers, that must be inside parentheses, because, as I said, everything inside parentheses counts as a 'single' argument when looked at from the outside context (in this case, the context of the if function). Also, the 'elsecommand' is optional, (if 1 3) is also legal. For example:

(if (eq 1 1) (objSendMessage gPlayerShip Nil "1 indeed equals 1"))

in this example, 'condition' is (eq 1 1) and 'command' is (objSendMessage gPlayerShip Nil "1 indeed equals 1"). Easy to notice that the format of the if is really exactly the same, when you treat everything inside parentheses as a 'single' argument. It's completely analogous to (if 1 3).

Now how do we perform more than one command if some condition is fulfilled? Here comes the 'block' function: it allows you to lump several functions together, and treat them as a 'single' argument from outside the parentheses:

(block varList func1 func2 func3...)

So this entire parenthesed bit of code will be treated as this 'single' argument that if function expects. In other words, the basic layout of 'if' function is still the same:

Code: Select all

(if (shpIsRadioactive gPlayerShip)
    (block Nil
        (objSendMessage gPlayerShip Nil "Damn! We are radioactive!")
        (objSendMessage gPlayerShip Nil "Find a commonwealth station ASAP!")
    )
)
Now it gets clumsy with all the parentheses, but the point is: the entire (block ... ) thing, from the viewpoint of the 'if' function, is still just a SINGLE function, so the outline is exactly the same as in (if 1 3) example. It's just that both '1' and '3' are now something more complex.

Now let's look a the 'lambda' thing, and then I'm done for now:

(lambda argList body)

What does this do? Just as (if 1 3) evaluates as 3 from the outside of those parentheses around it, (lambda args body) evaluates as something (a nameless function) which does nothing initially, but can be invoked later, and it will then perform whatever 'body' is. Here is a completely basic, but working example:

(lambda Nil 2)

What does this do? Well, whenever you would invoke this, it would just return 2. Not very useful, but it will serve my purpose. Now, since this 'lambda' is something unnamed, I just wouldn't be able to invoke it later. So what do I do? I put it inside a certain variable using 'setq' command, like this:

(setq two (lambda Nil 2))

Now if I did
(objSendMessage gPlayerShip Nil (two)) - it would display 2. Because 'two' is the name of a variable, which contains this lambda function that just returns 2.

Of course, such lambda functions aren't very useful. So let's do a slighty more complicated (but stll useless :lol: ) example:

(setq addtwo (lambda (num) (add num 2))

Here, 'argList' is (num) and 'body' is (add num 2). This defines a nameless function (lambda function) which takes one parameter (num), and returns that parameter incremented by 2. We also put that (otherwise nameless) function into an 'addtwo' variable, so we can invoke it later.

(objSendMessage gPlayerShip Nil (addtwo 6)) - would now display '8'.

What this entire little course wants to demonstrate: that there is nothing magical about parentheses and functions. They all do what they are supposed to do, they all expect certain arguments, and those arguments can actually be something much more complex, but placed inside parentheses, so that a function treats it like a single argument. There are a couple of exceptions to this, but I don't want to complicate things even further.

(BTW, this isn't for you, Betelgeuse, as you already know all this; but for those who feel utterly bewildered by the script syntax and the principles of how it works)
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

Many functions can be passed code and with the help of lambda you can even pass code to user definded functions without it being evaluated or even named. Such as objEnumItems.
how does this sound (I did test the same level thing you can't assume those kinds of things).

Functions are evaluated from innermost to outermost and from left to right in cases of at the same level. The exception to this rules is when the function expects to be passed a function then that function is not evaluated at that time.

question do you know a way of stating for a xml function to say it is expecting a function so the code isn't run right away?
Crying is not a proper retort!
Apemant
Commonwealth Pilot
Commonwealth Pilot
Posts: 94
Joined: Mon Dec 03, 2007 12:51 pm

Betelgeuse wrote:Many functions can be passed code and with the help of lambda you can even pass code to user definded functions without it being evaluated or even named. Such as objEnumItems.
how does this sound (I did test the same level thing you can't assume those kinds of things).

Functions are evaluated from innermost to outermost and from left to right in cases of at the same level. The exception to this rules is when the function expects to be passed a function then that function is not evaluated at that time.
You really believe this sounds better to a beginner than a simple 'left to right'? Those are too advanced constructs for a casual modder who isn't a coder himself; you are just going to confuse them.

Either that, or you are in fact confused yourself: there are no functions which expect a function to be passed in some magical way. It really is simple: if you put a lambda inside a certain variable, for example, (setq two (lambda Nil 2)), then you have two options. If you pass this variable 'two' directly as a variable, for example (anotherLambda two), then the lambda inside 'two' will NOT be evaluated immediatelly, but just passed along to the body of anotherLambda. However, if you do (anotherLambda (two)), THEN the lambda inside 'two' will be evaluated before passing along the result to anotherLambda. Try this:

Code: Select all

(setq two (lambda Nil 2))
(setq anotherLambda (lambda (arg)
    (if (isfunction arg)
        (objSendMessage gPlayerShip Nil (cat "Arg is a function that evaluates to " (arg)))
        (objSendMessage gPlayerShip Nil (cat "Arg is not a function but just " arg))
    )
))
Then try invoking (anotherLambda two) and (anotherLambda (two)). The result is completely predictable, if you passed 'two' without parentheses it just got passed down without evaluation, as a variable (which contains a lambda function), and if you put it inside parentheses, it got evaluated first and the result was passed to anotherLambda. However, if you do a (anotherLambda (lambda Nil 2)) then anotherLambda will ALWAYS receive a lambda function, and never 2; the lambda is never invoked at the time of its definition. Furthermore, anotherLambda could then pass this argument to a third lambda function; again, if it just passed it as 'arg', it would go down without evaluation; and if passed it down as '(arg)' then it would be evaluated first and the result sent down to that third lambda in the chain. So you have complete control over what evaluates when. A lambda is never invoked but passed along un-evaluated all the way till you either invoke it directly, by putting it as a first thing after an open parenthesis (because the parser then expects a function to invoke), or using the 'apply' function whose purpose is really hard to explain to a beginner (it turns a single argument of a type 'list', into an actual argument list. How do you explain it to a beginner? I don't know)

So there is no magic behind the scenes and no special rules of evaluation that break down simple left to right. You just have to take parentheses into consideration; when the left to right evaluation at certain level encounters something in parentheses, of course it has to stop evaluation on that level, create a sublevel and evaluate whatever is inside parentheses, and then return to the original level, yielding a single argument to that level. But that just doesn't mean it wasn't still left to right - everything in front of parentheses WAS ALREADY evaluated (and probably placed onto a stack of some kind), it wasn't skipped to first evaluate the expression in parentheses!

If you don't believe, try this

Code: Select all

(setq two (lambda Nil
    (block Nil
        (dbgOutput "two is being evaluated")
        2
    )
))
(setq three (lambda Nil
    (block Nil
        (dbgOutput "three is being evaluated")
        3
    )
))
(setq four (lambda Nil
    (block Nil
        (dbgOutput "four is being evaluated")
        4
    )
))

(add (four) (multiply (two) (three)))

What do you think will happen? It will output -
four is being evaluated
two is being evaluated
three is being evaluated
(and then it would yield 10)

Which clearly demonstrates that four was evaluated before the stuff in parentheses that follow; i.e. left to right still holds.

Edit: of course, left to right can't be taken literally, in a mindless way. For example, what would left to right mean in this example:
(add 2 3)
The parser first encounters 'add'. But how can it perform 'adding' when it doesn't know what to add? So of course, it must also evaluate 2 and 3 before it can finally execute the add. So should we claim that the exact order is 2, 3, add? Would it improve the clarity of syntax logic? Well, maybe.
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

Well you have to take in to account parentheses are the most important in a functional language. Things happening at the same level is the rare exception to the rule. Why say read left to right when they are not run left to right 99% of the time? I am not saying it is right to left I am saying that saying left to right isn't helpful. You can also say top to bottom for blocks and that is alot more common condition but also not helpful in reading most of the code.

Maybe it should be put in order.

This is the order of operation.

1. Run from innermost function to outermost function.
2. In cases of a tie things to the left of or above get run first.
3. When passing to a function that is expecting a function as an argument the argument function is not run at that time.
4. Things prepended by ' are not run.

I think the problem here is that how you would read the code isn't how it is run. You would read it left to right but that says little on how it is run. I am not trying to make rules on how to read code.
Crying is not a proper retort!
User avatar
Betelgeuse
Fleet Officer
Fleet Officer
Posts: 1920
Joined: Sun Mar 05, 2006 6:31 am

now that I think about it some more I think you are right the left to right rule should be first (they both would have different outcomes in some code but I am too lazy to test right now). But you really need to explain more on the parens. They are scary to non coders.

ps: I blame this whole argument on lack of sleep
Last edited by Betelgeuse on Tue Dec 11, 2007 4:11 pm, edited 1 time in total.
Crying is not a proper retort!
Post Reply