Guide:Assignment

From IokeWiki
Revision as of 01:07, 26 January 2009 by Cv (talk | contribs) (New page: = Assignment = Superficially, Ioke's assignment model is quite simple. But there exists some subtleties too. One of the main reasons for this is that assigning a cell that doesn't exist w...)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Assignment

Superficially, Ioke's assignment model is quite simple. But there exists some subtleties too. One of the main reasons for this is that assigning a cell that doesn't exist will create that cell. Where it gets created is different based on what kind of context the assignment happens in. The main difference here is between a method activation context, or a lexical block context.

Ioke also supports assignment of places, which makes assigning much more flexible. A third feature of Ioke assignment is that it will check for the existence of an assignment method before assigning a specific name. This chapter will make all these things clear, and show some examples.

Let's start with a small example of simple assignment:

foo = Origin mimic
foo x = 42
foo y = 13
foo x += 2

The first line creates a new Origin mimic, and then assigns that to the name foo. Since this code executes at the top level, "foo" will be a new cell created in Ground. The second line creates a new cell called "x" inside the "foo" object. It gets assigned the value 42. The third line creates a "y" cell, and the fourth line sends the += message, which will first call +, and then assign using =. So at the end of this program, "foo" will contain two cells: "x" with value 44, and "y" with value 13. As mentioned above, cells get created the first time they are assigned to. If you need to create a cell in a specific object, just namespace it. For example, if you want to make sure that you create a cell in Ground, just do "Ground foo = 42".

