Intermediate

Functions in Eiffel

Learn how Eiffel structures behavior with routines - queries and commands, parameters, recursion, contracts, and agents - using Docker-ready examples

In Eiffel, there are no free-standing functions - every piece of behavior lives inside a class as a feature. Features that do something are called routines, and Eiffel draws a sharp, language-level distinction between two kinds of routine:

  • A function (a query) computes and returns a value but must not change the object’s state.
  • A procedure (a command) changes state but returns nothing.

This is the Command-Query Separation principle, and it is one of the things that makes Eiffel code so predictable: asking a question never secretly modifies the answer. Throughout this tutorial we use “routine” for both, and “function” specifically for the value-returning kind.

Eiffel also lets you attach contracts to routines - require clauses (preconditions) state what a caller must guarantee, and ensure clauses (postconditions) state what the routine promises in return. Contracts are part of the routine’s signature, not an afterthought. In this tutorial you will learn how to define and call routines, pass parameters, use local variables, write recursive functions, and treat routines as first-class values through agents (Eiffel’s closures).

Anatomy of a Routine

A routine has a name, an optional parameter list, an optional return type (which makes it a function), optional contract clauses, and a body between do and end. The special predefined entity Result holds the value a function returns - you assign to it instead of using a return statement:

  • name (params): RETURN_TYPE - the signature; omit RETURN_TYPE to make it a procedure
  • require tag: condition - preconditions the caller must satisfy
  • local - declares local variables, scoped to the routine
  • do ... end - the executable body
  • ensure tag: condition - postconditions the routine guarantees
  • Result - the value returned by a function (defaults to the type’s zero value)

Queries, Commands, Parameters, and Recursion

The example below puts the core ideas together: value-returning functions (square, add), a recursive function with contracts (factorial), a function with a local variable and a loop (sum_to), a state-changing command (increment), and a command that takes a parameter (greet). Notice that factorial calls itself - recursion is completely natural in Eiffel.

Create a file named functions.e:

note
    description: "Demonstrates functions (queries) and procedures (commands) in Eiffel"

class
    FUNCTIONS

create
    make

feature -- Initialization

    make
            -- Run all demonstrations.
        do
                -- Functions (queries) that return values
            print ("square (5) = " + square (5).out + "%N")
            print ("add (3, 4) = " + add (3, 4).out + "%N")
            print ("factorial (5) = " + factorial (5).out + "%N")
            print ("sum_to (10) = " + sum_to (10).out + "%N")

                -- Procedures (commands) that change state
            print ("counter starts at " + counter.out + "%N")
            increment
            increment
            print ("counter after two increments = " + counter.out + "%N")

                -- A procedure with a parameter
            greet ("Eiffel")
        end

feature -- Queries (functions return a value, no side effects)

    square (n: INTEGER): INTEGER
            -- Square of `n'.
        do
            Result := n * n
        end

    add (a, b: INTEGER): INTEGER
            -- Sum of `a' and `b'.
        do
            Result := a + b
        end

    factorial (n: INTEGER): INTEGER
            -- Factorial of `n', computed recursively.
        require
            non_negative: n >= 0
        do
            if n <= 1 then
                Result := 1
            else
                Result := n * factorial (n - 1)
            end
        ensure
            at_least_one: Result >= 1
        end

    sum_to (n: INTEGER): INTEGER
            -- Sum of all integers from 1 to `n'.
        local
            i: INTEGER
        do
            from
                i := 1
            until
                i > n
            loop
                Result := Result + i
                i := i + 1
            end
        end

