To make our parts smart, we give them procedural instructions packaged up as methods and functions. Methods and functions are basically a sequence of instructions that are performed when requested. There are many kinds of instructions, with expressions being the most commonly used. Others will be introduced over the next two chapters.

Acorn encourages a part-oriented architecture for the world's behavioral logic:

These design principles make a world's design easier to understand and maintain.

Method Definition

In the previous chapter, we introduced how to invoke type-specific methods on values. Each of WOM's specialized parts for building a world (such as Scene or Entity) provide pre-defined methods for accessing a part's contents or controlling its behavior.

What's cool is that we can further customize a part's behavior by adding methods to it which give it unique motion or interaction behaviors. In Acorn, such methods are simply another type of keyed value that is added to a part. similar to adding a property to a part (though in a different Index). For example:

# Teach frosty how to 'jump'
frosty +[
	# Define a method named 'jump', which takes a single parameter 'velocity'.
	jump(velocity):>
		# Tab-indented statements are performed when the method is called
		::vel = velocity

# Later on, we can use the method
frosty.jump(5.0)

A method consists of its name and parameters to the left of the method definition operator :>. Its indented instruction block follows. Instructions can make use of method-specific variables self and this. Instructions can return value(s) to the method caller. Let's explore each of these in greater detail.

Method Name

The method name is a symbol. It is used to store the method definition (which is a type of value) in the part's method Index (distinct from the part's property Index) using the name as a key.

The nature of the name is meaningful:

Parameters

A method definition typically lists the names for parameters passed to the method when invoked. These names may be enclosed in parentheses and are separated by commas. The names are used in the method's instructions to retrieve the passed values:

