essenceDocumentation
essenceDocs
on this pageCalling a method
Coming from JavaScript?

Method calls

Call a method with :: and labelled arguments, and do arithmetic, comparison and logic with methods, because there are no operators.

A method is called on a value with ::, and a labelled argument carries its label in front of the value: price::multiply(with 3). Essence has no operators, so JavaScript’s +, === and && are the methods add, is and and. This page covers the call form, chaining, and the arithmetic, comparison and logic methods every later page uses.

implementation {
	constant price    = 1999
	constant quantity = 3

	constant subtotal = price::multiply(with quantity)

	Terminal.print(subtotal) § 5997
	Terminal.print(subtotal::add(500)) § 6497
	Terminal.print(subtotal::isGreaterThan(5000)) § true
	Terminal.print(subtotal::clamp(between 0, and 5000)) § 5000
}

price is the receiver: the value the method is called on. multiply is the method, and with quantity is its one argument, labelled with. A call is an expression. It has a value, and that value can be printed, bound to a constant, or handed to the next call.

Calling a method

A call is receiver::name(arguments). Each argument is either positional or labelled.

implementation {
	constant temperature = 104

	§ one positional argument
	Terminal.print(temperature::subtract(4)) § 100

	§ one labelled argument: the label stands in front of its value
	Terminal.print(temperature::multiply(with 2)) § 208

	§ a positional argument, then a labelled one
	Terminal.print(temperature::isBetween(90, and 110)) § true

	§ two labelled arguments, in the order the method declares them
	Terminal.print(temperature::clamp(between 0, and 100)) § 100
}

A label is a word written in front of its value, with only a space between them. There is no colon and no =. clamp(between: 0, and: 100) is a syntax-error, Expected an Expression but found ':'.

Labels are part of a method’s signature, and a call must write them. A method can have several overloads, versions that differ in their parameters. A call picks one by its labels first, then by its argument types.

These pages write a method with its labels, as clamp(between:and:), item(at:) or isBetween(_:and:), where _ marks a positional parameter. Each library page gives the full signature, as isBetween(_ lower: Integer, and upper: Integer) -> Boolean. The parameter of subtract is positional, so temperature::subtract(4) takes no label.

A call writes each argument’s label, in the order the method declares them. A missing, misspelt or reordered label is refused, as the last section of this page shows. A parameter may declare a default value, and then a call can leave that argument out. round has one: 7/2::round() passes no rounding direction and rounds to the nearest whole number, 4. Functions covers default values.

A trailing comma after the last argument is allowed. essence format removes it from a call that fits on one line, and writes one when it breaks a call over lines.

Chaining calls

A call’s result is a value, so it can be the receiver of the next call. A chain reads left to right, one call after another, with no precedence to remember.

implementation {
	constant fahrenheit = 212

	§ each result is the receiver of the next call, left to right
	Terminal.print(fahrenheit::subtract(32)::multiply(with 5)::divide(by 9)) § 100

	§ a long chain is broken over lines, with `::` leading each line
	Terminal.print(
		fahrenheit
			::subtract(32)
			::multiply(with 5)
			::divide(by 9)
			::clamp(between -40, and 40),
	) § 40
}

The first chain reads as ((212 - 32) * 5) / 9. A chain too long for one line is broken with :: at the start of each continued line, indented one step under the receiver. That is the layout essence format writes.

Grouping without parentheses

There are no grouping parentheses. An opening parenthesis in expression position always begins the parameter list of a function literal, Essence’s arrow function. Wrapping a call in parentheses is foreign-syntax, and its fix removes them:

[foreign-syntax]
Error: Essence has no grouping parentheses
   ╭─┤ grouping.es:2:17 │
   │
 2 │     Terminal.print((1::add(2))::multiply(with 3))
   │                    ─────┬─────  
   │                         ╰─────── these parentheses group one value
   │ 
   │ Help: Remove the parentheses.
   │ 
   │ Note: A '(' in an Expression opens a Function literal's Parameter list. A value needs no grouping, since a Method chain reads left to right.
