2026-03-31Β·9 min readΒ·sota.io team

Deploy Mercury to Europe β€” Logic-Functional Programming with Determinism Modes in 2026

Prolog is one of the most expressive languages ever designed. It can solve constraint satisfaction problems, perform natural language parsing, and express complex search over structured data with a conciseness no imperative language matches. And then it lets you call an undeclared predicate at runtime, silently fail where you expected success, and produce completely different output depending on evaluation order.

The standard critique of Prolog is not that logic programming is wrong β€” it is that Prolog does it without static guarantees.

Mercury is the answer to that critique.

Created at the University of Melbourne by Zoltan Somogyi, Mercury takes logic programming seriously as a foundation and adds a Haskell-grade type system, a mode analysis system that statically verifies data flow, and a determinism system that tells you β€” at compile time β€” whether a predicate produces exactly one solution, at most one solution, or may backtrack. The result is Prolog's expressiveness with Haskell's correctness guarantees, compiled to native code that matches C performance.

Mercury backends built on decades of European logic programming research run today on EU infrastructure. This guide shows how to deploy them with sota.io.

The European Logic Programming Heritage

Mercury did not emerge in isolation. It is the product of a research lineage that runs almost entirely through European universities.

Alain Colmerauer πŸ‡«πŸ‡· (UniversitΓ© Aix-Marseille) and Robert Kowalski πŸ‡¬πŸ‡§ (Imperial College London) created Prolog in the early 1970s. Colmerauer's group in Marseille developed the first Prolog implementation; Kowalski formalised the connection between logic programming and resolution theorem proving. Every logic programming language β€” Mercury included β€” builds on this Franco-British foundation.

David H.D. Warren πŸ‡¬πŸ‡§ (University of Edinburgh β†’ University of Manchester β†’ Bristol) designed the Warren Abstract Machine (WAM) in 1983 β€” the compilation model that transformed Prolog from an interpreted language into one with competitive performance. Mercury's compilation strategy descends directly from the WAM: Mercury compiles to C or native code via a WAM-inspired intermediate representation. Warren's Edinburgh and Manchester work is load-bearing infrastructure in every compiled logic programming language.

Jan Maluszynski πŸ‡ΈπŸ‡ͺ (LinkΓΆping University) developed the foundational theory of mode analysis for logic programming in the 1980s. Mercury's mode system β€” the machinery that statically verifies which arguments are inputs and which are outputs for each predicate call β€” is a direct application of Maluszynski's theoretical work, extended and made decidable. LinkΓΆping University, Sweden, is not a footnote to Mercury's mode system: it is the primary academic source.

Seif Haridi πŸ‡ΈπŸ‡ͺ (KTH Stockholm) and his group developed AND-parallel Prolog and constraint logic programming variants at the Swedish Institute of Computer Science (SICS). Mercury's determinism analysis β€” the distinction between det, semidet, nondet, and multi β€” shares theoretical roots with this Swedish work on controlled evaluation in logic programs.

Michael Hanus πŸ‡©πŸ‡ͺ (CAU Kiel) β€” creator of Curry, the functional logic programming language. Mercury and Curry represent two distinct approaches to the same core problem: how to unify functional and logic programming under static type discipline. Hanus's work at Kiel and his publications on narrowing strategies informed the Mercury team's understanding of the design space. (See the Curry post.)

Manuel Hermenegildo πŸ‡ͺπŸ‡Έ (Universidad PolitΓ©cnica de Madrid β†’ IMDEA) and the Ciao Prolog group developed assertion-based program analysis for Prolog β€” static verification of types, modes, and determinism integrated into the compilation pipeline. Hermenegildo's work at Madrid independently pursued the same goals as Mercury: turning Prolog's dynamic execution into something statically verifiable. The two efforts inform each other.

The TYPES European Network β€” Mercury, Agda, Idris, Curry, and Mercury form a loose constellation of dependently-typed and logically-grounded languages that have benefited from TYPES EU research collaboration. Edinburgh, Gothenburg, Nijmegen, Kiel, Copenhagen β€” the EU academic network that funded type-theoretic programming language research over two decades created the conditions for all of them.

Zoltan Somogyi built Mercury on this foundation, in Melbourne, and released the first version in 1995. The European logic programming community's research is the substrate on which Mercury runs.

How Mercury's Type and Mode System Works

Mercury has three interlocked static analysis systems that together give you correctness guarantees no other logic programming language offers.

The Type System

Mercury types are Hindley-Milner polymorphic, like Haskell or OCaml. Every term has a statically verified type. There are no untyped Prolog terms, no functor/3 introspection of unknown structure, no runtime type errors.

