Note: information on this page refers to Ceylon 1.2, not to the current release.
Operator Polymorphism
Description
Primitive and polymorphic operators
Almost all of the operators in Ceylon can be expressed in terms of methods defined on classes and/or interfaces in the language module. Those operators which do not have such a definition are called primitive operators. The primitive operators are:
-
.
(member), -
=
(assignment), -
===
(identity), -
is
, -
of
, -
()
(positional invocation), -
{}
(named argument invocation)
Many non-primitive operators are polymorphic, which means that it is possible to specify the behaviour of operators in a type-specific way by satisfying the interface(s) used in the operator's definition.
However, not every non-primitive operator is polymorphic. Some are defined only in terms of the primitive operators, for example.
Simple Example
A simple example might be writing a complex number class which implements
Exponentiable
. This would allow us to write expressions using the
operators +
(unary plus), -
(unary minus), +
(sum), -
(difference),
*
(product), /
(quotient), and ^
(power). For example:
class Complex(shared Float re, shared Float im)
satisfies Exponentiable<Complex,Integer> {
positiveValue => this;
negativeValue => Complex(-re,-im);
plus(Complex other) => Complex(re+other.re, im+other.im);
minus(Complex other) => Complex(re-other.re, im-other.im);
times(Complex other) =>
Complex(re*other.re-im*other.im,
re*other.im+im*other.re);
shared actual Complex divided(Complex other) {
Float d = other.re^2 + other.im^2;
return Complex((re*other.re+im*other.im)/d,
(im*other.re-re*other.im)/d);
}
shared actual Complex power(Integer other) {
"exponent must be non-negative"
assert(other>=0);
//lame impl
variable Complex result = Complex(1.0, 0.0);
for (i in 0:other) {
result*=this;
}
return result;
}
string => im<0.0 then "``re``-``-im``i"
else "``re``+``im``i";
hash => re.hash + im.hash;
shared actual Boolean equals(Object that) {
if (is Complex that) {
return re==that.re && im==that.im;
}
else {
return false;
}
}
}
void compare(Complex z) {
value a = Complex(1.0, 2.0); // 1 + 2i
value b = Complex(4.0, 2.0); // 4 + 2i
value c = Complex(0.0, 1.0); // i
value d = a * b + c;
if (d == z) {
print("``d`` == ``z``");
}
else {
print("``d`` != ``z``");
}
}
compare(Complex(2.0, 5.0));
compare(Complex(0.0, 11.0));
Identities
There are certain identities which are true of numbers (and of the built-in
numeric types), which may not hold with arbitrary types. For example, given any
x
and y
of a type T
which implements Invertible
, the
following identity should be satisfied:
x - y ≡ x + (-y)
Unfortunately, Ceylon can't validate that the above identity holds for T
's
implementation of Invertible
.
Advice
The single most important thing to remember is that
if an identity does not apply for T
then you cannot use that identity to
rewrite expressions involving T
.
Philosophically, the operator symbols are just notation and Ceylon simply defines how those operators work in terms of the various interfaces. It's up to the author of a class to decide what those symbols ought to mean for that class, bearing in mind those symbols have a conventional meaning.
Our advice is that if a type has a notion of arithmetic (or whatever)
which closely conforms to numerical arithmetic, then by all means implement
Numeric
(or whatever). If you do, then document clearly any identities which
do not hold.
If on the other hand, the type doesn't have some notion of arithmetic, then don't confuse people by making operators do funky things: plain old method calls will be clearest.
The user of a class which implements the various operator interfaces is advised to be cautious about assuming the truth of identities which are not documented as being satisfied.
See also
- Operator polymorphism in the Tour of Ceylon