───╯

Without precedence, grouping would only decide what is computed first. To do that, nest the part inside an argument, or bind it to a constant and continue from the constant:

implementation {
	constant price   = 80
	constant taxRate = 1/5

	§ price * (1 + taxRate), with the inner part nested in the argument
	Terminal.print(price::multiply(with 1::add(taxRate))) § 96

	§ the same, with the inner part bound to a constant first
	constant taxFactor = 1::add(taxRate)

	Terminal.print(price::multiply(with taxFactor)) § 96
}

A nested call runs before the call it is an argument of, so 1::add(taxRate) is computed first in both versions. Pick the constant when the nesting gets hard to read.

Statics and the dot

. reaches a static, a member that belongs to a namespace rather than to one value. A namespace is a named group of methods and statics, as Math is in JavaScript: Number.Pi plays the part of Math.PI.

implementation {
	§ `Number.Pi` is a static: a member reached on a namespace's name with `.`
	Terminal.print(Number.Pi) § π

	§ a static that takes arguments is called the same way
	Terminal.print(Number.highest(3, 9)) § 9

	§ a static's result is a value like any other, so a chain continues with `::`
	Terminal.print(Number.Pi::isBetween(3, and 22/7)) § true
}

Terminal.print is a static too: Terminal is a namespace, and print is one of its statics. Number.Pi is a static that holds a value. It is the exact number, not a float, so it prints as π. Number.highest is a static that is called with arguments, positional or labelled like any method’s. How a namespace declares its methods and statics is on Namespaces and methods.

The same . reads a member of a record, Essence’s version of a plain JavaScript object, as order.total. JavaScript also calls a method with ., so price.multiply(with 3) is a common slip. It is method-called-with-dot, 'multiply' is a Method, not a member, because a method lives in a namespace rather than among the value’s members. Where the name is no method either, as in price.frobnicate(), it is type-without-members, This value has no members to look up, because an Integer has no members for . to read.

There are no operators

Essence has no infix or prefix operators: no +, -, *, /, %, no ==, !=, <, >, no &&, ||, !, and no ternary. Every operation is a method instead:

implementation {
	constant balance = 250

	Terminal.print(balance::add(50)) § 300
	Terminal.print(balance::is(250)) § true
	Terminal.print(balance::isGreaterThan(100)::and(balance::isLessThan(500))) § true

	§ `-` is the sign of a number literal, and nothing else
	Terminal.print(balance::negate()) § -250
	Terminal.print(-2::raise(to 2)) § 4
}

-2::raise(to 2) is 4, because the sign belongs to the literal -2 before raise applies. To flip the sign of a value, call negate.

Compared with JavaScript.

const total = (price + shipping) * quantity
const same = a === b
const ready = !done && (count > 0 || force)

In Essence the three lines are price::add(shipping)::multiply(with quantity), a::is(b) and done::negate()::and(count::isGreaterThan(0)::or(force)). One difference in meaning: && and || stop at the first operand that decides them, while and and or are calls and evaluate both sides. More on Coming from JavaScript › Operators.

Write constant total = 1 + 2 and the compiler names the operator you wrote:

[operator-not-supported]
Error: Essence has no '+' operator
   ╭─┤ operators.es:2:21 │
   │
 2 │     constant total = 1 + 2
   │                        ▲  
   │                        ╰── this is not a Method call
   │ 
   │ Help 1: Write 'a::add(b)' for Numbers.
   │ Help 2: Write 'a::append(b)' to join two Strings.
   │ 
   │ Note: Arithmetic, comparison and logic are Method calls in Essence, read left to right — there are no operators, and so no precedence to remember.
───╯

