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; omitRETURN_TYPEto make it a procedurerequire tag: condition- preconditions the caller must satisfylocal- declares local variables, scoped to the routinedo ... end- the executable bodyensure tag: condition- postconditions the routine guaranteesResult- 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:
Resultis automatically declared in every function and starts at the type’s default (0forINTEGER), which is whysum_tocan accumulate into it without an explicit initialization.- Parameters are read-only. Inside a routine you cannot reassign
a,b, orn- they are constant for the duration of the call. To work with a changing value, declare alocalvariable likei. oldinincrement’s postcondition refers to the valuecounterhad before the routine ran, letting the contract express “counter went up by exactly one.”require name_not_emptyrejects 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:
| |
Create a file named agents.ecf:
| |
Now compile and run each example:
| |
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, notreturn. A function returns whatever valueResultholds when it finishes;Resultis predeclared and starts at the type’s default value.- Parameters are immutable. You cannot reassign a routine’s arguments - introduce a
localvariable when you need something that changes. - Contracts are part of the signature.
requirevalidates inputs before the body runs;ensure(witholdfor the prior state) guarantees what the routine delivers, catching bugs at their source. - Recursion is first-class. A function may call itself freely, as
factorialshows. - Agents make routines first-class values. Wrap any routine with
agent, store it as aPROCEDUREorFUNCTION, and invoke it later withcalloritem- Eiffel’s answer to closures and higher-order functions.
Running Today
All examples can be run using Docker:
docker pull eiffel/eiffel:latest
Comments
Loading comments...
Leave a Comment