The algebra and calculus of algebraic data types (2015)
codewords.recurse.com
codewords.recurse.com
But, fascinatingly, integration does in fact have a meaning. First, recall from the OP that d/dX List(X) = List(X) * List(X). You punched a hole in a list and you got two lists: the list to the left of the hole and the list to the right of the hole.
Ok, so now define CrazyList(X) to be the anti-derivative of one list: d/dX CrazyList(X) = List(X). Then notice that punching a hole in a cyclic list does not cause it to fall apart into two lists, since the list to the left and to the right are the same list. CrazyList = CyclicList! Aka a ring buffer.
There's a paper on this, apologies I can't find it right now. Maybe Alternkirch or a student of his.
The true extent of this goes far beyond anything I imagined, this is really only the tip of a vast iceberg.
It had similarly 'crazy' what-ifs, including one where the author used the quadratic formula and interpreted some meaning from it.
It was a course only text, not distributed outside the university as far as I know.
> Nat = 1 + Nat. This equation is clearly inconsistent, since x=1+x is false for all possible values of x.
Ah, but no, my friend. That equation is false merely for all possible finite values of x. But why impose such a constraint? We've already been identifying a correspondence between certain types and the number of inhabitants they have. Nat has countably many inhabitants. And one more than countably infinite is countably infinite!
Two sets having the same cardinality (number of inhabitants) is insufficient to prove that two sets are equal. There is an additional condition that they all share the same elements.
I read 1 + Nat as: map each element in the Naturals to its successor (i.e. Nat - {0}). 0 is in Nat, but 0 does not exist in "1 + Nat" since it got mapped to 1.
This would be true if the set we were working with were the Integers instead of the Naturals.
Integers could be Int = Nat | Neg Nat. So Int = 2 * Nat. Your logic would presumably interpret this as even naturals.. but ya
My logic comes from a Math background. Take any Abstract Algebra class and this notation is common.
For example: https://math.stackexchange.com/questions/1378174/how-to-deno....
To say the least I take numerous issues with the notation used, but I accept it. Since the integers and the naturals have the same cardinality, we have that Int = Nat.
> countably infinite
Countably infinite means you can count to any particular element of a set in finite time, even though there is no end to the pattern of elements in the set.
You cannot count to infinity plus one in finite time.
[1] https://en.wikipedia.org/wiki/Transfinite_number
[2] https://en.wikipedia.org/wiki/Ordinal_number#Ordinals_extend...
The natural numbers are of course countably infinite, but so are even numbers, prime numbers, fractions, algebraic numbers and lots of other kinds of numbers that people want to do arithmetic with. "Uncountably infinite" sets can't be put into a one to one correspondence with the naturals, no matter how you produce to produce such a mapping, it's always possible to produce a number in the set which wasn't part of it. Real numbers being the prototypical example of that, but the power set (the set of all the subsets) of the naturals is, also.
People do arithmetic with all kinds of things that "aren't numbers". Math isn't restricted to working with numbers.
For example, uncountable sets can exhibit the property you mention: you count to x via the (very) finite sequence {x} for any element x of the set of real numbers.
Conversely, countable sets (even if you restrict such enumerations to a certain ordering) don’t in general have this property: ordering the integers by [x < y iff x < y (mod 2) or x < y if x = y (mod 2)] (that is, taking two isomorphic copies of the integers, identified with the even and odd integers respectively, juxtaposed side by side), the sequence {0,2,4,6, …} fails to arrive at 1 no matter how far you count along. So there’s an element we can’t count to in ‘finite time’.
The property you seem to be alluding to, that ‘the set admits a total order such that every element can be reached in finitely many steps starting at the first element’ actually corresponds to the order type of the natural numbers — often called ω. It’s countable, but there are many, many (in fact, uncountably many) more sets with this cardinality that don’t have that property.
The proper definition of ‘countably infinite’ is simply that the set can be placed in bijection with the natural numbers. This has nothing (directly) to do with ordering.
Does it not imply that there exists an ordering which reaches each value in finite time? That's how I'd straightforwardly read GP's comment. A given countable set under its 'natural' ordering might not have order type ω, but there will always exist some alternative ordering with type ω.
(And indeed, the existence of such an ordering implies that the set is countable.)
So it actually does work as a definition, if you phrase it properly.
I guess it's just equivalent to the observation that ω has the property that every initial segment (i.e. every subset of the form {0, 1, ..., n}) is finite.
So you could say: a set S is countable if it admits an ordering that makes it 'covered by all finite initial segments'.
...but this boils down to saying the set admits an ordering that makes it isomorphic to some n, or ω... which just means it's countable in the usual sense.
The algebra and calculus of algebraic data types - https://news.ycombinator.com/item?id=17942112 - Sept 2018 (48 comments)
The Algebra of Algebraic data types - https://news.ycombinator.com/item?id=9775467 - June 2015 (40 comments)
Dumb idea: Unzipping?
Differentiation takes as input the overall structure and extracts the behavior at a certain location. Integration takes the behavior at many different locations and returns a coherent picture of the overall structure.
I'm struggling to picture the math. Does this work the way I envision?
1) A brief introduction to the Algebra of Types (nice intuitive explanation) - https://code.egym.de/a-brief-introduction-to-the-algebra-of-...
2) Algebraic data type - https://en.wikipedia.org/wiki/Algebraic_data_type
3) Comparison of programming languages (algebraic data type) - https://en.wikipedia.org/wiki/Comparison_of_programming_lang...
This makes me wonder, is there a ring of types? There's addition and multiplication. Division and subtraction aren't necessary to define a ring, so their absence isn't particularly surprising.
Those two structs are equivalent (isomorphic) in the algebraic sense – names don’t matter, and order (indices are just a kind of names) doesn’t either. (a, b) = a x b = b x a = (b, a).
You have to work up to isomorphism anyway even to get a noncomutative semiring because technically the multiplication isn't exactly associative only associative up to an isomorphism.
However, I have grown skeptical on its pedagogical use in programming. Specifically, I question if it is really how most people think in terms of programming.
I further question if this is how most modelling considers things. An easy example would be chemistry. There are algebraic ideas in chemistry, but I would struggle to say how they look the same as this.
Am I looking past an obvious aide to modelling that these approaches can give? I appreciate how this post acknowledges that there are not obvious uses of integration and such, but I feel that sort of proves my point. There are familiar mathematical tools that are good to use when we can. This does not mean you need to force them onto everything you have.
It would probably help me a ton, if I ever saw these applied to state machines?
No, of course not. Algebra is only useful when you want to manipulate that exact thing, and most programmers never do any complex manipulation of types.
Compiler programmers writing powerful type systems do think that way.
> There are algebraic ideas in chemistry, but I would struggle to say how they look the same as this.
Oh no, they don't. The algebra of Quantum Mechanics is way weirder than that. (At least when you move after simple rotations and step-potential wheels.)
Then yes, I agree with you.
A user can have one of two roles: Normal or Admin. You can model this as:
- User = Name x Password x Role
- Role = Normal + Admin
or you can multiply it out and get - User = Admin or Normal
- Admin = Name x Password
- Normal = Name x Password
Which you should choose depends on the application. The second representation forces you to consider at every step if you have an Admin or Normal user, which is better for security. The first representation is more convenient when you don't care about the role.I do these transformations a lot when modelling things. They are trivial, but if you don't understand the underlying algebra you might not see them (in the same way the developers who aren't used to sum types tend to be blind to them.)
Does that help?
Is it (Name x Password x Role) x (Normal + Admin) = ?
What data-structure would that represent?
Then
(Name x Password x Role) x (Normal + Admin)
= (Name x Password x Role) x Role
= Name x Password x Role x Role
(See the normal rules of algebra apply)In Ocaml you would write
type Something = User * Role
or type Something = Name * Password * Role * Role
In Typescript you would have something like type Something = { user: User, role: Role }
or type Something = { name: Name, password: Password, role1: Role, role2: Role }
These two types are isomorphic (they have the same structure when you ignore irrelevant details) but you'd program with them slightly differently.Neither type makes a great deal of sense, but it's what you asked for. :-)
For example, if instead of Role, it was IsAdmin, and IsAdmin = True | False, then your application here gets you the odd True = Name x Password. What does that even mean?
It also has an odd one of what if Role = Normal + Admin + SuperAdmin, but User is still Admin | Normal? What does that even mean?
User has a Name, Password, and a Role, which gives
User = Name x Password x Role
Role is Normal or Admin. Formally: Role = Normal + Admin
Now substitute User = Name x Password x (Normal + Admin)
Multiply out User = (Name x Password x Normal) + (Name x Password x Admin)
I added an extra step where I named some of the components.If you understand how this can be represented in code as an algebraic data type, you will see these two different representations have different properties when you come to program with them.
If you don't, I'm afraid this text box does not contain enough space. Chapter 2 of https://scalawithcats.com/ goes through this in much more detail.
The rest of your math, I largely followed, but I can't see where `Normal = Name x Password` results. Nor do I see what it would communicate. This is why I dropped instead to Boolean = True | False. If I substitute that in your statements, I get `True = Name x Password`. And I don't see how that means anything. :(
So when we write
User = (Name x Password x Normal) + (Name x Password x Admin)
we know that Normal and Admin distinguish two otherwise identical cases. So with a bit of substitution we can write User = Normal + Admin
Normal = (Name x Password)
Admin = (Name x Password)
True + False is isomorphic to Normal + Admin, but if you aren't used to working in a language with algebraic data types you might not be used to thinking in this way.In Haskell you'd write something like
data User = Normal Admin Password | Admin Name Password
You could equally write data User = True Admin Password | False Name Password
because True and False are just tags, distinct from their use in the boolean type. This would just really confusing to other programmers.To that end, if this is a matter of going fast due to constraints of a message board, I am fine. My specific confusion was at the start where you had `Admin = Name x Password`. I'm not at all clear on what that would communicate to me in the types of any variables I would have in my code.
Admin = Name x Password defines a type called Admin that has a Name and a Password.
In OCaml I would write something like
type Admin = Name * Password
In Scala I'd write final case class Admin(name: Name, password: Password)
The construction User = Normal + Admin
Normal = (Name x Password)
Admin = (Name x Password)
in words is: A User is Normal or Admin
A Normal (User) is a Name and a Password
An Admin (User) is a Name and a Password
In Scala enum User:
case Normal(name: Name, password: Password)
case Admin(name: Name, password: Password)
In OCaml type User =
Normal of Name * Password
| Admin of Name * PasswordI'm also confused with these assertions, as with this formulation, you make it far too easy to think that a User is an Admin. Due to the norm for equality rules being that they are transitive.
Again, if this is just a shortcoming of this particular example, I'm fine with that. I am not trying to tear down the whole idea over the example. I literally just don't understand what it would mean for my model to say that an admin is a name and a password. Transitively, that feels very dangerous when considered in the way I'm used to modeling authentication and authorization.
There's (somewhat) good reasons we don't define these operations, of course. All the same, without being able to represent these inverse operations, the ease of which it maps to familiar notions of the typical algebras we're used to ends up thrown out the window.
The algebra metaphor makes a bit more sense when you move beyond ADTs into more advanced levels of type-theory. Dependent types are a fair bit beyond a mere algebra, instead being more like a complete metalanguage akin to higher-order logic.