The algebra and calculus of algebraic data types
codewords.recurse.com
codewords.recurse.com
https://www.cs.ox.ac.uk/files/3228/PRG06.pdf
and here's a more recent free book from David Schmidt on the topic:
My area of research is concatenative programming, where concatenation of two programs denotes the composition of those programs, and the empty program is the identity function (on the “program state”, which is usually a stack). That means that the syntax and semantics of the language both form monoids, and there’s a homomorphism from the syntax onto the semantics.
Several years ago, I was trying to come up with languages with other more interesting algebraic structures, so you could apply theorems from those structures to programs. A particular challenge is a language whose structure forms a nontrivial ring¹, in particular a Euclidean ring—then you could find the greatest common divisor of two programs, and because a Euclidean ring is a unique factorisation domain, you could also factor a program into prime subprograms. I was fascinated by what that might mean and whether it could be useful.
Unfortunately, it was that “nontrivial” aspect that I could never get past. The language always ended up insufficiently powerful to express anything of interest, its algebraic structure was trivial (e.g. there’s only one program), or it ended up having a weaker structure (e.g. an idempotent semiring). Chris Pressey also worked on this concept a bunch around 2007, and produced some results like Cabra² and Burro³, but ran into similar dead ends like Potro⁴.
¹ https://en.wikipedia.org/wiki/Ring_(mathematics)
² http://catseye.tc/article/Languages.md#cabra
³ http://catseye.tc/article/Languages.md#burro
⁴ https://github.com/catseye/Chrysoberyl/blob/master/article/L...
A monoid can be seen as a one-object category (the monoid elements are the morphisms on that object, and the monoid operator is morphism composition), so perhaps concatenative languages have categorical semantics too. (Although it seems like the semantics of "quote", or whatever [ foo bar baz ] is in forth, might make things a little interesting - ie. require more structure than a monoid homomorphism. I expect you really want cartesian closed categories, and quote is probably curry or apply.)
Maybe you can offer some insight if I tell you that the quotation syntax “[ … ]” in a concatenative language corresponds to lambda abstraction:
e : a
---------------------
[ e ] : ∀s. s → s × a
And the quotation operator “quote”, which takes a value on the stack and returns it quoted, corresponds to eta-expansion (or lifting lambda abstraction into a term, if you like) and has the type: quote : ∀sa. s × a → s × (∀t. t → t × a)
Generally the standard Turing-complete basis in concatenative calculus is the following set of combinators: compose : ∀rstu. r × (s → t) × (t → u) → r × (s → u)
swap : ∀sab. s × a × b → s × b × a
drop : ∀sa. s × a → s
dup : ∀sa. s × a → s × a × a
quote : ∀sa. s × a → s × (∀t. t → t × a)
apply : ∀st. s × (s → t) → t
(Although you can get away with fewer if they’re equivalent in power.)The first three correspond to the standard B, C, K, and W combinators from combinatory logic, which give you all the substructural rules from logic—swapping is exchange, dropping is weakening, and copying is contraction. Without swap/drop/dup, it’s equivalent to ordered linear lambda calculus, which can be interpreted in any category (since it’s just B and I); what are the categorical equivalents of linear (BC), affine (BCK), ordered (BKW), and relevant (BCW) logics?
> Unfortunately, it was that “nontrivial” aspect that I could never get past. The language always ended up insufficiently powerful to express anything of interest, its algebraic structure was trivial (e.g. there’s only one program), or it ended up having a weaker structure (e.g. an idempotent semiring). Chris Pressey also worked on this concept a bunch around 2007, and produced some results like Cabra² and Burro³, but ran into similar dead ends like Potro⁴.
Maybe that in itself is an important result?
Well, I didn’t have the skills to actually prove the negative then, I was just never able to find a positive example. Still not convinced that it’s impossible, if I ever get back to it, or someone wants to pick it up as a problem to tinker with.
[1] http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.49....
Can you do anything else weird with ADTs?
What confuses me (besides what to make of the minus sign) is that the type of sets of x ought to be 2^x -- that is, x -> Bool -- because it's isomorphic to the characteristic function of a set, and a function type is an exponential (https://bartoszmilewski.com/2015/03/13/function-types/). But 2^x differs from exp(x) by a constant factor. So I seem to be messing with surface-level analogies without real understanding.
To define a subset of x, you simply choose whether any element is present or not:
type Set x = x → Bool
Cardinality: 2^x
Defining an unordered list (of unlimited length, with possible repetitions) with algebraic data types is harder, if at all possible. Especially when you consider what "unordered" should mean in the face of repetitions. I wouldn't be surprised if it was not possible, or if the expansion turned out to have a strange cardinality such as e^x
`a -> Void` get interpreted as "not a" or "from a follows contradiction" or equivalently "a is uninhabited". Combinatorially it's `0 ^ a` which for non-empty a is zero but is equal to 1 when a is empty (0^0=1). In other words there are no functions of type `a -> Void` for non-empty a and there's exactly one such function for uninhabited a (id :: Void -> Void).
`Void -> a` is interpreted "from falsehood, anything (follows)" https://en.wikipedia.org/wiki/Principle_of_explosion. Combinatorially a^0 = 1 for all a so there's exactly one such function. An easy way to define it is by induction on Void (which has no cases and you're done).
a^0 = 1
0^a = 0
0^0 = 1
The fact that there's precisely 1 function void -> a is considered rather special in category theory, and means that 'void' is the so called 'initial object' for the category (which is automatically unique up to isomorphism)."Void -> a" means that the function cannot be called, because there no way to conjure a Void value. In Haskell, you can do it by (ab)using 'undefined', but that's kind of cheating. However, if you're using Idris with totality checking, I don't think you'll actually get any code calling such a function compile.
The "a -> Void" means that the function can never return.
The “C” void is expressed by the unit singleton.
So it is |a|^1.
And assuming the theory is consistent(so no infinite loops etc.) this is the only instance of that function, so the cardinality is 1.
{-# LANGUAGE RankNTypes #-}
type Bottom = forall a. a -- zero inhabitants
f :: Bottom -> a
f x = x
That passes the type checker just fine.I think you are running into some weird haskellisms with your Bottom-type.
The normal way of defining that in haskell is using the "EmptyDataDecls" pragma, like this:
{-# LANGUAGE EmptyDataDecls #-}
data Empty
g :: Empty -> a
g x = x
Which doesn't pass the type checker.(From a theoretic standpoint, I would have thought your Bottom was a essentially a type-level identity function..)
'forall a. a' is just how you define the bottom type in System F, which Haskell is somewhat based on. As a type it describes that a term of that type can just conjure a value of any type out of thin air, which is obviously nonsense. That what makes it the bottom type.
The sub typing behavior associated with that is just the normal subsumption rule of polymorphic functions. The same reason why you can pass a function of type 'forall a. a -> a' to something expecting a function of type 'Int -> Int'. The types don't 'match' directly, but they do under the subsumption rule.
With "forall a. a" I was coming from the persepctive of intuisionistic type theory where the usual parametric polymorphism just get subsumed by Π-types and universal quantification is usually represented with Π-types, so you would have:
forall a.a (Universal Quantification)
<==> Π(a : Type) a (Π-type)
<==> (a : Type) -> a (Agda-notation)
==> a -> a (Π-type as regular function type)
where Type represents any type. That's what I meant by "type-level identity function".Another way to look at it is as a constrained subset of the Cartesian product of domain and codomain. In this case the domain is the empty set, so the Cartesian product is empty too.
Take this Haskell code that passes the type checker:
bottom = bottom
f :: Int -> a
f _ = bottom
Here 'bottom' has the conceptual type Void/Bottom and checks successfully against the 'a'.So assuming we have this Void type in Haskell (I don't know if Haskell has it or not), this should also type check:
f :: Void -> a
f x = x
Just like bottom (of type Void) checked against the 'a' earlier, an argument of that type should also type check. f :: Void -> a
f _ = let x = x in x data Unit = Unit
but the Haskell standard Prelude already has the empty tuple type (), with single element (), functioning as the standard unit type. distl :: (a, Either b c) -> Either (a, b) (a, c)
distl (a, b_c) = case b_c of
Left b -> Left (a, b)
Right c -> Right (a, c)
factl :: Either (a, b) (a, c) -> (a, Either b c)
factl ab_ac = case ab_ac of
Left (a, b) -> (a, Left b)
Right (a, c) -> (a, Right c)
With the “TypeOperators” feature enabled (and “UnicodeSyntax” for pretty arrows and such, because why not), you can write it more literally: type (×) a b = (a, b)
infixl 7 ×
type (+) a b = Either a b
infixl 6 +
distl ∷ a × (b + c) → a × b + a × c
factl ∷ a × b + a × c → a × (b + c)