:- type user
    --->    user(
                id    :: int,
                email :: string,
                role  :: user_role
            ).

:- type user_role
    --->    admin
    ;       member
    ;       guest.

:- type result(T)
    --->    ok(T)
    ;       error(string).

Algebraic data types with named fields. Pattern matching is exhaustive β€” the compiler rejects non-exhaustive switch statements. The result(T) type is Mercury's idiomatic error handling: ok(Value) or error(Message), with no exceptions and no runtime surprises.

The Mode System

Every Mercury predicate declaration specifies modes β€” which arguments are inputs (in) and which are outputs (out) for each calling pattern:

:- pred find_user(int::in, user::out) is semidet.
:- pred find_user(string::in, user::out) is semidet.
:- pred insert_user(user::in, db::in, db::out) is det.

The mode annotation int::in means the integer argument must be ground (fully instantiated) when find_user is called. user::out means the user argument will be bound by the predicate if it succeeds.

The compiler verifies that every call site satisfies the mode requirements. If you call find_user(X, User) where X is unbound, the compiler rejects it. If you call find_user(42, User) where User is already bound, the compiler rejects it (use semidet checking instead). Data flow is statically verified.

The Determinism System

Mercury's determinism annotations specify exactly how many solutions a predicate produces:

DeterminismSolutionsFailure
detExactly 1Cannot fail
semidet0 or 1May fail
multi1 or moreCannot fail
nondet0 or moreMay fail
erroneous0Always throws
failure0Always fails
% det: exactly one solution, cannot fail
:- pred format_email(user::in, string::out) is det.
format_email(User, Email) :-
    Email = User ^ email.

% semidet: zero or one solution
:- pred find_admin(list(user)::in, user::out) is semidet.
find_admin([H | _], H) :-
    H ^ role = admin.
find_admin([_ | T], Admin) :-
    find_admin(T, Admin).

% nondet: multiple solutions via backtracking
:- pred member_users(list(user)::in, user::out) is nondet.
member_users([H | _], H) :-
    H ^ role = member.
member_users([_ | T], User) :-
    member_users(T, User).

The compiler verifies these annotations. If you declare a predicate det but the compiler can prove it might fail, it rejects the program. If you declare semidet but the predicate always succeeds, the compiler accepts it (conservative approximation). The annotations are checked contracts, not documentation.

Building a Backend Service with Mercury

Mercury's HTTP story uses the mercury_http library or raw socket programming. Here is a minimal service structure:

Project structure:

my-mercury-service/
β”œβ”€β”€ server.m            # Main entry point
β”œβ”€β”€ handlers.m          # Request handlers
β”œβ”€β”€ types.m             # Domain types
β”œβ”€β”€ Makefile
β”œβ”€β”€ Dockerfile
└── .gitignore

types.m:

:- module types.
:- interface.

:- import_module string, int.

:- type user_id == int.

:- type user
    --->    user(
                user_id   :: user_id,
                username  :: string,
                email     :: string
            ).

:- type api_response(T)
    --->    success(T)
    ;       not_found(string)
    ;       bad_request(string).

:- implementation.
% (no implementation needed for pure type definitions)

handlers.m:

:- module handlers.
:- interface.

:- import_module types, string, list, maybe.

% Find a user by ID β€” semidet (may not exist)
:- pred find_user_by_id(user_id::in, list(user)::in, user::out) is semidet.

% Validate an email β€” det (always returns a result)
:- pred validate_email(string::in, api_response(string)::out) is det.

:- implementation.

:- import_module char, require.

find_user_by_id(Id, [H | _], H) :-
    H ^ user_id = Id.
find_user_by_id(Id, [_ | T], User) :-
    find_user_by_id(Id, T, User).

validate_email(Email, Response) :-
    ( string.contains_char(Email, '@') ->
        Response = success(Email)
    ;
        Response = bad_request("Invalid email: missing @")
    ).

server.m:

:- module server.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.

:- implementation.

:- import_module int, string, list, maybe.
:- import_module handlers, types.

% Static user store (replace with DB in production)
:- func sample_users = list(user).
sample_users = [
    user(1, "alice", "alice@example.eu"),
    user(2, "bob",   "bob@example.eu")
].

main(!IO) :-
    io.write_string("Mercury service starting on :8080\n", !IO),
    accept_loop(8080, !IO).

