Once we have a value, a named property can be applied to it. The result depends on the requested property:
- Some provide access to a data attribute of the value. For example, the 'size' property is used to get or set the number of members of a collection value.
- Others perform some work using the method assigned to the property. For example, the '+' property is used to add together two numbers.
- Some properties, called computed properties, do both.
The properties available to a value are determined by the value's type. Thus, the properties that can be applied to an Integer value will be different than those which apply to a List collection.
This chapter describes several ways to make use of a value's properties or methods.
The '.' Operator
The '.' operator is used to access a value's properties or methods which it inherits from its type.
Property Access
Use the dot operator '.' to explicitly access a value's property. The base value goes to the left and the name of the property goes to the right. The name can be a name token, a symbol token, or an expression in parentheses that evaluates to a symbol. Acorn uses the property it finds within the value's type.
For example, collections (such as a List) often offer the 'size' property which corresponds to the number of values held by the collection. Thus:
# alphabet is a List containing the letters of the alphabet alphabet.size # returns 26
To change any property's value, place it to left of an assignment operator:
alphabet.size = 10 # The alphabet list is now smaller, only having the letters "a"-"j"
When no base value is placed before the dot, Acorn uses this as the base value. Refer to this blocks for a more detailed treatment on how several operators like '.' implicitly make use of the 'this' value.
.size # equivalent to this.size
'null' is returned when using a property unknown to the value's type:
alphabet.mumble # returns null
Calculated property names
Property names are nearly always symbols. Most of the time, we know which property we want to use by name, so we just specify that name after the dot. Acorn treats this variable-like identifier after the dot as a symbol, rather than a variable.
If the property name requires characters not allowed in a variable name, enclose its name within single quotes:
alphabet.'size' # equivalent to: alphabet.size
For accessing a property whose name is dynamically specified, use an expression within parentheses that calculates the property's symbolic name:
shape.(attr) # if attr is 'color', equivalent to: shape.color
Method Calls
Calling a method also uses the '.' notation, but may add to it comma-separated values in parentheses to pass on to the method as its parameters. The base value (to the left of the dot) is passed to the method as the implicit parameter self:
# Insert characters into a text "pot".Insert(2, "u") # returns "pout"
Here is another example:
# For Floats, the '+' method adds the two numbers 4.0.'+'(2.1) # Returns 6.1 # For text values, the '+' method concatenates them "abc".'+'("def") # Returns "abcdef"
Three notes about this example:
- Acorn does support the '+' operator as a simpler way to do addition (see here).
- Since the property name of '+' uses a punctuation character not allowed in a name token, its name is enclosed within the single quotes of a symbol token (all property names are symbols).
- The example uses '+' twice: once to add two Float values and the other time to concatenate two Text values. In each case, Acorn uses the appropriate method as defined by the base value's type.
Return values from method calls
Most methods will return at least one value. The return value(s) can be ignored, if desired. If the requested method does not exist, 'null' is returned.
A method may return multiple values. One way to capture these values is with a parallel assignment:
a,b = list.Unpack # a receives the first return value, b the second
Passing a Variable Number of Parameters
A value returned from one method may be passed on to another method. If a method call is used as the last parameter and it returns multiple values, all those values are included as parameters:
# Log a message using position's unpacked x, y, and z values. $.Log("position at: %,%,%", position.Unpack)
Similarly, using the splat operator ('...') as the last specified parameter will include all its values as parameters.
# Passing on the remaining parameters received $.Log("msg %", ...)
Method Assignment
Like properties, method calls may appear to the left of an assignment:
thing.Add('tweeter') = 'simple'
This only works if the Add method is implemented using a get/set Closure that has a defined 'set' method. The value to be assigned is passed to the method as the first parameter, followed by the specified parameters. Thus, the example above calls the Add closure's 'set' method, passing it the two parameters 'simple' and 'tweeter'.
Calculated Method Names
Just like with properties, the symbolic name of a method may be calculated using an expression enclosed within parentheses:
thing.(verbsym)(a,b) # If verbsym is 'Add', equivalent to: thing.Add(a,b)
Alternatively, the expression may return an executable value (such as a method or closure) rather than a symbol. In this case, the method will be called directly (bypassing the step where the method to perform is retrieved using its symbolic name). This is helpful when calling anonymous methods.
self.(approach)(a,b) # approach is a variable holding an anonymous method
Method Chaining
Method calls can be appended end-to-end. They are performed from left to right, with the return value of the first being used as the base value of the second, and so on.
# Handle when frosty is hit by the summer sun frosty.Jump(10).Yell("Ow!").Grow(0.9)
Chaining depends on a convention where methods return 'self' when no other return value is expected. Thus, the value of frosty is effectively passed on to all three methods, making him jump, yell and shrink.
A similar technique, method cascading, is also supported using this blocks. Method cascading does not require that the cascaded methods return 'self'.
Computed Properties
So far, this chapter has presented properties and methods as distinctly different sorts of things. It is not quite that clear-cut. First of all, properties and methods share the same symbolic namespace as established by the base value's type. By convention, symbols that start with a lower case letter are typically properties. Capital-lettered symbols are typically methods.
How does Acorn know whether the dot operator is to access a property value or call a method? It doesn't look at the name, which is not enforced. If the inherited value assigned to that name refers to something executable it calls it. Otherwise, it treats the symbol's assigned value as a property to retrieve or change.
Computed properties are a hybrid between properties and methods. They look just like properties, as no parameters are specified. However, the computed property's result is performed by calling the property's defined method or closure.
For example, the '.size' property for collections is typically implemented as a computed property using a get/set closure.
The '.:' and '::' operators
The '.:' operator works exactly like '.' with one exception: Any executable value referred to by the inherited property or method name will not be called. Instead, that executable value (e.g., a Method) is returned as a first-class value. Using '.:' is the equivalent of treating methods (and computed properties) just like properties.
method = list.:size # retrieves the method or closure that list uses to get/set its size
The '::' operator is similar to '.:', except it works with a value's owned (rather than inherited) values. It is used to retrieve or set a specific value within a collection using the specified index. As with '.', the identifier after '::' is treated as a symbol rather than a variable. Like '.', '::' operations can be chained.
capitals::a = 'A' capitals::a # 'A' # Access Mr. Jones's barn's weather vane farm::barn::vane
Method Call Shortcuts
The language features described above are sufficient to perform any kind of property access or method call. However, to help make code more succinct and readable, Acorn offers a number of useful shortcuts for certain kinds of method calls, such as:
- Direct method calls
- [] Indexing
- Arithmetic, Collection and Comparison operators
- '+' and .. constructor operators
Direct method calls
It is possible to call a method directly without using the dot notation. This is useful for calling locally defined anonymous methods or methods retrieved using the '.:' operator. To perform this call, follow the variable (or expression) that holds the method with parentheses that enclose all parameters. This call automatically assumes self to be the base value (unless the called method is a closure that has bound self to another value):
# Assume the variable 'add' holds a method which adds together its two parameters add(4,5) # returns the value of 9. Equivalent to self.(add)(4,5)
[] Indexing
Indexing provides direct get or set access to specified elements within a collection. To index into a collection, follow the collection's value with the desired index enclosed in square brackets. Under the covers, Acorn uses the collection's '[]' method to perform the indexing.
# Assume the variable 'letters' holds a List containing the values: 'a', 'b', 'c' letters[0] # returns the value 'a'. Equivalent to: letters.'[]'(0) letters[1] = 'x' # changes the second value in letters from 'b' to 'x' # Assume the variable 'dict' holds an empty Index dict['word'] = "A collection of letters." dict['word'] # returns "A collection of letters." dict['gnihton'] # returns null, as no value exists at that index
Note that dict['word'] will typically give the same result as dict.word. However, it does not have to: '[]' uses a method which can be overridden. By contrast, '::' is an internal operator whose logic cannot be altered.
Arithmetic and Other Operators
Several operators may be used as syntactic short-cuts for method calls, where the method's symbolic name is typically the same as the operator.
Arithmetic Operators
Acorn supports the standard set of arithmetic operators, each of which maps to a specific method name. They can be used with any type that defines methods for them, such as Integer, Float, and various collection types:
- '+'
- Addition or concatenation. When '+' is used as a prefix operator, it creates new instance values (see below).
- '-'
- Subtraction or elimination. When '-' is used as a prefix operator (negative), its method name is '-@'.
- '*'
- Multiply or replication.
- '/'
- Divide or split.
- '%'
- Modulo or format.
- '**'
- Exponent.
Acorn applies the normal algebraic precedence order for these operators. Parentheses can be used to override the order of evaluation.
a+b*c # equivalent to a.'+'(b.'*'(c)) (a+b)*c # equivalent to (a.'+'(b)).'*'(c)
Arithmetic Assignment
For convenience, Acorn does support the arithmetic assignment operators: '+=', '-=', '*=', '/='.
a.b(c) += 1 # equivalent to a.b(c) = a.b(c) + 1, with the values a,b,c only evaluated once
Although these assignment operators do not perform the assignment using a method, the arithmetic operation is performed using a method.
Collection Operators
In addition, Acorn offers these collection-only operators:
- <<
- Append operator. This adds the value on the right to the ordered collection on the left (similar to '+', but it changes the existing collection rather than creating a new one).
- >>
- Prepend operator. Similar to append, but it inserts the second value into the beginning of the ordered collection.
- The '+' prefix is the equivalent of calling the '.New' constructor method on the value. Should any dot property be placed right after a '+'-created value, that property is applied after creating the instance using .New.
- '+' can be applied to any value, but unless it is a Type, it will likely return 'null' since non-type values generally do not implement the '.New' property.
- Other class methods besides '.New' may be defined as constructors for new instances. Calling them must be done explicitly, e.g.: Shape.NewSphere(4.0)
- For atomic types (e.g., an Integer), a literal effectively creates a new value of that type. However, one can use '+' to convert some value to an atomic value. For example: +Integer("124")
Comparison Operators
Several comparison operators are implemented using the '<=>' method. Additionally, the '~~' operator is implemented using a method of the same name. See the Conditional Expressions chapter for details on these operators.
'+' New Operator
The + prefix operator, followed by a type value, creates a new value (or instance) of that type. For example:
+List # Creates a new empty List. Equivalent to List.New
Parameters, enclosed in parentheses, can be added to guide how the new value is initialized:
+List(1,2,3) # Creates a new List that has three values
Alternatively, a literal symbol or string can be specified without parentheses, and will be treated as a single parameter:
+Regex"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"
Some notes:
Range Constructor
The '..' operator is a useful shortcut for creating new Range values:
# The space before .. prevents confusing 1 with the Float 1. 1 .. 4 # equivalent to: +Range(1,4) 1 .. 5 .. 2 # equivalent to: +Range(1,5,2)
Range values are useful for iterating over a range of values or for matching whether a value falls within an interval of values.