I have heard the term "coalgebras" several times in functional programming and PLT circles, especially when the discussion is about objects, comonads, lenses, and such. Googling this term gives pages that give mathematical description of these structures which is pretty much incomprehensible to me. Can anyone please explain what coalgebras mean in the context of programming, what is their significance, and how they relate to objects and comonads?
相关问题
- Unusual use of the new keyword
- Get Runtime Type picked by implicit evidence
- What's the point of nonfinal singleton objects
- PlayFramework: how to transform each element of a
- Error in Scala Compiler: java.lang.AssertionError:
相关文章
- Gatling拓展插件开发,check(bodyString.saveAs("key"))怎么实现
- RDF libraries for Scala [closed]
- Is it possible to write pattern-matched functions
- Why is my Dispatching on Actors scaled down in Akk
- Haskell underscore vs. explicit variable
- Is there something like the threading macro from C
- Top-level expression evaluation at compile time
- Stuck in the State Monad
Going through the tutorial paper A tutorial on (co)algebras and (co)induction should give you some insight about co-algebra in computer science.
Below is a citation of it to convince you,
Prelude, about Category theory. Category theory should be rename theory of functors. As categories are what one must define in order to define functors. (Moreover, functors are what one must define in order to define natural transformations.)
What's a functor? It's a transformation from one set to another which preserving their structure. (For more detail there is a lot of good description on the net).
What's is an F-algebra? It's the algebra of functor. It's just the study of the universal propriety of functor.
How can it be link to computer science ? Program can be view as a structured set of information. Program's execution correspond to modification of this structured set of information. It sound good that execution should preserve the program structure. Then execution can be view as the application of a functor over this set of information. (The one defining the program).
Why F-co-algebra ? Program are dual by essence as they are describe by information and they act on it. Then mainly the information which compose program and make them changed can be view in two way.
Then at this stage, I'd like to say that,
During the life of a program, data and state co-exist, and they complete each other. They are dual.
F-algebras and F-coalgebras are mathematical structures which are instrumental in reasoning about inductive types (or recursive types).
F-algebras
We'll start first with F-algebras. I will try to be as simple as possible.
I guess you know what is a recursive type. For example, this is a type for a list of integers:
It is obvious that it is recursive - indeed, its definition refers to itself. Its definition consists of two data constructors, which have the following types:
Note that I have written type of
Nil
as() -> IntList
, not simplyIntList
. These are in fact equivalent types from the theoretical point of view, because()
type has only one inhabitant.If we write signatures of these functions in a more set-theoretical way, we will get
where
1
is a unit set (set with one element) andA × B
operation is a cross product of two setsA
andB
(that is, set of pairs(a, b)
wherea
goes through all elements ofA
andb
goes through all elements ofB
).Disjoint union of two sets
A
andB
is a setA | B
which is a union of sets{(a, 1) : a in A}
and{(b, 2) : b in B}
. Essentially it is a set of all elements from bothA
andB
, but with each of this elements 'marked' as belonging to eitherA
orB
, so when we pick any element fromA | B
we will immediately know whether this element came fromA
or fromB
.We can 'join'
Nil
andCons
functions, so they will form a single function working on a set1 | (Int × IntList)
:Indeed, if
Nil|Cons
function is applied to()
value (which, obviously, belongs to1 | (Int × IntList)
set), then it behaves as if it wasNil
; ifNil|Cons
is applied to any value of type(Int, IntList)
(such values are also in the set1 | (Int × IntList)
, it behaves asCons
.Now consider another datatype:
It has the following constructors:
which also can be joined into one function:
It can be seen that both of this
joined
functions have similar type: they both look likewhere
F
is a kind of transformation which takes our type and gives more complex type, which consists ofx
and|
operations, usages ofT
and possibly other types. For example, forIntList
andIntTree
F
looks as follows:We can immediately notice that any algebraic type can be written in this way. Indeed, that is why they are called 'algebraic': they consist of a number of 'sums' (unions) and 'products' (cross products) of other types.
Now we can define F-algebra. F-algebra is just a pair
(T, f)
, whereT
is some type andf
is a function of typef :: F T -> T
. In our examples F-algebras are(IntList, Nil|Cons)
and(IntTree, Leaf|Branch)
. Note, however, that despite that type off
function is the same for each F,T
andf
themselves can be arbitrary. For example,(String, g :: 1 | (Int x String) -> String)
or(Double, h :: Int | (Double, Double) -> Double)
for someg
andh
are also F-algebras for corresponding F.Afterwards we can introduce F-algebra homomorphisms and then initial F-algebras, which have very useful properties. In fact,
(IntList, Nil|Cons)
is an initial F1-algebra, and(IntTree, Leaf|Branch)
is an initial F2-algebra. I will not present exact definitions of these terms and properties since they are more complex and abstract than needed.Nonetheless, the fact that, say,
(IntList, Nil|Cons)
is F-algebra allows us to definefold
-like function on this type. As you know, fold is a kind of operation which transforms some recursive datatype in one finite value. For example, we can fold a list of integer into a single value which is a sum of all elements in the list:It is possible to generalize such operation on any recursive datatype.
The following is a signature of
foldr
function:Note that I have used braces to separate first two arguments from the last one. This is not real
foldr
function, but it is isomorphic to it (that is, you can easily get one from the other and vice versa). Partially appliedfoldr
will have the following signature:We can see that this is a function which takes a list of integers and returns a single integer. Let's define such function in terms of our
IntList
type.We see that this function consists of two parts: first part defines this function's behavior on
Nil
part ofIntList
, and second part defines function's behavior onCons
part.Now suppose that we are programming not in Haskell but in some language which allows usage of algebraic types directly in type signatures (well, technically Haskell allows usage of algebraic types via tuples and
Either a b
datatype, but this will lead to unnecessary verbosity). Consider a function:It can be seen that
reductor
is a function of typeF1 Int -> Int
, just as in definition of F-algebra! Indeed, the pair(Int, reductor)
is an F1-algebra.Because
IntList
is an initial F1-algebra, for each typeT
and for each functionr :: F1 T -> T
there exist a function, called catamorphism forr
, which convertsIntList
toT
, and such function is unique. Indeed, in our example a catamorphism forreductor
issumFold
. Note howreductor
andsumFold
are similar: they have almost the same structure! Inreductor
definitions
parameter usage (type of which corresponds toT
) corresponds to usage of the result of computation ofsumFold xs
insumFold
definition.Just to make it more clear and help you see the pattern, here is another example, and we again begin from the resulting folding function. Consider
append
function which appends its first argument to second one:This how it looks on our
IntList
:Again, let's try to write out the reductor:
appendFold
is a catamorphism forappendReductor
which transformsIntList
intoIntList
.So, essentially, F-algebras allow us to define 'folds' on recursive datastructures, that is, operations which reduce our structures to some value.
F-coalgebras
F-coalgebras are so-called 'dual' term for F-algebras. They allow us to define
unfolds
for recursive datatypes, that is, a way to construct recursive structures from some value.Suppose you have the following type:
This is an infinite stream of integers. Its only constructor has the following type:
Or, in terms of sets
Haskell allows you to pattern match on data constructors, so you can define the following functions working on
IntStream
s:You can naturally 'join' these functions into single function of type
IntStream -> Int × IntStream
:Notice how the result of the function coincides with algebraic representation of our
IntStream
type. Similar thing can also be done for other recursive data types. Maybe you already have noticed the pattern. I'm referring to a family of functions of typewhere
T
is some type. From now on we will defineNow, F-coalgebra is a pair
(T, g)
, whereT
is a type andg
is a function of typeg :: T -> F T
. For example,(IntStream, head&tail)
is an F1-coalgebra. Again, just as in F-algebras,g
andT
can be arbitrary, for example,(String, h :: String -> Int x String)
is also an F1-coalgebra for some h.Among all F-coalgebras there are so-called terminal F-coalgebras, which are dual to initial F-algebras. For example,
IntStream
is a terminal F-coalgebra. This means that for every typeT
and for every functionp :: T -> F1 T
there exist a function, called anamorphism, which convertsT
toIntStream
, and such function is unique.Consider the following function, which generates a stream of successive integers starting from the given one:
Now let's inspect a function
natsBuilder :: Int -> F1 Int
, that is,natsBuilder :: Int -> Int × Int
:Again, we can see some similarity between
nats
andnatsBuilder
. It is very similar to the connection we have observed with reductors and folds earlier.nats
is an anamorphism fornatsBuilder
.Another example, a function which takes a value and a function and returns a stream of successive applications of the function to the value:
Its builder function is the following one:
Then
iterate
is an anamorphism foriterateBuilder
.Conclusion
So, in short, F-algebras allow to define folds, that is, operations which reduce recursive structure down into a single value, and F-coalgebras allow to do the opposite: construct a [potentially] infinite structure from a single value.
In fact in Haskell F-algebras and F-coalgebras coincide. This is a very nice property which is a consequence of presence of 'bottom' value in each type. So in Haskell both folds and unfolds can be created for every recursive type. However, theoretical model behind this is more complex than the one I have presented above, so I deliberately have avoided it.
Hope this helps.
I'll start with stuff that is obviously programming-related and then add on some mathematics stuff, to keep it as concrete and down-to-earth as I can.
Let's quote some computer-scientists on coinduction…
http://www.cs.umd.edu/~micinski/posts/2012-09-04-on-understanding-coinduction.html
http://adam.chlipala.net/cpdt/html/Coinductive.html
http://www.alexandrasilva.org/#/talks.html
Relating the ambient mathematical context to usual programming tasks
What is "an algebra"?
Algebraic structures generally look like:
This should sound like objects with 1. properties and 2. methods. Or even better, it should sound like type signatures.
Standard mathematical examples include monoid ⊃ group ⊃ vector-space ⊃ "an algebra". Monoids are like automata: sequences of verbs (eg,
f.g.h.h.nothing.f.g.f
). Agit
log that always adds history and never deletes it would be a monoid but not a group. If you add inverses (eg negative numbers, fractions, roots, deleting accumulated history, un-shattering a broken mirror) you get a group.Groups contain things that can be added or subtracted together. For example
Duration
s can be added together. (ButDate
s cannot.) Durations live in a vector-space (not just a group) because they can also be scaled by outside numbers. (A type signature ofscaling :: (Number,Duration) → Duration
.)Algebras ⊂ vector-spaces can do yet another thing: there’s some
m :: (T,T) → T
. Call this "multiplication" or don't, because once you leaveIntegers
it’s less obvious what "multiplication" (or "exponentiation") should be.(This is why people look to (category-theoretic) universal properties: to tell them what multiplication should do or be like:
)
Algebras → Coalgebras
Comultiplication is easier to define in a way that feels non-arbitrary, than is multiplication, because to go from
T → (T,T)
you can just repeat the same element. ("diagonal map" – like diagonal matrices/operators in spectral theory)Counit is usually the trace (sum of diagonal entries), although again what's important is what your counit does;
trace
is just a good answer for matrices.The reason to look at a dual space, in general, is because it's easier to think in that space. For example it's sometimes easier to think about a normal vector than about the plane it's normal to, but you can control planes (including hyperplanes) with vectors (and now I'm speaking of the familiar geometric vector, like in a ray-tracer).
Taming (un)structured data
Mathematicians might be modelling something fun like TQFT's, whereas programmers have to wrestle with
+ :: (Date,Duration) → Date
),Paris
≠(+48.8567,+2.3508)
! It's a shape, not a point.),Computer scientists, when talking about coalgebras, usually have set-ish operations in mind, like Cartesian product. I believe this is what people mean when they say like "Algebras are coalgebras in Haskell". But to the extent that programmers have to model complex data-types like
Place
,Date/Time
, andCustomer
—and make those models look as much like the real world (or at least the end-user's view of the real world) as possible—I believe duals, could be useful beyond only set-world.Algebras
I think the place to start would be to understand the idea of an algebra. This is just a generalization of algebraic structures like groups, rings, monoids and so on. Most of the time, these things are introduced in terms of sets, but since we're among friends, I'll talk about Haskell types instead. (I can't resist using some Greek letters though—they make everything look cooler!)
An algebra, then, is just a type
τ
with some functions and identities. These functions take differing numbers of arguments of typeτ
and produce aτ
: uncurried, they all look like(τ, τ,…, τ) → τ
. They can also have "identities"—elements ofτ
that have special behavior with some of the functions.The simplest example of this is the monoid. A monoid is any type
τ
with a functionmappend ∷ (τ, τ) → τ
and an identitymzero ∷ τ
. Other examples include things like groups (which are just like monoids except with an extrainvert ∷ τ → τ
function), rings, lattices and so on.All the functions operate on
τ
but can have different arities. We can write these out asτⁿ → τ
, whereτⁿ
maps to a tuple ofn
τ
. This way, it makes sense to think of identities asτ⁰ → τ
whereτ⁰
is just the empty tuple()
. So we can actually simplify the idea of an algebra now: it's just some type with some number of functions on it.An algebra is just a common pattern in mathematics that's been "factored out", just like we do with code. People noticed that a whole bunch of interesting things—the aforementioned monoids, groups, lattices and so on—all follow a similar pattern, so they abstracted it out. The advantage of doing this is the same as in programming: it creates reusable proofs and makes certain kinds of reasoning easier.
F-Algebras
However, we're not quite done with factoring. So far, we have a bunch of functions
τⁿ → τ
. We can actually do a neat trick to combine them all into one function. In particular, let's look at monoids: we havemappend ∷ (τ, τ) → τ
andmempty ∷ () → τ
. We can turn these into a single function using a sum type—Either
. It would look like this:We can actually use this transformation repeatedly to combine all the
τⁿ → τ
functions into a single one, for any algebra. (In fact, we can do this for any number of functionsa → τ
,b → τ
and so on for anya, b,…
.)This lets us talk about algebras as a type
τ
with a single function from some mess ofEither
s to a singleτ
. For monoids, this mess is:Either (τ, τ) ()
; for groups (which have an extraτ → τ
operation), it's:Either (Either (τ, τ) τ) ()
. It's a different type for every different structure. So what do all these types have in common? The most obvious thing is that they are all just sums of products—algebraic data types. For example, for monoids, we could create a monoid argument type that works for any monoid τ:We can do the same thing for groups and rings and lattices and all the other possible structures.
What else is special about all these types? Well, they're all
Functors
! E.g.:So we can generalize our idea of an algebra even more. It's just some type
τ
with a functionf τ → τ
for some functorf
. In fact, we could write this out as a typeclass:This is often called an "F-algebra" because it's determined by the functor
F
. If we could partially apply typeclasses, we could define something likeclass Monoid = Algebra MonoidArgument
.Coalgebras
Now, hopefully you have a good grasp of what an algebra is and how it's just a generalization of normal algebraic structures. So what is an F-coalgebra? Well, the co implies that it's the "dual" of an algebra—that is, we take an algebra and flip some arrows. I only see one arrow in the above definition, so I'll just flip that:
And that's all it is! Now, this conclusion may seem a little flippant (heh). It tells you what a coalgebra is, but does not really give any insight on how it's useful or why we care. I'll get to that in a bit, once I find or come up with a good example or two :P.
Classes and Objects
After reading around a bit, I think I have a good idea of how to use coalgebras to represent classes and objects. We have a type
C
that contains all the possible internal states of objects in the class; the class itself is a coalgebra overC
which specifies the methods and properties of the objects.As shown in the algebra example, if we have a bunch of functions like
a → τ
andb → τ
for anya, b,…
, we can combine them all into a single function usingEither
, a sum type. The dual "notion" would be combining a bunch of functions of typeτ → a
,τ → b
and so on. We can do this using the dual of a sum type—a product type. So given the two functions above (calledf
andg
), we can create a single one like so:The type
(a, a)
is a functor in the straightforward way, so it certainly fits with our notion of an F-coalgebra. This particular trick lets us package up a bunch of different functions—or, for OOP, methods—into a single function of typeτ → f τ
.The elements of our type
C
represent the internal state of the object. If the object has some readable properties, they have to be able to depend on the state. The most obvious way to do this is to make them a function ofC
. So if we want a length property (e.g.object.length
), we would have a functionC → Int
.We want methods that can take an argument and modify state. To do this, we need to take all the arguments and produce a new
C
. Let's imagine asetPosition
method which takes anx
and ay
coordinate:object.setPosition(1, 2)
. It would look like this:C → ((Int, Int) → C)
.The important pattern here is that the "methods" and "properties" of the object take the object itself as their first argument. This is just like the
self
parameter in Python and like the implicitthis
of many other languages. A coalgebra essentially just encapsulates the behavior of taking aself
parameter: that's what the firstC
inC → F C
is.So let's put it all together. Let's imagine a class with a
position
property, aname
property andsetPosition
function:We need two parts to represent this class. First, we need to represent the internal state of the object; in this case it just holds two
Int
s and aString
. (This is our typeC
.) Then we need to come up with the coalgebra representing the class.We have two properties to write. They're pretty trivial:
Now we just need to be able to update the position:
This is just like a Python class with its explicit
self
variables. Now that we have a bunch ofself →
functions, we need to combine them into a single function for the coalgebra. We can do this with a simple tuple:The type
((Int, Int), String, (Int, Int) → c)
—for anyc
—is a functor, socoop
does have the form we want:Functor f ⇒ C → f C
.Given this,
C
along withcoop
form a coalgebra which specifies the class I gave above. You can see how we can use this same technique to specify any number of methods and properties for our objects to have.This lets us use coalgebraic reasoning to deal with classes. For example, we can bring in the notion of an "F-coalgebra homomorphism" to represent transformations between classes. This is a scary sounding term that just means a transformation between coalgebras that preserves structure. This makes it much easier to think about mapping classes onto other classes.
In short, an F-coalgebra represents a class by having a bunch of properties and methods that all depend on a
self
parameter containing each object's internal state.Other Categories
So far, we've talked about algebras and coalgebras as Haskell types. An algebra is just a type
τ
with a functionf τ → τ
and a coalgebra is just a typeτ
with a functionτ → f τ
.However, nothing really ties these ideas to Haskell per se. In fact, they're usually introduced in terms of sets and mathematical functions rather than types and Haskell functions. Indeed,we can generalize these concepts to any categories!
We can define an F-algebra for some category
C
. First, we need a functorF : C → C
—that is, an endofunctor. (All HaskellFunctor
s are actually endofunctors fromHask → Hask
.) Then, an algebra is just an objectA
fromC
with a morphismF A → A
. A coalgebra is the same except withA → F A
.What do we gain by considering other categories? Well, we can use the same ideas in different contexts. Like monads. In Haskell, a monad is some type
M ∷ ★ → ★
with three operations:The
map
function is just a proof of the fact thatM
is aFunctor
. So we can say that a monad is just a functor with two operations:return
andjoin
.Functors form a category themselves, with morphisms between them being so-called "natural transformations". A natural transformation is just a way to transform one functor into another while preserving its structure. Here's a nice article helping explain the idea. It talks about
concat
, which is justjoin
for lists.With Haskell functors, the composition of two functors is a functor itself. In pseudocode, we could write this:
This helps us think about
join
as a mapping fromf ∘ f → f
. The type ofjoin
is∀α. f (f α) → f α
. Intuitively, we can see how a function valid for all typesα
can be thought of as a transformation off
.return
is a similar transformation. Its type is∀α. α → f α
. This looks different—the firstα
is not "in" a functor! Happily, we can fix this by adding an identity functor there:∀α. Identity α → f α
. Soreturn
is a transformationIdentity → f
.Now we can think about a monad as just an algebra based around some functor
f
with operationsf ∘ f → f
andIdentity → f
. Doesn't this look familiar? It's very similar to a monoid, which was just some typeτ
with operationsτ × τ → τ
and() → τ
.So a monad is just like a monoid, except instead of having a type we have a functor. It's the same sort of algebra, just in a different category. (This is where the phrase "A monad is just a monoid in the category of endofunctors" comes from as far as I know.)
Now, we have these two operations:
f ∘ f → f
andIdentity → f
. To get the corresponding coalgebra, we just flip the arrows. This gives us two new operations:f → f ∘ f
andf → Identity
. We can turn them into Haskell types by adding type variables as above, giving us∀α. f α → f (f α)
and∀α. f α → α
. This looks just like the definition of a comonad:So a comonad is then a coalgebra in a category of endofunctors.