Every operator is answered the same way, by name, with operator-not-supported: *, %, &&, ==, <, || and / each get their own message, and the help writes the method for the one you typed. Spaces make no difference, so price+1, count++ and !true are each answered about the operator inside them. Neither does an argument list: Terminal.print(1 + 2) reports the same Essence has no '+' operator.

Apart from the arrow ->, - has one job: it is the sign of a number literal. -250 is a literal. In front of a name, Terminal.print(-balance) is Essence has no '-' operator, and the help names the operand: Write 'balance::negate()'.

Between two numbers it is refused as well. constant difference = 10 - 4 reports Essence has no '-' operator rather than binding 10 and leaving - 4 as a statement of its own. An operator is only found where the expression in front of it ended on the same line, so a statement that opens on the next line with -4 is still the literal -4.

Arithmetic

The arithmetic methods on Integer and Rational, the exact fraction type, are add, subtract, multiply(with:), divide(by:), quotient(dividingBy:), remainder(dividingBy:), raise(to:), negate() and absolute().

implementation {
	constant cents = 1999

	Terminal.print(cents::add(1)) § 2000
	Terminal.print(cents::subtract(2000)) § -1
	Terminal.print(cents::multiply(with 3)) § 5997
	Terminal.print(cents::divide(by 100)) § 1999/100
	Terminal.print(cents::quotient(dividingBy 100)) § 19
	Terminal.print(cents::remainder(dividingBy 100)) § 99
	Terminal.print(2::raise(to 10)) § 1024
	Terminal.print(cents::negate()) § -1999
	Terminal.print(-1999::absolute()) § 1999
}

Dividing one Integer by another never truncates. divide(by:) returns an exact Rational. quotient(dividingBy:) and remainder(dividingBy:) split a division into a whole-number quotient and what is left over. Both are Euclidean, so the remainder is never negative.

implementation {
	§ dividing two Integers returns a Rational, never a truncated Integer
	Terminal.print(10::divide(by 4)) § 5/2
	Terminal.print(10::divide(by 5)) § 2

	§ quotient and remainder are Euclidean: the remainder is never negative
	Terminal.print(-7::quotient(dividingBy 3)) § -3
	Terminal.print(-7::remainder(dividingBy 3)) § 2

	§ `toString` returns a String, which `inspect` shows in quotes
	Terminal.inspect(10::divide(by 4)::toString()) § "5/2"
	Terminal.inspect(1999::toString()) § "1999"
}

10::divide(by 5) prints 2, but it is still a Rational, one that happens to be whole. Every divisor on this page is a literal, which the compiler can see is not zero. A divisor it cannot check, such as a constant, might be zero, so divide(by:), quotient(dividingBy:) and remainder(dividingBy:) then return an Optional, a value that holds either a result or nothing.

toString() writes a Rational as a fraction in lowest terms, and a whole one bare. Decimal output takes a mode argument, which Numbers covers along with exactness and parsing.

Compared with JavaScript.

const share = 10 / 4 // 2.5, a float
const wrapped = -7 % 3 // -1

10::divide(by 4) is the exact 5/2, not a float. -7::remainder(dividingBy 3) is 2, because remainder is Euclidean where % keeps the sign of the dividend. More on Coming from JavaScript › Numbers.

Equality and ordering

is and isNot compare values structurally: two values are the same when they have the same content, wherever they came from.

implementation {
	constant stock = 12

	Terminal.print(stock::is(12)) § true
	Terminal.print(stock::isNot(0)) § true
	Terminal.print(1::is(1/1)) § true
	Terminal.print("open"::is("open")) § true
	Terminal.print([1, 2]::is([1, 2])) § true

	Terminal.print(stock::isLessThan(10)) § false
	Terminal.print(stock::isLessThanOrEqualTo(12)) § true
	Terminal.print(stock::isGreaterThan(10)) § true
	Terminal.print(stock::isGreaterThanOrEqualTo(20)) § false
	Terminal.print(stock::isBetween(12, and 20)) § true
	Terminal.print(stock::clamp(between 1, and 10)) § 10
}

