Operators in Eiffel
Learn arithmetic, comparison, logical, and string operators in Eiffel - and discover how operators are really feature calls on objects
Operators are the building blocks of expressions. In Eiffel, they look familiar at first glance - +, -, *, / work as you would expect - but there is something fundamentally different going on underneath. Eiffel is a pure object-oriented language, so every operator is actually a feature (method) call on an object. The expression a + b is just syntactic sugar for a.plus (b).
This unification gives Eiffel a clean, consistent semantics. You can read every binary operator as “send this message to the left operand, with the right operand as the argument.” And because operators are just features, classes you define yourself can introduce new infix and prefix operators through the alias keyword.
In this tutorial, you will explore Eiffel’s arithmetic, comparison, logical, and string operators, and see how operator precedence and the boolean short-circuit operators (and then, or else) work.
Categories of Operators
Eiffel groups operators roughly as follows:
- Arithmetic:
+,-,*,/,//(integer division),\\(integer remainder),^(power) - Relational:
=,/=,<,>,<=,>= - Boolean:
not,and,or,xor,and then,or else,implies - String:
+for concatenation (a feature ofSTRING) - Assignment:
:=(a statement, not an operator that yields a value)
Note that = is comparison, not assignment. Eiffel uses := for assignment - a deliberate choice that prevents the classic C-family bug of writing = when you meant ==.
A Complete Operators Demo
Create a file named operators.e:
note
description: "Demonstration of Eiffel operators"
class
OPERATORS
create
make
feature -- Initialization
make
-- Run the operator demonstrations.
local
a, b: INTEGER
x, y: REAL_64
s1, s2: STRING
flag_a, flag_b: BOOLEAN
do
-- Arithmetic operators on INTEGER
a := 17
b := 5
print ("Arithmetic with a=17, b=5%N")
print (" a + b = " + (a + b).out + "%N")
print (" a - b = " + (a - b).out + "%N")
print (" a * b = " + (a * b).out + "%N")
print (" a // b = " + (a // b).out + "%N")
print (" a \\ b = " + (a \\ b).out + "%N")
print (" a ^ 2 = " + (a ^ 2).out + "%N")
-- Real division (use REAL_64 for fractional results)
x := 10.0
y := 4.0
print ("%NReal division: 10.0 / 4.0 = " + (x / y).out + "%N")
-- Relational operators return BOOLEAN
print ("%NComparisons with a=17, b=5%N")
print (" a = b : " + (a = b).out + "%N")
print (" a /= b : " + (a /= b).out + "%N")
print (" a > b : " + (a > b).out + "%N")
print (" a <= b : " + (a <= b).out + "%N")
-- Boolean operators
flag_a := True
flag_b := False
print ("%NBooleans with flag_a=True, flag_b=False%N")
print (" not flag_a : " + (not flag_a).out + "%N")
print (" flag_a and flag_b : " + (flag_a and flag_b).out + "%N")
print (" flag_a or flag_b : " + (flag_a or flag_b).out + "%N")
print (" flag_a xor flag_b : " + (flag_a xor flag_b).out + "%N")
print (" flag_a implies flag_b : " + (flag_a implies flag_b).out + "%N")
-- Short-circuit boolean operators
print ("%NShort-circuit: True or else (1 // 0 = 0) = ")
print ((True or else (1 // 1 = 0)).out + "%N")
-- String concatenation
s1 := "Design"
s2 := "Contract"
print ("%NString concat: " + s1 + " by " + s2 + "%N")
-- Operator precedence: * binds tighter than +
print ("%N2 + 3 * 4 = " + (2 + 3 * 4).out + "%N")
print ("(2 + 3) * 4 = " + ((2 + 3) * 4).out + "%N")
end
end
What’s Happening
A few things worth highlighting:
(a + b).out-.outis a feature onANYthat returns a printableSTRING. We call it on the result of each expression so we can concatenate it into our output.//and\\- Integer division and integer remainder. Note that\\is two backslashes; Eiffel does not use backslash as a string escape (it uses%), so the literal"a \\ b"prints asa \\ b.%N- Eiffel’s newline escape inside a string literal.and then/or else- The short-circuit forms.True or else <anything>never evaluates the right operand, which is useful for guarding against errors like division by zero.implies- A first-class logical implication operator.a implies bis equivalent to(not a) or b, and is widely used in contracts (require,ensure).
Operators Are Feature Calls
You can write any infix operator as a normal feature call. These two lines do the same thing:
result := a + b
result := a.plus (b)
This is why classes you write can introduce their own operators using the alias keyword, for example plus alias "+". It is the same mechanism that the built-in types use.
The Configuration File
Eiffel needs an ECF file describing the project. Create a file named operators.ecf:
| |
The root element declares OPERATORS as the entry-point class and make as its creation procedure.
Running with Docker
| |
The ec compiler translates the Eiffel source into C, invokes the C toolchain, and drops the native executable under EIFGENs/operators/W_code/.
Expected Output
Arithmetic with a=17, b=5
a + b = 22
a - b = 12
a * b = 85
a // b = 3
a \\ b = 2
a ^ 2 = 289
Real division: 10.0 / 4.0 = 2.5
Comparisons with a=17, b=5
a = b : False
a /= b : True
a > b : True
a <= b : False
Booleans with flag_a=True, flag_b=False
not flag_a : False
flag_a and flag_b : False
flag_a or flag_b : True
flag_a xor flag_b : True
flag_a implies flag_b : False
Short-circuit: True or else (1 // 0 = 0) = True
String concat: Design by Contract
2 + 3 * 4 = 14
(2 + 3) * 4 = 20
Operator Precedence (High to Low)
Eiffel’s precedence table is conventional, with a few language-specific entries:
| Level | Operators | Notes |
|---|---|---|
| 1 | . (feature call), unary +/- | Tightest binding |
| 2 | ^ | Power |
| 3 | *, /, //, \\ | Multiplicative |
| 4 | binary +, - | Additive |
| 5 | =, /=, <, >, <=, >= | Relational |
| 6 | not | Unary boolean |
| 7 | and, and then | Boolean conjunction |
| 8 | or, or else, xor | Boolean disjunction |
| 9 | implies | Implication, lowest binding |
When in doubt, parenthesize. Eiffel programmers tend to err on the side of explicit grouping because contracts often combine many boolean expressions.
Why Short-Circuit Matters in Contracts
Eiffel’s and then and or else are essential when writing preconditions that guard against invalid operands:
divide (n, d: INTEGER): INTEGER
require
d_not_zero: d /= 0
do
Result := n // d
end
If you wrote a guarded expression like d /= 0 and n // d > 0, the plain and would evaluate both sides regardless. Using d /= 0 and then n // d > 0 ensures the division is only attempted when d is non-zero - a small but vital safety property.
Key Concepts
- Every operator is a feature call on the left operand;
a + bis exactlya.plus (b). :=is assignment,=is comparison - the two are syntactically distinct, eliminating a whole class of typos.//is integer division,\\is integer remainder,^is power - the only arithmetic operators that may surprise newcomers.and thenandor elseare short-circuit; plainandandorare not. Choose the short-circuit forms when the right operand depends on the truth of the left.impliesis a built-in logical implication operator, often seen inrequireandensureclauses.%is the escape character inside string literals (%Nfor newline,%Tfor tab,%%for percent) - backslashes are just literal characters.- Use
.outto convert any value to aSTRINGfor printing or concatenation. - User-defined classes can introduce operators via the
aliaskeyword on feature declarations.
Running Today
All examples can be run using Docker:
docker pull eiffel/eiffel:latest
Comments
Loading comments...
Leave a Comment