feature -- State

    counter: INTEGER
            -- Value maintained by `increment' (a query attribute).

feature -- Commands (procedures change state, return nothing)

    increment
            -- Increase `counter' by one.
        do
            counter := counter + 1
        ensure
            incremented: counter = old counter + 1
        end

    greet (name: STRING)
            -- Print a greeting addressed to `name'.
        require
            name_not_empty: not name.is_empty
        do
            print ("Hello, " + name + "!%N")
        end

end

A few things worth highlighting:

  • Result is automatically declared in every function and starts at the type’s default (0 for INTEGER), which is why sum_to can accumulate into it without an explicit initialization.
  • Parameters are read-only. Inside a routine you cannot reassign a, b, or n - they are constant for the duration of the call. To work with a changing value, declare a local variable like i.
  • old in increment’s postcondition refers to the value counter had before the routine ran, letting the contract express “counter went up by exactly one.”
  • require name_not_empty rejects bad input at the boundary instead of producing a confusing error later.

Routines as Values: Agents

Eiffel routines are first-class through agents. The agent keyword wraps a routine into an object you can store, pass around, and call later - Eiffel’s equivalent of closures or function references. A procedure agent has type PROCEDURE [TUPLE [...]]; a function agent has type FUNCTION [TUPLE [...], RESULT_TYPE]. You invoke them with call (for procedures) or item (for functions), passing arguments as a manifest tuple in square brackets.

Create a file named agents.e:

note
    description: "Demonstrates agents - Eiffel's first-class routines"

class
    AGENTS

create
    make

feature -- Initialization

    make
            -- Pass routines around as agents.
        do
                -- A procedure passed as an agent
            for_each_1_to (3, agent print_line)

                -- A function passed as an agent
            print ("apply (agent square, 6) = " + apply (agent square, 6).out + "%N")
        end

feature -- Higher-order routines

    for_each_1_to (n: INTEGER; action: PROCEDURE [TUPLE [INTEGER]])
            -- Call `action' once for each integer from 1 to `n'.
        do
            across 1 |..| n as i loop
                action.call ([i.item])
            end
        end

    apply (f: FUNCTION [TUPLE [INTEGER], INTEGER]; x: INTEGER): INTEGER
            -- Result of applying `f' to `x'.
        do
            Result := f.item ([x])
        end

feature -- Routines passed as agents

    print_line (k: INTEGER)
            -- Print `k' on its own line.
        do
            print ("Line " + k.out + "%N")
        end

    square (n: INTEGER): INTEGER
            -- Square of `n'.
        do
            Result := n * n
        end

end

Here for_each_1_to receives a procedure agent and calls it for every number in the interval 1 |..| n, while apply receives a function agent and returns its result. The caller simply writes agent print_line or agent square - Eiffel captures the routine (with its target object) into a callable value.

Running with Docker

Eiffel needs an ECF (Eiffel Configuration File) that names the root class and its entry feature, then compiles to a native executable before running.

Create a file named functions.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="functions" uuid="a1b2c3d4-0000-0000-0000-000000000005">
    <target name="functions">
        <root class="FUNCTIONS" 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>

Create a file named agents.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="agents" uuid="a1b2c3d4-0000-0000-0000-000000000006">
    <target name="agents">
        <root class="AGENTS" 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>

Now compile and run each example:

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

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

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

The compiler reads the ECF, translates your Eiffel classes to C, compiles them to a native binary in EIFGENs/<system>/W_code/, and you then run that binary directly.

Expected Output

Running the functions example produces:

square (5) = 25
add (3, 4) = 7
factorial (5) = 120
sum_to (10) = 55
counter starts at 0
counter after two increments = 2
Hello, Eiffel!

Running the agents example produces:

Line 1
Line 2
Line 3
apply (agent square, 6) = 36

Key Concepts

  • Routines live in classes. Eiffel has no global functions - every routine is a feature of some class, called on an object (here, implicitly on the current object).
  • Command-Query Separation. A function returns a value and leaves state untouched; a procedure changes state and returns nothing. Keeping the two apart makes code far easier to reason about.
  • Result, not return. A function returns whatever value Result holds when it finishes; Result is predeclared and starts at the type’s default value.
  • Parameters are immutable. You cannot reassign a routine’s arguments - introduce a local variable when you need something that changes.
  • Contracts are part of the signature. require validates inputs before the body runs; ensure (with old for the prior state) guarantees what the routine delivers, catching bugs at their source.
  • Recursion is first-class. A function may call itself freely, as factorial shows.
  • Agents make routines first-class values. Wrap any routine with agent, store it as a PROCEDURE or FUNCTION, and invoke it later with call or item - Eiffel’s answer to closures and higher-order functions.

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