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:
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
) 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)