Inside of a method, the situation is exactly the same. If you assign something, it will be assigned in the current context, which is the local activation context (meaning it's the place where local variables are available). There are two situations where this doesn't hold true. The first one is within the special method "do". This method will take any code as argument and execute that with the receiver of the "do" message as the ground/context of the code inside it. That means "do" is a good way to create new cells inside an object.

This is a bit academic, so lets take a look at an example:

Foo = Origin mimic
Foo x = method(
  ;; this creates a local variable in the method activation
  foo = 42
)

Foo = Origin mimic
Foo do(
  ;; this creates the cell foo inside of Foo
  foo = 42
)

Here you can see a method defined called x. This method will just create a new local cell, which means calling the method will not make any difference on its receiver at all. The call to "do" in contrast will immediately execute the code inside it, and this code will create the cell "foo" inside of "Foo".

The second exception to the general rule is when executing inside of a lexical context. A lexical context is basically established inside of a block, but can also be created transparently when sending code to a method. A lexical block will try to not create new cells. When you assign a cell without a specific place to assign it, a lexical block will first see if there is any cell with that name further out, and if so it will make the assignment there instead. Only when no such cell exists, a new cell will be created in the lexical context. This code shows this in action:

x = 42
fn(x = 43. y = 42) call
x ;; => 43
y ;; => Condition Error NoSuchCell

The "fn" message creates a new lexical block. The chapter on code will talk more about this. But as you can see, this block assigns 43 to the cell "x", and 42 to the cell "y". But since the cell "y" doesn't exist, it will only be created inside the lexical context, while "x" exists outside, and will be assigned a new value instead. The basic idea is that code like this should behave like you expect it to behave.

The canonical form of assignment is a bit different from the way you usually write code in Ioke. The section on the syntax of assignments talked a bit about this. Specifically, something like "foo = 42" will get translated into "=(foo, 42)". That also means that assignment is just a regular method call, and can be overridden or removed just like any other method. That is exactly how both lexical context, and local method context make it possible to have different logic here. This is true for all assignment operators.

All assignment operators take as their first argument the place to assign to. This place will be unevaluated. Only the second argument to an assignment will be evaluated. In most cases, a place is the same thing as a cell name, but it doesn't have to be. Let's look at the case of assigning a cell with a strange name. Say we want to assign the cell with the no name. We can do it like this:

cell("") = 42

What happens here is a bit subtle. Since the left hand side of the assignment takes arguments, the "=" method figures out that the assignment is not to a simple cell name, but to a place. The parsing step will change "cell("") = 42" into "=(cell(""), 42)". Notice here that the argument comes along into the specification of the place. When this happens, the assignment operator will not try to create or assign a cell - instead it will in this case call the method cell=. So "cell("") = 42" will ultimately end up being the same as "cell=("", 42)". This way of transforming the name will work the same for all cases, so you can have as many arguments as you want to the place on the left hand side. The equals sign will be added to the method name, and a message will be sent to that instead.

This makes assignment of places highly flexible, and the only thing you need to do is implement methods with the right names. This feature is used extensively in Lists and Dicts to make it easy to assign to specific indexes. So, say we have a list called x. Then this code: "x[13] = 42" will be transformed into "x =([](13), 42)" which will in turn be transformed into "x []=(13, 42)". Ioke lists also has an at= method, so you can do "x at(13) = 42" which will call at=, of course.

The second transformation that might happen is that if you try to assign a cell that has an assigner, you will call that assigner instead of actually assigning a cell. So, for example, if you do "foo documentation = 42", this will not actually create or assign the cell "documentation". Instead it will find that Base has a cell called "documentation=", and instead send that message. So the prior code would actually be equivalent to "foo documentation=(42)".

All of these assignment processes together make it really easy to take control over assignment, while still making it very obvious and natural in most cases.

Let

Sometimes you really need to change the value of something temporarily, but then make sure that the value gets set back to the original value afterwards. Other situations often arise when you want to have a new name bound to something, but maybe not for a complete method. This might be really useful to create closures, and also to create localized helper methods. For example, the Ioke implementations for Enumerable use a helper syntax macro. This macro is bound temporarily, using a let form. This ensures that the syntax doesn't stay around and pollute the namespace.

A let in Ioke can do two different things, that on the surface look mostly the same but are really very different operations. The first one is to introduce new lexical bindings, and the second is to do a dynamic rebind of a specific place. The easiest way of thinking about it is that the lexical binding introduces a local change or addition to the available names you're currently using, while a dynamic rebinding will change the global state temporarily, and then set it back.

This sounds really academic, so let us go for some examples. We begin with lexical bindings.

;; put everything in a method to show explicit scope
foo = method(x, y,
  x println ; => argument value of x
  let(x, 14,
    x println ; => 14
  )
  x println ; => argument value of x

  y println ; => argument value of y
  y = 13
  y println ; => 13
  let(y, 14,
    y println ; => 14
  )
  y println ; => 13

  z println ; will signal condition
  let(z, 42,
    z println ; => 42
  )
  z println ; will signal condition
)

Here a new method is created that has two arguments, x and y. The first let-expression will create a new scope where a binding from x to 14 is established. This binding is valid until the end of the let-form (but it can be changed, doing an assignment will set the value to something else, but only until the end of the let form). The same thing is true with y. We can change the value of y outside of the let form. That changes the actual argument variable. But a let form that binds y will only have it active for a limited time. Finally, a let form can also create totally new variables, as when creating z.

I didn't show any example of it, but the first part of a let-name can be any kind of place, not just a simple name. Anything you can use with =, can be used as a name for let. So you could do something like let(cell(""), 42, nil) if you wanted to.

OK, so that's lexical binding. What about dynamic rebinding? The main difference in a dynamic binding is that the scope you work in is something that is referencable from other scopes. In most cases this will be global places, but not necessarily. You can also rebind cells inside of other objects with the dynamic binding feature.

bar = method(
  let(Origin foo, method("haha" println),
    "x" foo
    Origin foo
    [1,2,3] foo
  )

  let(Text something, 42,
    "abc" something println ; => 42
  )
  "abc" something println ; will signal condition

  let(Text inspect, "HAHA",
    "foo bar qux" inspect println ; => "HAHA"
  )
  "foo bar qux" inspect println ; => #["foo bar qux"]

  let(Text asRational, method(42),
    (3 + "haha") println ; => 45
  )
)

This example actually changes things quote a lot. The first and second examples introduce new cells into existing places, uses them and then doesn't do anything. The third example actually overrides an existing cell in Text - inspect - and then uses it inside of the let code. Finally, after the let block is over, we see that the original method is back. The fourth example shows that our changes with let actually are global. There is no asRational on Text, but we add it temporarily and can then use it in arithmetic with numbers. This is once again a temporary change that will disappear afterwards.

Ioke's let-form is incredibly powerful, and it allows very nice temporal and localized changes. Of course, it's a power that can be abused, but it gives lots of interesting possibilities for expression.