:- pred accept_loop(int::in, io::di, io::uo) is det.
accept_loop(Port, !IO) :-
    % In production: use mercury_http or socket library
    % This illustrates the structure β€” replace with real HTTP server
    io.format("Listening on port %d\n", [i(Port)], !IO),
    handle_request(!IO),
    accept_loop(Port, !IO).

:- pred handle_request(io::di, io::uo) is det.
handle_request(!IO) :-
    % Read request path (simplified)
    io.read_line_as_string(Result, !IO),
    ( Result = ok(Line) ->
        dispatch(Line, !IO)
    ;
        io.write_string("Connection closed\n", !IO)
    ).

:- pred dispatch(string::in, io::di, io::uo) is det.
dispatch(Path, !IO) :-
    ( string.prefix(Path, "/users/") ->
        IdStr = string.suffix_from(Path, 7),
        ( string.to_int(IdStr, Id) ->
            ( find_user_by_id(Id, sample_users, User) ->
                io.format("200 OK: user %d = %s\n",
                    [i(User ^ user_id), s(User ^ username)], !IO)
            ;
                io.write_string("404 Not Found\n", !IO)
            )
        ;
            io.write_string("400 Bad Request: invalid user ID\n", !IO)
        )
    ;
        io.write_string("404 Not Found\n", !IO)
    ).

The !IO state threading β€” io::di, io::uo (destructive input, unique output) β€” is Mercury's mechanism for sequencing side effects purely. Every I/O operation transforms the IO token. The type system enforces that I/O operations happen in the correct order and cannot be accidentally reordered.

Dockerfile:

FROM debian:bookworm-slim AS builder

RUN apt-get update && apt-get install -y \
    mercury-recommended \
    make \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .

# Compile with optimisation
RUN mmc --make server -O2

FROM debian:bookworm-slim AS runtime

RUN apt-get update && apt-get install -y \
    libgcc-s1 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY --from=builder /app/server .

EXPOSE 8080
CMD ["./server"]

Deploy on sota.io:

# Install sota CLI
curl -fsSL https://sota.io/install.sh | sh

# Deploy
sota deploy --port 8080

# Environment variables
sota env set DATABASE_URL=postgresql://...

# Custom domain
sota domains add mercury.yourdomain.eu

Connecting to PostgreSQL from Mercury

Mercury's odbc library or the mercury_db bindings provide parameterised queries:

:- module db.
:- interface.

:- import_module io, maybe, types.

:- pred get_user(int::in, maybe(user)::out, io::di, io::uo) is det.
:- pred insert_user(user::in, io::di, io::uo) is det.

:- implementation.

:- import_module odbc, string, int.

get_user(UserId, MaybeUser, !IO) :-
    odbc.sql(
        "SELECT id, username, email FROM users WHERE id = ?",
        [odbc.int(UserId)],
        Result,
        !IO
    ),
    (
        Result = ok([Row | _]),
        Row = [odbc.int(Id), odbc.text(Username), odbc.text(Email)],
        MaybeUser = yes(user(Id, Username, Email))
    ;
        Result = ok([]),
        MaybeUser = no
    ;
        Result = error(Msg),
        io.format("DB error: %s\n", [s(Msg)], !IO),
        MaybeUser = no
    ).

insert_user(User, !IO) :-
    odbc.sql(
        "INSERT INTO users (username, email) VALUES (?, ?)",
        [odbc.text(User ^ username), odbc.text(User ^ email)],
        _Result,
        !IO
    ).

The !IO threading ensures database operations happen in declaration order. The determinism annotation is det on get_user promises it always produces exactly one output β€” the maybe(user) handles the not-found case without throwing exceptions or using Prolog-style failure.

Why EU Teams Use Mercury

Prolog expressiveness with static correctness. Mercury gives you logic programming β€” recursive search, backtracking, constraint-style predicate definitions β€” inside a type system that rejects programs with mode errors, type errors, or determinism violations at compile time. For EU applications where correctness is non-negotiable (regulatory compliance tools, verification backends, AI explainability systems under the EU AI Act), Mercury's static guarantees are meaningful.

Determinism as documentation. When a Mercury predicate is annotated det, you know it cannot fail. When it is semidet, you know exactly when to handle the failure case. Compared to Prolog (where any predicate can fail or produce unexpected solutions) or even Haskell (where Maybe is used inconsistently), Mercury's determinism system is unusually precise. Reading a Mercury interface file tells you more about a predicate's behaviour than reading most function signatures in other languages.

Native performance. Mercury compiles to C, which is then compiled by GCC or Clang. Performance is typically within 2-5x of hand-written C β€” orders of magnitude faster than interpreted Prolog. For logic programming workloads (constraint solving, search, rule engines) that would be too slow in SWI-Prolog at production load, Mercury is the compilation path.