o = Part[
	adder(a,b):>
		a+b

o.adder(3,4)	 # returns 7
o.adder("a","b") # returns "ab"

If a passed value is a collection or compound value, its content can be changed when passed to a method. Numbers and other immediate values may change within a method, but are unchanged where they are passed.

o = Part[
	changer(a,b):>
		a=3       # within the method, a is now 3
		b.clear!  # empty b's collection

x=5
y="abc"
o.changer(x,y)  # will change the y collection, but cannot change a number
m,n = x,y     # m is 5, but n is ""

The number of parameters passed can be different than the number defined. Any excess are ignored. Missing parameters are assigned to null.

To capture a variable number of parameters, use the ellipsis before a final parameter name. The excess parameters will be turned into a Tuple and passed into that parameter name.

o = Part[
	printer(dev..parms):>
		dev.print(parms)

o.printer(console, "number is", 3)

Local variables

Local variables (starting with a lower case letter) were introduced in the previous chapter. Now we can clarify that local variables are local to the method they appear in. Each use of a method has its own collection of values attached to its local variables. When a method is invoked, all local variables have a value of null. When the method is finished, its local variable values cease to exist.

The method's parameters are in effect also local variables. However, their initial value is the values actually passed to the method when it is called.

self

In addition to the named parameters, there is also an assumed parameter named "self". The value of self is basically the value that the method was applied to.

slow(factor):>
	noise = Float.rand  # noise is a local variable
	factor *= noise     # parameter value can be altered
	self.vel *= factor  # change self's velocity

This variable and block

Sometimes there is one variable in a method that is getting most of the attention. Acorn knows this variable as "this", and allows it to be omitted from expressions before member look-ups or method calls.

At the start of a method, this is assigned to self. However, it can be changed easily using a "this" block. An expression instruction establishes the value of "this", which can then be used implicitly by all tab-indented statements in the following block. This accomplishes something similar to jQuery's chaining, but in a more flexible way.

sample:>
	v = .v  # local variable v assigned to self.x
	.k      # resets 'this' to self.k
		.vel += Xyz[1,0,0]  # .vel refers to self.k.vel
		.move v
	.v += 1 # this is back to self again

Return value

A method is called as part of an expression. When the method's code is done executing and ready to restore execution back to the calling expression, it returns back one or more values.

By default, the method returns the value of the last instruction's expression. However, a "return" instruction can be placed anywhere within a method's code that accomplishing the same this, returning control to the calling expression, passing back the value of any expression that follows the return instruction.

sq:>
	self*self   # same as return self*self

dist:>
	return (self.x.sq + self.y.sq + self.z.sq).sqrt
	self.sq     $ ignored, since we have already returned
	
Xyz[3,4,0].dist  # Calculates as 5.0

Function Definition

Functions are very much like methods: they have parameters, local variables, and an instruction block. They also return values:

sq = (x)->   # -> is the operator for defining a function
	x*x

Notice that a function definition is a value (of type Function), which means we can store it in a variable. The sq variable can then be used to call the function, passing comma-separated parameters:

sq(4)  # calls the function stored in sq, passing 4 for x. Returns 16

Notice that the grammar for a function call looks just like a selector for a collection. Acorn sees them as a conceptually similar way to do functional mapping.

Note: Empty parentheses are required when a function call has no parameters to pass. When parameters and parentheses are both missing, the value returned is the function itself.

Function vs. Method

Pretty much everything described earlier for methods apply to functions, other than these key differences:

In most cases, reusable instructions should be packaged as a method, when the instructions focus on a value of a particular type. However, functions have a prominent role in several scenarios:

Local Variable Sharing

Functions are often defined within methods or other functions. Despite looking like one piece of code, there is no sharing of local variables between the inner function and the outer function or method it is declared within. To share values, they must be passed as parameters or returned as values.

Put another way, Acorn functions are not closures.

Type Definition

In this chapter, we have explained how to define a method or a function, either of which can be added to a part to define its behavior and give it interactive "smarts". What if we wanted to create a collection or library of methods and/or functions that we could apply across multiple parts. For example, we might want to define a swinging open and close behavior for doors, a generic walk and run behavior for all legged animals, etc.

That is what a Type really is: a Part that contains a reusable library of methods and/or functions that automatically apply to all values that have subscribed to it. As always, the builder is used to define a new Type:

# Define a new Type called Animal
Animal = Type[
	Part     # Animals get all Part methods
	
	# Define a Animal method called 'speak'	
	speak(word):>
		$.play ::vocab(word)  # Look up the word in the animal's vocabulary, then play it

Once a new type has been defined, the builder can be used to create a new part based on that type:

# Create a new Animal using the brand-new type just defined		
fred = Animal[
	vocab: Index[hello: "I hate you"]

# Ask fred to say 'hello' in his vocabulary
fred.speak('hello')	# plays "I hate you"

Every animal value we create using the new Animal type will use the same method. However, since each animal has a different vocabulary, they will each say hello in all sorts of colorfully different ways.

Multiple Inheritance and Mixins

When we defined Animal above, we started with a line Part, which refers to the Acorn's Part type. Listing types within a new type is how one includes, by reference, additional libraries of methods within a new library of methods.

The same technique can be used to add libraries of methods to a single part:

frosty +[
	MoonBayFever   # Makes the MoonBayFever type's methods accessible to frosty, poor dear

In effect, all Types and Parts keep a List of types whose methods they have access to. Any changes to the methods for a type are automatically seen by all types or parts subscribed to its methods.

Method Search

Given all the interconnected method libraries used by values, how does Acorn select which method to use against a value?

  1. If the value is a Part (but not a Type), it will first look up the method symbol in the part's own method library. Even though a Type is a Part, we won't do this for a Type because a Type's methods are intended for use by the values it makes, and not for the Type itself.

    Failing that, Acorn searches each of the part's added type libraries one-by-one, in the order they were added to the part. Each of these type libraries may point to other type libraries to search through.

  2. For non-Parts, Acorn looks for the method in the value's type, then recursively through any type libraries it references, going all the way back ultimately to the universal type All.
  3. Failing that, Acorn call the "._mnf" method-not-found method, starting the search all over again (TBD). Failing everything, the null value is returned.

If for performance or other reasons, you wish to avoid this search, do a method call by passing the method itself, rather than the method's Symbol name:

frosty.MoonBayFever::bayAtMoon

Conclusion

Now that you can add methods, functions, and type libraries to any part in your 3-D world, how will you make your static world come alive with interesting, complex behaviors?

What else do you need to improve it further? How about the ability to evaluate the state of the world and change behavior accordingly...

_