Functions in Ada
Learn how to define and use subprograms in Ada - functions and procedures, parameter modes, default and named parameters, recursion, and overloading with Docker-ready examples
In Ada, the units of reusable behavior are called subprograms, and they come in two distinct flavors: functions, which compute and return a value, and procedures, which perform actions but return nothing. This clean separation is a deliberate design choice. A function call is an expression; a procedure call is a statement. Keeping the two apart makes Ada code easier to read and reason about — when you see a function call you know it produces a value, and when you see a procedure call you know it does something.
Ada gives subprograms a level of expressiveness that many older languages lack. Parameters have explicit modes (in, out, in out) that document exactly how data flows in and out. Callers can supply arguments by name, making call sites self-documenting. Parameters can carry default values, and a single name can be overloaded across multiple parameter profiles. As a strongly, statically typed language, Ada checks every call against its declared profile at compile time, so mismatched arguments are caught long before the program runs.
In this tutorial you’ll define functions and procedures, control how arguments are passed using parameter modes, use default and named parameters, write recursive subprograms, and overload a subprogram name. Every example is a complete, runnable program you can compile with GNAT.
Functions and Procedures
The most fundamental distinction in Ada is between a function and a procedure. A function declares a return type and must return a value; a procedure does not. Both are declared in the declarative region of a program (between is and begin), and both can be called from the executable part.
Create a file named functions.adb:
| |
A few things to notice:
function Add (X, Y : Integer) return Integerdeclares the parameter profile and the return type. Parameters of the same type can share a declaration (X, Y : Integer).- The
end Add;clause repeats the subprogram name. As with the main procedure, this redundancy helps the compiler catch mismatched blocks. Integer'Imageconverts an integer to itsStringform. It prepends a space for non-negative values, which is why no extra space is needed after the=signs.- A function call like
Add (3, 4)appears wherever an expression is expected; a procedure call likeGreet ("Ada")stands alone as a statement.
Parameter Modes and Default Parameters
Ada parameters declare a mode that states how the data moves. The default mode, in, makes the parameter read-only inside the subprogram. An out parameter is a way to send a result back through the argument list, and an in out parameter is both read and written. Ada also lets in parameters carry a default value, so callers may omit them.
Create a file named parameters.adb:
| |
The mode system is one of Ada’s safety features. The compiler enforces it: you cannot assign to an in parameter, and you cannot read an out parameter before writing it. Because the direction of data flow is part of the declaration, a reader understands a subprogram’s effect from its signature alone. Functions traditionally use only in parameters, which keeps them free of side effects on their arguments.
Recursion
A subprogram in Ada may call itself. Recursion is the natural way to express problems that break down into smaller versions of themselves, such as factorials or Fibonacci numbers. Ada’s numeric subtypes — Natural (integers >= 0) and Positive (integers >= 1) — let you constrain parameters and results, and the runtime will raise an exception if a value ever falls outside its range.
Create a file named recursion.adb:
| |
Using Natural for the input and Positive for the factorial result documents the contract directly in the types: a factorial is never zero or negative, and the input is never negative. If a calculation overflowed the range of Positive, Ada would raise Constraint_Error rather than silently producing a wrong answer — a small illustration of why Ada is favored in systems where incorrect results are unacceptable.
Named Parameters and Overloading
Ada lets callers associate arguments by name rather than by position, which makes a call self-documenting and order-independent. Ada also supports overloading: several subprograms can share a name as long as their parameter or return profiles differ. The compiler picks the right one based on the argument types — a form of compile-time dispatch.
Create a file named overloading.adb:
| |
The two Describe functions coexist because their parameter types differ; calling Describe (42) selects the Integer version and Describe (True) selects the Boolean version. In Build_Range, the High => 100, Low => 1 syntax binds each argument to its parameter explicitly, so the reversed order is harmless and the intent is obvious at the call site. Named association is especially valuable when a subprogram has several parameters of the same type, where positional arguments would be easy to transpose.
Running with Docker
The codearchaeology/ada:latest image bundles the GNAT compiler. Mount the current directory and run gnatmake to compile each program, then execute the resulting binary.
| |
Expected Output
Running functions:
3 + 4 = 7
5 squared = 25
Hello, Ada!
Running parameters:
5 squared = 25
2 to the 8th = 256
17 / 5 = 3 remainder 2
21 doubled = 42
Running recursion:
5! = 120
10! = 3628800
Fib(10) = 55
Running overloading:
integer 42
boolean TRUE
Range: 1 .. 100
Key Concepts
- Functions vs. procedures — Functions return a value and are used as expressions; procedures perform actions and are used as statements. Ada keeps these two roles strictly separate.
- Parameter modes —
in(read-only, the default),out(write-back), andin out(read and write) document exactly how data flows through a subprogram, and the compiler enforces them. - Default parameters —
inparameters can declare a default value, so callers may omit them, as withExp : Integer := 2inPower. - Named association — Arguments can be passed by name (
High => 100, Low => 1), making calls self-documenting and order-independent. - Overloading — Multiple subprograms can share a name when their parameter or return profiles differ; the compiler resolves the call by argument types.
- Recursion with constrained subtypes — Subprograms may call themselves, and subtypes like
NaturalandPositiveadd range contracts that the runtime checks automatically. - Repeated end names — Closing a subprogram with
end Name;mirrors the declaration and helps the compiler catch structural mistakes. - Compile-time checking — Every call is validated against the declared profile, so wrong argument counts or types are rejected before the program ever runs.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/ada:latest
Comments
Loading comments...
Leave a Comment