European logic programming lineage. Mercury is built on European research: Colmerauer's Prolog (Marseille), Warren's WAM (Edinburgh/Manchester), Maluszynski's mode analysis (LinkΓΆping), Haridi's parallel evaluation models (KTH Stockholm), Hermenegildo's assertion verification (Madrid). Deploying Mercury on EU infrastructure closes a circle β€” European research foundations, running in European data centres.

Pure semantics, no surprises. Mercury is referentially transparent except in explicitly marked I/O regions. Unlike Prolog (where assert/retract mutate the global database) or Erlang (where process messaging is transparent only by convention), Mercury's purity is enforced by the type system. det predicates with no io in their signature are pure functions β€” no hidden state, no global effects, no threading surprises.

Prolog knowledge base migration. EU enterprises with legacy Prolog rule engines (insurance, banking, logistics optimisation) often need a migration path that preserves the logic programming investment while adding correctness guarantees. Mercury can directly compile large subsets of Prolog after mode and type annotation β€” the predicate syntax is intentionally Prolog-compatible.

Practical Considerations

Ecosystem maturity. Mercury is a research-origin language with a small but committed community. The mercury-recommended Debian package covers the core compiler and standard library. Third-party library coverage is narrower than GHC Haskell or SWI-Prolog. For projects with unusual dependencies, budget time to write bindings.

Mode annotation overhead. Adding mode and determinism annotations to a large Prolog codebase takes time. The payoff is the compiler's feedback during this process β€” mode errors often surface real bugs in the original Prolog code that were silently masked by backtracking. Budget the annotation work as a debugging investment.

Compilation to C. Mercury's C backend means deployment is a standard binary or Docker image β€” no Mercury runtime required in production, just the compiled output and standard C library dependencies. This simplifies deployment considerably compared to languages requiring language-specific runtimes.

When Mercury is the right choice:

Deploy on EU Infrastructure

sota.io runs on servers in Germany (Frankfurt) and other EU data centres. All data stays within EU jurisdiction, GDPR compliance is structural, and every deployment is isolated by default.

Get started:

# Install sota CLI
curl -fsSL https://sota.io/install.sh | sh

# Log in
sota login

# Deploy from Dockerfile
sota deploy --port 8080

# Custom domain
sota domains add mercury.yourdomain.eu

Pure. Type-safe. Logic-powered. Hosted in Europe.

European Connections Summary

WhoInstitutionContribution
Alain Colmerauer πŸ‡«πŸ‡·UniversitΓ© Aix-MarseilleProlog creator β€” Mercury's logic programming foundation
Robert Kowalski πŸ‡¬πŸ‡§Imperial College LondonProlog formalisation, resolution semantics
David H.D. Warren πŸ‡¬πŸ‡§University of Edinburgh β†’ Manchester β†’ BristolWarren Abstract Machine β€” Mercury's compilation model
Jan Maluszynski πŸ‡ΈπŸ‡ͺLinkΓΆping UniversityMode analysis theory β€” direct foundation of Mercury's mode system
Seif Haridi πŸ‡ΈπŸ‡ͺKTH Stockholm / SICSAND-parallel Prolog, determinism models
Manuel Hermenegildo πŸ‡ͺπŸ‡ΈUPM Madrid β†’ IMDEAAssertion-based Prolog verification, Ciao Prolog
Michael Hanus πŸ‡©πŸ‡ͺCAU KielCurry functional-logic language β€” Mercury's design space sibling
Jan Wielemaker πŸ‡³πŸ‡±VU AmsterdamSWI-Prolog β€” the industrial Prolog Mercury users often migrate from
TYPES EU NetworkEdinburgh, Gothenburg, Nijmegen, Kiel...Pan-European type theory research funding the PL landscape Mercury inhabits
Zoltan Somogyi πŸ‡­πŸ‡ΊπŸ‡¦πŸ‡ΊUniversity of MelbourneMercury creator β€” synthesised this European research into a compiled language

Mercury is the answer to a question European researchers spent three decades formulating: can logic programming be made statically correct? Colmerauer and Kowalski asked it in Marseille and London. Warren made Prolog fast in Edinburgh. Maluszynski formalised modes in LinkΓΆping. Somogyi, in Melbourne, synthesised their answers into Mercury. The result deploys on sota.io in Frankfurt.


sota.io is EU-native infrastructure for backend services. Deploy your Mercury application to German servers in minutes β€” GDPR-compliant, managed PostgreSQL, custom domains included.