Beginner

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 of STRING)
  • 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 - .out is a feature on ANY that returns a printable STRING. 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 as a \\ 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 b is 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:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-22-0" name="operators" uuid="00000000-0000-0000-0000-000000000002">
    <target name="operators">
        <root class="OPERATORS" feature="make"/>
        <setting name="console_application" value="true"/>
        <library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
        <cluster name="root_cluster" location="."/>
    </target>
</system>

The root element declares OPERATORS as the entry-point class and make as its creation procedure.

Running with Docker

1
2
3
4
5
6
# Pull the official EiffelStudio image
docker pull eiffel/eiffel:latest

# Compile and run the operators example
docker run --rm -v $(pwd):/app -w /app eiffel/eiffel:latest \
    sh -c 'ec -batch -config operators.ecf -c_compile && ./EIFGENs/operators/W_code/operators'

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:

LevelOperatorsNotes
1. (feature call), unary +/-Tightest binding
2^Power
3*, /, //, \\Multiplicative
4binary +, -Additive
5=, /=, <, >, <=, >=Relational
6notUnary boolean
7and, and thenBoolean conjunction
8or, or else, xorBoolean disjunction
9impliesImplication, 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 + b is exactly a.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 then and or else are short-circuit; plain and and or are not. Choose the short-circuit forms when the right operand depends on the truth of the left.
  • implies is a built-in logical implication operator, often seen in require and ensure clauses.
  • % is the escape character inside string literals (%N for newline, %T for tab, %% for percent) - backslashes are just literal characters.
  • Use .out to convert any value to a STRING for printing or concatenation.
  • User-defined classes can introduce operators via the alias keyword on feature declarations.

Running Today

All examples can be run using Docker:

docker pull eiffel/eiffel:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining