Apophasis
A Language of Negative Declaration
Apophasis: A Language of Negative Declaration
On the Design and Consequences of a Programming Language in Which Nothing Can Be Affirmed
Dr. Ioannis Stavridis, Department of Computational Theology, University of Thessaloniki
Proceedings of the 9th International Conference on Language Design and Philosophical Commitment (LDPC), Edinburgh, 2025
1. Introduction
Every mainstream programming language permits — requires, in fact — the programmer to say what a thing is. x = 5. let name: String. class Dog extends Animal. These are affirmations: positive declarations that a value, a variable, or a type occupies a specific place in the ontological structure of the programme. The programmer points at a region of computational space and says: this.
Apophasis does not permit this. Apophasis is a statically typed, compiled programming language in which the only well-formed type declaration is a negation. You cannot say what a value is. You can only say what it is not. The type of a variable is the intersection of everything it has been declared not to be, and the compiler infers what remains. If what remains is a single type, the programme is well-formed. If what remains is ambiguous — if the negations are insufficient to determine a unique type — the compiler rejects the programme with an error the community has come to call “the cloud of unknowing,” a phrase borrowed from the fourteenth-century English mystic and adopted without attribution.
The language was designed in 2020 by Dr. Ephraim Daskalakis, a computer scientist at the National Technical University of Athens who had previously worked on dependent type systems and who describes his intellectual trajectory as “a long retreat from the idea that types should say things.” The name is drawn from the tradition of apophatic theology — the via negativa, the approach to the divine that proceeds entirely through negation, on the principle that God is so far beyond human categories that the only honest statements are negative ones: God is not finite, not temporal, not material, not comprehensible. Daskalakis’s contribution was to notice that this epistemological constraint could be implemented as a type system, and that the result would be, in a precise technical sense, sound.
Whether a sound type system built on negation alone is useful is a question the language has spent five years not quite answering.
2. Origins
Daskalakis’s foundational paper, “Toward Negative Typing: A Type System Without Positive Assertions” (2019, presented at POPL, accepted after what two reviewers described as “the most unusual review process of my career”), began with an observation about the relationship between type declarations and knowledge claims. A type annotation, Daskalakis argued, is a proposition: x: Int asserts that x is an integer. This assertion is a claim of positive knowledge — the programmer knows what x is and tells the compiler. The compiler verifies. Both parties agree. The programme proceeds on the basis of shared certainty.
But positive knowledge is, Daskalakis noted, a strong epistemological commitment. In many real-world programmes, the programmer does not know exactly what a value is. They know what it is not — it is not null, not a string, not an error — and they proceed on the basis of these exclusions, narrowing the possibility space until what remains is workable. Existing type systems accommodate this partially through constructs like discriminated unions, type guards, and assertion functions, but always within a framework that expects, eventually, a positive declaration. The negative knowledge is instrumental. It serves the positive.
Daskalakis proposed to invert this. What if negative knowledge were foundational? What if the type system were built entirely on negation, with positive types emerging — if they emerged at all — as the residue left behind when enough things had been denied?
The paper included a proof that a type system based on finite intersections of negated types is sound and decidable, provided the universe of types is finite — a constraint that Daskalakis described as “reasonable for practical languages and theologically appropriate.” The paper did not include an implementation. It included instead a section titled “Implications for the Relationship Between Language and Reality” that the POPL reviewers asked to be removed and that Daskalakis published separately in a journal of philosophy of mathematics, where it was received with interest and no citations.
The language itself followed in 2020, implemented by Daskalakis and two students over a period of eight months, during which, by Daskalakis’s account, “we spent approximately one month on the compiler and seven months on the question of what we had built.”
3. The Language
3.1 Negative Declarations
The syntax of type declaration in Apophasis uses the operator ¬ (logical not), which is the only type operator the language provides. A variable is declared by stating what it is not:
let x: ¬String, ¬Bool, ¬List, ¬Void
This declares that x is not a string, not a boolean, not a list, and not void. Given the language’s default type universe — which includes Int, Float, String, Bool, List, Record, Function, and Void — the compiler infers that x must be one of Int, Float, Record, or Function. The programme is not yet well-formed, because the type is ambiguous. The programmer must add further negations:
let x: ¬String, ¬Bool, ¬List, ¬Void, ¬Float, ¬Record, ¬Function
Now the compiler infers that x is an Int. The programme proceeds. The programmer has arrived at Int by denying everything that Int is not, a process that is logically identical to declaring x: Int but that requires, in the default type universe, seven negations instead of one affirmation. Daskalakis considers this a feature. “Affirmation is cheap,” he has said. “Negation is work. The work is the point.”
3.2 The Negation Ratio
A consequence of the design is that the ratio of negations to inferred types in a well-formed Apophasis programme is always at least n − 1, where n is the size of the type universe. In the default universe of eight types, every fully determined variable requires at least seven negations. In programmes that use custom types — which are themselves defined by negation, as described below — the universe grows, and the negation ratio grows with it.
The largest Apophasis programme analysed in this paper, a web server written by a team in Heraklion, contains 14,200 type negations across 380 inferred positive types, a negation ratio of 37.4. The programme’s source code is, by character count, approximately ninety-two percent negation. The remaining eight percent is logic, I/O, and the operator ¬ itself, which accounts for roughly four percent on its own.
The community does not consider this a problem. The community considers it a discovery about the nature of knowledge: that saying what a thing is requires, as a prerequisite, a comprehensive account of what it is not, and that this account is necessarily longer, more laborious, and more informative than the positive claim it enables. “The negation is not overhead,” Daskalakis wrote in the language’s design document. “The negation is the content. The positive type is merely what is left over.”
3.3 Custom Types
Custom types in Apophasis are defined, naturally, by negation. The programmer specifies a new type by declaring what it is not relative to existing types:
type Currency: ¬String, ¬Bool, ¬List, ¬Void, ¬Function, ¬Record
This states that Currency is none of the listed types. But this leaves Currency in the same residual category as Int and Float, which means the compiler cannot distinguish them. To resolve this, the programmer must also negate Int and Float:
type Currency: ¬String, ¬Bool, ¬List, ¬Void, ¬Function, ¬Record, ¬Int, ¬Float
Now Currency is distinct: it is the only type that is none of the above. But defining Currency has expanded the type universe from eight types to nine, which means that every other variable declaration in the programme now requires one additional negation to remain fully determined. A programme that introduces ten custom types requires every variable to carry at least seventeen negations. One that introduces fifty requires fifty-seven.
The Heraklion web server defines 112 custom types. Its type universe contains 120 members. Every fully resolved variable requires 119 negations. The team reports that their IDE plugin, which auto-completes negation chains, is the most critical tool in their development process, and that without it the language would be, in their assessment, “technically usable but humanely unbearable.” They continue to use it.
3.4 Functions
Functions in Apophasis present a particular challenge, because a function’s type includes both its parameter types and its return type, all of which must be negatively declared. A function that takes an integer and returns a string must be declared by negating all types that its parameter is not and all types that its return value is not, separately:
fn convert(x: ¬String, ¬Bool, ¬List, ¬Void, ¬Float, ¬Record, ¬Function)
-> ¬Int, ¬Bool, ¬List, ¬Void, ¬Float, ¬Record, ¬Function {
...
}
The function signature is longer than the function body in every Apophasis programme the author has examined. In the Heraklion web server, the average function signature is forty-three lines. The average function body is seven lines. The team has adopted a convention of placing signatures in separate files, a practice they call “the apophatic header,” which is technically a workaround for a readability problem but which the community has embraced as a philosophical principle — the declaration of what a function is not, they argue, is a different kind of knowledge from the specification of what the function does, and the two should not share a file any more than a theological treatise should share a binding with a cookbook.
4. The Double Negation Problem
The most consequential debate in the Apophasis community concerns double negation. If a value is declared ¬¬Int — not not an integer — is it an integer?
In classical logic, the answer is yes. The law of double negation elimination holds that ¬¬P ≡ P. In intuitionistic logic, the answer is no — double negation elimination is rejected, and ¬¬P is a weaker statement than P, asserting only that the negation of P leads to a contradiction, not that P itself is established.
Daskalakis, whose theological influences lean toward the apophatic tradition of Pseudo-Dionysius, chose the intuitionistic interpretation. In Apophasis, ¬¬Int is not Int. It is a distinct type — the type of things whose non-integer-ness has been denied, which is not the same as the type of things whose integer-ness has been affirmed. The distinction is, in one sense, the entire language.
The practical consequence is that a value of type ¬¬Int cannot be passed to a function expecting Int. They are different types. To convert between them, the programmer must supply a proof of affirmation — a construct introduced in version 0.3 that the specification calls “the cataphatic bridge” and that permits, under explicit and carefully delimited circumstances, the assertion that denying the negation of a thing is equivalent to affirming it.
let x: ¬¬Int = deny(¬Int, contradiction_proof)
affirm x as Int {
-- x is now Int within this block
-- but only within this block
-- and the programmer has taken moral responsibility for the affirmation
}
The affirm block is the only place in Apophasis where a positive type appears in source code, and its use is logged by the compiler in a file the community calls the “confession.” The confession file lists every affirmation in the programme, the line number at which it occurs, and the proof that was supplied. Large programmes have long confessions. The Heraklion web server’s confession file is 2,400 lines. The team reviews it weekly.
4.1 The Cataphatic Faction
Not everyone accepts this. A group of users centred at Imperial College London, led by Dr. Anya Petrov, has argued since 2022 that the intuitionistic interpretation is theoretically pure but practically ruinous. The Cataphatic Faction, as they are known, maintains that double negation should be eliminable — that ¬¬Int should simply be Int — on the grounds that a programming language is not a proof assistant and that requiring programmers to construct explicit proofs of affirmation every time they want to use a value whose type they have already determined (negatively, at great length) is not epistemological rigour but “computational penance.”
Petrov’s fork of the compiler, apophasis-classic, implements double negation elimination. Programmes compiled with apophasis-classic are between forty and sixty percent shorter, because the affirm blocks and their associated proofs can be omitted. The fork has approximately half the community’s users and all of its production deployments. It is not invited to the annual workshop.
Daskalakis’s response has been consistent. Double negation elimination is “the cataphatic temptation” — the seduction of positive knowledge, the desire to arrive at affirmation without earning it. “You have said what x is not, and what x is not is not. You have not said what x is. The gap between these is the gap between theology and bookkeeping.”
The community’s annual workshop devotes its first session, by tradition, to a re-examination of the double negation question. The session has never reached consensus. It is scheduled for ninety minutes and has never finished in less than four hours. The organisers now schedule nothing after it.
5. The Universe Problem
A subtler problem arises from the relationship between the type universe and the expressiveness of negation. In classical logic, negation relative to a finite universe is equivalent to affirmation of the complement. If the universe contains A, B, and C, then ¬A is equivalent to “B or C.” The negation carries information only because the universe is known and fixed.
Apophasis’s type universe is not fixed. It grows as the programmer defines new types. And each new type, as described above, expands the universe, requiring every existing declaration to be extended with an additional negation. This means that a programme’s meaning can change when a type is added — not because the logic has changed, but because the negations, which were previously sufficient to determine a unique type, are no longer sufficient in the expanded universe.
The community calls this “apophatic drift” — the tendency of a well-formed programme to become ambiguous as the type universe grows, requiring additional negations to restore determinacy. In practice, adding a single custom type to a large programme can introduce hundreds of ambiguity errors, each of which must be resolved by appending a negation of the new type to every declaration that was previously fully determined.
A tool called negate-all automates this process. It identifies every declaration that has become ambiguous after a type addition and appends the necessary negation. The tool is widely used and widely resented. “Every time someone adds a type,” a contributor in the Heraklion team wrote in a forum post, “we run negate-all and the programme grows by six hundred lines. The programme has never gotten shorter. I am not sure it can.”
Daskalakis considers apophatic drift to be correct behaviour. The addition of a new concept to the universe genuinely changes what existing negations mean, and the programme should be updated to account for this. “The universe has expanded,” he wrote in the design document’s appendix. “Your previous state of ignorance is no longer the same ignorance. You must deny the new possibility explicitly, or accept that your knowledge has become less definite. This is not a limitation of the type system. It is a property of epistemology.”
6. The Silence
Version 0.5, released in early 2024, introduced a feature that Daskalakis considers the language’s most important contribution and that the Cataphatic Faction considers evidence of psychological escalation: the silence type.
silence is a type that cannot be negated. It is not ¬anything. It is not the absence of type, which would be Void. It is not the negation of all types, which would be a contradiction. It is, in the specification’s words, “the type that remains when the activity of negation itself ceases.” A value of type silence cannot be operated on, passed to a function, returned, printed, or compared. It can only be bound and held.
let s: silence = quiet()
-- s exists. s has a type. The type is silence.
-- Nothing further can be said about s.
-- This is the programme's final statement about s.
The quiet() function is the only constructor for silence, and it takes no arguments, because any argument would introduce a positive relationship between the silence and the thing that produced it. Calling quiet() is an act of linguistic cessation: the programme stops talking about this value. The value persists — it occupies memory, it has a lifetime, the garbage collector knows about it — but the type system has nothing more to say about it. It has been, in the language’s terminology, “released from predication.”
The practical use of silence is, by design, none. It cannot participate in computation. It cannot carry data. It exists in the programme the way a rest exists in a musical score — a structured absence that shapes the programme’s form without contributing to its output.
Daskalakis has argued that silence completes the language. “Every apophatic tradition ends in silence. You negate, and negate, and negate, and eventually the negations exhaust the universe of things that can be said, and what is left is not a final positive claim but the cessation of claims. The language needed a type for this.” Petrov’s response was that “a type that does nothing is not a type but a mood,” which is, by the standards of type theory discourse, a devastating criticism, and which the Apophatic community has adopted as a compliment.
7. Current Status
Apophasis has, as of early 2026, approximately 1,200 active users, two incompatible compilers (Daskalakis’s apophasis-pure and Petrov’s apophasis-classic), and a package registry that lists 89 libraries, of which 23 are tools for managing negation chains and 14 are experimental implementations of silence.
The language has not been adopted in production by any major company, though a team at a Greek fintech startup used it for three months before switching to Rust, an experience they documented in a blog post titled “What We Learned from Apophasis,” whose most cited passage reads: “We learned that negative knowledge is real knowledge, that the type system is sound, that the compiler is correct, and that writing 119 negations to declare a variable as an integer is the kind of experience that changes a person, though not necessarily in the direction of increased productivity.”
The community’s most active area of research is what Daskalakis calls “deep negation” — the exploration of types defined by chains of negation longer than two. If ¬¬Int is not Int, what is ¬¬¬Int? The specification does not say. Daskalakis has suggested that triply negated types represent “the negation of the negation of the negation — a denial that the denial was denied — which is not a return to the original denial but a new epistemic position entirely.” A doctoral student in Thessaloniki is writing a thesis on the semantics of arbitrarily deep negation chains. The thesis is in its fourth year. Its current length is 340 pages, of which approximately 280 are devoted to the question of what ¬¬¬¬Bool means.
The Cataphatic Faction has proposed a version of the language in which all negation chains of depth greater than two are eliminated. The Apophatic Purists have proposed a version in which affirm blocks are removed entirely, leaving the language without any mechanism for bridging from negative to positive knowledge. Neither proposal has been adopted. The specification committee meets quarterly, and the meetings are characterised by what one participant described as “a very high ratio of silence to resolution,” a description that Daskalakis received with visible satisfaction.
Acknowledgments. The author thanks Dr. Daskalakis for extensive interviews conducted over the course of six meetings, during which Dr. Daskalakis answered most questions by explaining what the answer was not. Dr. Petrov was more forthcoming and more frustrated, often simultaneously. The Heraklion web server team provided access to their codebase and their confession file, both of which were illuminating in different ways. The author notes, in the interest of disclosure, that he has used Apophasis to write exactly one programme — a hello-world that required fourteen negations — and that the experience, while instructive, did not produce a desire to write a second.