1::is(1/1) is true: the Integer 1 and the Rational 1/1 are the same number. Two written lists with the same items are the same list, where JavaScript’s === compares two references and returns false. Comparing values of unrelated types does not compile, as in TypeScript: 1::is("1") is refused.

Ordering is isLessThan, isLessThanOrEqualTo, isGreaterThan and isGreaterThanOrEqualTo. isBetween(_:and:) returns whether a value lies between two bounds, both included. clamp(between:and:) returns the value itself when it lies between them, and the nearer bound when it does not. JavaScript’s Math.max(a, b) is the static Number.highest(a, b) from the section above. The highest item of a whole list is highestItem, which Lists covers.

Boolean logic

The logic methods on Boolean are and, or, negate and exclusiveOr.

implementation {
	constant isOpen   = true
	constant hasStock = false

	Terminal.print(isOpen::and(hasStock)) § false
	Terminal.print(isOpen::or(hasStock)) § true
	Terminal.print(hasStock::negate()) § true
	Terminal.print(isOpen::exclusiveOr(hasStock)) § true

	§ `and` evaluates its argument even when the receiver is false
	Terminal.print(hasStock::and(Terminal.inspect(isOpen))) § true
	§ false
}

and and or are ordinary calls, and a call evaluates its argument before the method runs. So and evaluates its argument even when the receiver is false, unlike &&. The last line shows it: inspect prints true before and returns false. When the right side must run only if the left side allows it, write a define expression instead, Essence’s replacement for the ternary, which Conditions covers.

When a call fails

A method the receiver’s type does not have is unknown-method:

[unknown-method]
Error: No Method named 'plus' for this value
   ╭─┤ unknown.es:2:23 │
   │
 2 │     Terminal.print(1999::plus(1))
   │                    ──┬─  ──┬─  
   │                      ╰─────│─── this is a PositiveInteger
   │                            │   
   │                            ╰─── no Method of this name
   │ 
   │ Note: Searched Namespaces 'Integer', 'NonZeroInteger', 'NonNegativeInteger', 'PositiveInteger', 'Scalar', 'Number'.
───╯

The note lists where the compiler looked. Every method lives in a namespace, and a call searches every namespace declared for the receiver’s type. Scalar and Number are declared for several number types at once. The label calls 1999 a PositiveInteger, not an Integer. A written literal proves facts about itself, such as being positive, and the compiler searches the namespaces for those facts too, as Checked refinements explains.

A method that exists, called with arguments it does not take, is no-matching-overload. A missing label is a common cause:

[no-matching-overload]
Error: No overload of 'clamp' accepts these Arguments
   ╭─┤ overload.es:4:17 │
   │
 4 │     Terminal.print(temperature::clamp(0, and 100))
   │                                 ──┬── ▲  
   │                                   ╰───│── 'Integer::clamp' takes Parameter 'between' here
   │                                       │  
   │                                       ╰── this Argument carries no label
   │ 
   │ Help: Write 'between' before the value.
   │ 
   │ Note 1: 'Integer::clamp' (provided by Orderable) takes 2 Arguments: Parameter 'between' is Integer, Parameter 'and' is Integer.
   │ Note 2: 'Number::clamp' (provided by Orderable) takes 2 Arguments: Parameter 'between' is Number, Parameter 'and' is Number.
───╯

The report leads with the entry the call came closest to: the label says what the argument carries, the one beside the method name says what that entry wanted there, and the Help is the edit. The notes list every signature the method has, with the label and type of each parameter: temperature::clamp(between 0, and 100). “Provided by Orderable” means the method’s body comes from Orderable, a protocol: a set of methods a type promises to have. A protocol is like a TypeScript interface that can also carry method bodies. Protocols covers them. A misspelt label, a reordered one such as clamp(and 100, between 0), and a wrong argument type such as cents::add("1") are reported the same way.

esc