Understand Either as a Functor - purescript

Looking into how Either is defined as a functor, I can see that
derive instance functorEither :: Functor (Either a)
which reads to me as "You can map an Either so long as you can map its element.
But either doesn't have just one element. How would this be implemented without derive? Here's what I've tried:
data Either a b = Left a | Right b
instance functorEither :: Functor (Either a)
where
map f (Right b) = Right $ f b
map _ a = a
Of course, the types don't work here:
The Right has this signature: map :: forall a b. (a -> b) -> f a -> f b
The Left however, isn't okay: map :: forall a b. (a -> b) -> f a -> f a
Part of my intuition is saying that Either a b isn't a functor, only Either a is a functor. Which is why map works over Right and ignores Left
That doesn't really give me any intuition for how this is implemented. I still need a way of matching both constructors, don't I?
On the other hand, I think an implementation of map that replaces the inner function with identity is technically law-abiding for functor? The law of composition is met if you just ignore it?

While your proposed definition of the Functor instance indeed fails to compile, it isn't for the reason you say. And it's also "essentially" correct, just not written in a way that will satisfy the compiler.
For convenience, here's your definition again:
data Either a b = Left a | Right b
instance functorEither :: Functor (Either a)
where
map f (Right b) = Right $ f b
map _ a = a
and here's the actual error that you get when trying to compile it:
Could not match type
a02
with type
b1
while trying to match type Either a0 a02
with type Either a0 b1
while checking that expression a
has type Either a0 b1
in value declaration functorEither
where a0 is a rigid type variable
bound at (line 0, column 0 - line 0, column 0)
b1 is a rigid type variable
bound at (line 0, column 0 - line 0, column 0)
a02 is a rigid type variable
bound at (line 0, column 0 - line 0, column 0)
I admit that's a little hard to interpret, if you're not expecting it. But it has to do with the fact that map for Either a needs to have type forall b c. (b -> c) -> Either a b -> Either a c. So the a on the left of map _ a = a has type Either a b, while the one on the right has type Either a c - these are different types (in general), since b and c can be anything, so you can't use the same variable, a, to denote a value of each type.
(This question, although about Haskell rather than Purescript, goes deeper into explanation of exactly this error.)
To fix it, as implied in the question above, you have to explicitly mention that the value you're mapping over is a Left value:
data Either a b = Left a | Right b
instance functorEither :: Functor (Either a)
where
map f (Right b) = Right $ f b
map _ (Left a) = Left a
which is fine because Left a can be interpreted on the left as of type Either a b and on the right as an Either a c.
As for what the instance "does": you are correct that "Either a b isn't a functor, only Either a is a functor" - because a functor must take one type variable, which Either a does but Either a b doesn't. And yes, because the type variable that actually "varies" between Either a b and Either a c is the one that is used in Right, map must only map over the Right values, and leave the Left ones alone - that's the only thing that will satisfy the types needed.
Either a b is often interpreted as representing the result of a computation, where Left values represent failure while Right ones represent success. In this sense it's a slightly "expanded" version of Maybe - the difference is that rather than failure being represented by a single value (Nothing), you get a piece of data (the a type in Either a b) which can tell you information about the error. But the Functor instance works identically to that for Maybe: it maps over any success, and leaves failures alone.
(But there's no logical reason why you can't "map over" the Left values as well. The Bifunctor class is an extension of Functor which can do exactly that.)

Related

How declare tagged union of polymorphic collection types

I'm new to Purescript. My current learning exercise is to create a tagged union of polymorphic Array and List. I'll use it in a function that finds the length of any Array or List. Here's my attempt:
import Data.List as L
import Data.Array as A
data Collection = CollectionList (forall a. L.List a)
| CollectionArray (forall b. Array b)
colLength :: Collection -> Int
colLength (CollectionList list) = L.length list
colLength (CollectionArray arr) = A.length arr
main :: Effect Unit
main = do
logShow (colLength (CollectionArray [3,5]))
The compiler doesn't like it:
Could not match type Int with type b0
while checking that type Int is at least as general as type b0
while checking that expression 3 has type b0
in value declaration main
where b0 is a rigid type variable
I'm confused by the parts, checking that type Int is at least as general as type b0 and b0 is a rigid type variable. My intention was to allow b to be anything. Not sure what I did to make the compiler put conditions on what b can be.
If you know how, please show the correct way to define a tagged union of polymoric types that'll work in my colLength function.
forall a doesn't mean "any type goes here"
It means that whoever accesses the value, gets to choose what a is, and whoever provides the value has to make sure that the value is of that type. It's a contract between the provider and the consumer.
So when you provide the value CollectionArray [3,5], you have to make it such that it works for all possible a that whoever accesses that value later might choose.
Obviously, there is only one way you can construct such value:
CollectionArray []
What you probably actually meant to do (and I'm guessing here) was to make your collection polymorphic, in the sense that it can contain values of any type, but the type is chosen by whoever creates the collection, and then whoever accesses it later has to deal with that particular type.
To do that, you have to put the type variable on the outside:
data Collection a = CollectionList (L.List a)
| CollectionArray (Array a)
That way, when you create a collection CollectionArray [3,5], it becomes of type Collection Int, and now everywhere you pass it, such as colLength, will have to deal with that Int
This, in turn, can be achieved by making colLength itself generic:
colLength :: forall a. Collection a -> Int
colLength (CollectionList list) = L.length list
colLength (CollectionArray arr) = A.length arr
Now whoever accesses (i.e. calls) colLength itself gets to choose what a is, which works fine, because it's the same place that created the Connection Int in the first place.

What kind of morphism is `filter` in category theory?

In category theory, is the filter operation considered a morphism? If yes, what kind of morphism is it? Example (in Scala)
val myNums: Seq[Int] = Seq(-1, 3, -4, 2)
myNums.filter(_ > 0)
// Seq[Int] = List(3, 2) // result = subset, same type
myNums.filter(_ > -99)
// Seq[Int] = List(-1, 3, -4, 2) // result = identical than original
myNums.filter(_ > 99)
// Seq[Int] = List() // result = empty, same type
One interesting way of looking at this matter involves not picking filter as a primitive notion. There is a Haskell type class called Filterable which is aptly described as:
Like Functor, but it [includes] Maybe effects.
Formally, the class Filterable represents a functor from Kleisli Maybe to Hask.
The morphism mapping of the "functor from Kleisli Maybe to Hask" is captured by the mapMaybe method of the class, which is indeed a generalisation of the homonymous Data.Maybe function:
mapMaybe :: Filterable f => (a -> Maybe b) -> f a -> f b
The class laws are simply the appropriate functor laws (note that Just and (<=<) are, respectively, identity and composition in Kleisli Maybe):
mapMaybe Just = id
mapMaybe (g <=< f) = mapMaybe g . mapMaybe f
The class can also be expressed in terms of catMaybes...
catMaybes :: Filterable f => f (Maybe a) -> f a
... which is interdefinable with mapMaybe (cf. the analogous relationship between sequenceA and traverse)...
catMaybes = mapMaybe id
mapMaybe g = catMaybes . fmap g
... and amounts to a natural transformation between the Hask endofunctors Compose f Maybe and f.
What does all of that have to do with your question? Firstly, a functor is a morphism between categories, and a natural transformation is a morphism between functors. That being so, it is possible to talk of morphisms here in a sense that is less boring than the "morphisms in Hask" one. You won't necessarily want to do so, but in any case it is an existing vantage point.
Secondly, filter is, unsurprisingly, also a method of Filterable, its default definition being:
filter :: Filterable f => (a -> Bool) -> f a -> f a
filter p = mapMaybe $ \a -> if p a then Just a else Nothing
Or, to spell it using another cute combinator:
filter p = mapMaybe (ensure p)
That indirectly gives filter a place in this particular constellation of categorical notions.
To answer are question like this, I'd like to first understand what is the essence of filtering.
For instance, does it matter that the input is a list? Could you filter a tree? I don't see why not! You'd apply a predicate to each node of the tree and discard the ones that fail the test.
But what would be the shape of the result? Node deletion is not always defined or it's ambiguous. You could return a list. But why a list? Any data structure that supports appending would work. You also need an empty member of your data structure to start the appending process. So any unital magma would do. If you insist on associativity, you get a monoid. Looking back at the definition of filter, the result is a list, which is indeed a monoid. So we are on the right track.
So filter is just a special case of what's called Foldable: a data structure over which you can fold while accumulating the results in a monoid. In particular, you could use the predicate to either output a singleton list, if it's true; or an empty list (identity element), if it's false.
If you want a categorical answer, then a fold is an example of a catamorphism, an example of a morphism in the category of algebras. The (recursive) data structure you're folding over (a list, in the case of filter) is an initial algebra for some functor (the list functor, in this case), and your predicate is used to define an algebra for this functor.
In this answer, I will assume that you are talking about filter on Set (the situation seems messier for other datatypes).
Let's first fix what we are talking about. I will talk specifically about the following function (in Scala):
def filter[A](p: A => Boolean): Set[A] => Set[A] =
s => s filter p
When we write it down this way, we see clearly that it's a polymorphic function with type parameter A that maps predicates A => Boolean to functions that map Set[A] to other Set[A]. To make it a "morphism", we would have to find some categories first, in which this thing could be a "morphism". One might hope that it's natural transformation, and therefore a morphism in the category of endofunctors on the "default ambient category-esque structure" usually referred to as "Hask" (or "Scal"? "Scala"?). To show that it's natural, we would have to check that the following diagram commutes for every f: B => A:
- o f
Hom[A, Boolean] ---------------------> Hom[B, Boolean]
| |
| |
| |
| filter[A] | filter[B]
| |
V ??? V
Hom[Set[A], Set[A]] ---------------> Hom[Set[B], Set[B]]
however, here we fail immediately, because it's not clear what to even put on the horizontal arrow at the bottom, since the assignment A -> Hom[Set[A], Set[A]] doesn't even seem functorial (for the same reasons why A -> End[A] is not functorial, see here and also here).
The only "categorical" structure that I see here for a fixed type A is the following:
Predicates on A can be considered to be a partially ordered set with implication, that is p LEQ q if p implies q (i.e. either p(x) must be false, or q(x) must be true for all x: A).
Analogously, on functions Set[A] => Set[A], we can define a partial order with f LEQ g whenever for each set s: Set[A] it holds that f(s) is subset of g(s).
Then filter[A] would be monotonic, and therefore a functor between poset-categories. But that's somewhat boring.
Of course, for each fixed A, it (or rather its eta-expansion) is also just a function from A => Boolean to Set[A] => Set[A], so it's automatically a "morphism" in the "Hask-category". But that's even more boring.
filter can be written in terms of foldRight as:
filter p ys = foldRight(nil)( (x, xs) => if (p(x)) x::xs else xs ) ys
foldRight on lists is a map of T-algebras (where here T is the List datatype functor), so filter is a map of T-algebras.
The two algebras in question here are the initial list algebra
[nil, cons]: 1 + A x List(A) ----> List(A)
and, let's say the "filter" algebra,
[nil, f]: 1 + A x List(A) ----> List(A)
where f(x, xs) = if p(x) x::xs else xs.
Let's call filter(p, _) the unique map from the initial algebra to the filter algebra in this case (it is called fold in the general case). The fact that it is a map of algebras means that the following equations are satisfied:
filter(p, nil) = nil
filter(p, x::xs) = f(x, filter(p, xs))

Scala currying example in the tutorial is confusing me

I'm reading a bit about Scala currying here and I don't understand this example very much:
def foldLeft[B](z: B)(op: (B, A) => B): B
What is the [B] in square brackets? Why is it in brackets? The B after the colon is the return type right? What is the type?
It looks like this method has 2 parameter lists: one with a parameter named z and one with a parameter named op which is a function.
op looks like it takes a function (B, A) => B). What does the right side mean? It returns B?
And this is apparently how it is used:
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val res = numbers.foldLeft(0)((m, n) => m + n)
print(res) // 55
What is going on? Why wasn't the [B] needed when called?
In Scala documentation that A type (sometimes A1) is often the placeholder for a collection's element type. So if you have...
List('c','q','y').foldLeft( //....etc.
...then A becomes the reference for Char because the list is a List[Char].
The B is a placeholder for a 2nd type that foldLeft will have to deal with. Specifically it is the type of the 1st parameter as well as the type of the foldLeft result. Most of the time you actually don't need to specify it when foldLeft is invoked because the compiler will infer it. So, yeah, it could have been...
numbers.foldLeft[Int](0)((m, n) => m + n)
...but why bother? The compiler knows it's an Int and so does anyone reading it (anyone who knows Scala).
(B, A) => B is a function that takes 2 parameters, one of type B and one of type A, and produces a result of type B.
What is the [B] in square brackets?
A type parameter (that's also known as "generics" if you've seen something like Java before)
Why is it in brackets?
Because type parameters are written in brackets, that's just Scala syntax. Java, C#, Rust, C++ use angle brackets < > for similar purposes, but since arrays in Scala are accessed as arr(idx), and (unlike Haskell or Python) Scala does not use [ ... ] for list comprehensions, the square brackets could be used for type parameters, and there was no need for angular brackets (those are more difficult to parse anyway, especially in a language which allows almost arbitrary names for infix and postfix operators).
The B after the colon is the return type right?
Right.
What is the type?
Ditto. The return type is B.
It looks like this method has 2 parameter lists: one with a parameter named z and one with a parameter named op which is a function.
This method has a type parameter B and two argument lists for value arguments, correct. This is done to simplify the type inference: the type B can be inferred from the first argument z, so it does not have to be repeated when writing down the lambda-expression for op. This wouldn't work if z and op were in the same argument list.
op looks like it takes a function (B, A) => B.
The type of the argument op is (B, A) => B, that is, Function2[B, A, B], a function that takes a B and an A and returns a B.
What does the right side mean? It returns B?
Yes.
What is going on?
m acts as accumulator, n is the current element of the list. The fold starts with integer value 0, and then accumulates from left to right, adding up all numbers. Instead of (m, n) => m + n, you could have written _ + _.
Why wasn't the [B] needed when called?
It was inferred from the type of z. There are many other cases where the generic type cannot be inferred automatically, then you would have to specify the return type explicitly by passing it as an argument in the square brackets.
This is what is called polymorphism. The function can work on multiple types and you sometimes want to give a parameter of what type will be worked with. Basically the B is a type parameter and can either be given explicitly as a type, which would be Int and then it should be given in square brackets or implicitly in parentheses like you did with the 0. Read about polymorphism here Scala polymorphism

What does "coalgebra" mean in the context of programming?

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?
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 function mappend ∷ (τ, τ) → τ and an identity mzero ∷ τ. Other examples include things like groups (which are just like monoids except with an extra invert ∷ τ → τ 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 of n τ. 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 have mappend ∷ (τ, τ) → τ and mempty ∷ () → τ. We can turn these into a single function using a sum type—Either. It would look like this:
op ∷ Monoid τ ⇒ Either (τ, τ) () → τ
op (Left (a, b)) = mappend (a, b)
op (Right ()) = mempty
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 functions a → τ, b → τ and so on for any a, b,….)
This lets us talk about algebras as a type τ with a single function from some mess of Eithers 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 τ:
data MonoidArgument τ = Mappend τ τ -- here τ τ is the same as (τ, τ)
| Mempty -- here we can just leave the () out
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.:
instance Functor MonoidArgument where
fmap f (Mappend τ τ) = Mappend (f τ) (f τ)
fmap f Mempty = Mempty
So we can generalize our idea of an algebra even more. It's just some type τ with a function f τ → τ for some functor f. In fact, we could write this out as a typeclass:
class Functor f ⇒ Algebra f τ where
op ∷ f τ → τ
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 like class 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:
class Functor f ⇒ CoAlgebra f τ where
coop ∷ τ → f τ
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 over C which specifies the methods and properties of the objects.
As shown in the algebra example, if we have a bunch of functions like a → τ and b → τ for any a, b,…, we can combine them all into a single function using Either, 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 (called f and g), we can create a single one like so:
both ∷ τ → (a, b)
both x = (f x, g x)
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 of C. So if we want a length property (e.g. object.length), we would have a function C → 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 a setPosition method which takes an x and a y 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 implicit this of many other languages. A coalgebra essentially just encapsulates the behavior of taking a self parameter: that's what the first C in C → F C is.
So let's put it all together. Let's imagine a class with a position property, a name property and setPosition function:
class C
private
x, y : Int
_name : String
public
name : String
position : (Int, Int)
setPosition : (Int, Int) → C
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 Ints and a String. (This is our type C.) Then we need to come up with the coalgebra representing the class.
data C = Obj { x, y ∷ Int
, _name ∷ String }
We have two properties to write. They're pretty trivial:
position ∷ C → (Int, Int)
position self = (x self, y self)
name ∷ C → String
name self = _name self
Now we just need to be able to update the position:
setPosition ∷ C → (Int, Int) → C
setPosition self (newX, newY) = self { x = newX, y = newY }
This is just like a Python class with its explicit self variables. Now that we have a bunch of self → functions, we need to combine them into a single function for the coalgebra. We can do this with a simple tuple:
coop ∷ C → ((Int, Int), String, (Int, Int) → C)
coop self = (position self, name self, setPosition self)
The type ((Int, Int), String, (Int, Int) → c)—for any c—is a functor, so coop does have the form we want: Functor f ⇒ C → f C.
Given this, C along with coop 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 function f τ → τ 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 functor F : C → C—that is, an endofunctor. (All Haskell Functors are actually endofunctors from Hask → Hask.) Then, an algebra is just an object A from C with a morphism F A → A. A coalgebra is the same except with A → 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:
map ∷ (α → β) → (M α → M β)
return ∷ α → M α
join ∷ M (M α) → M α
The map function is just a proof of the fact that M is a Functor. So we can say that a monad is just a functor with two operations: return and join.
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 just join for lists.
With Haskell functors, the composition of two functors is a functor itself. In pseudocode, we could write this:
instance (Functor f, Functor g) ⇒ Functor (f ∘ g) where
fmap fun x = fmap (fmap fun) x
This helps us think about join as a mapping from f ∘ f → f. The type of join is ∀α. f (f α) → f α. Intuitively, we can see how a function valid for all types α can be thought of as a transformation of f.
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 α. So return is a transformation Identity → f.
Now we can think about a monad as just an algebra based around some functor f with operations f ∘ f → f and Identity → 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 and Identity → f. To get the corresponding coalgebra, we just flip the arrows. This gives us two new operations: f → f ∘ f and f → 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:
class Functor f ⇒ Comonad f where
coreturn ∷ f α → α
cojoin ∷ f α → f (f α)
So a comonad is then a coalgebra in a category of endofunctors.
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:
data IntList = Nil | Cons (Int, IntList)
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:
Nil :: () -> IntList
Cons :: (Int, IntList) -> IntList
Note that I have written type of Nil as () -> IntList, not simply IntList. 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
Nil :: 1 -> IntList
Cons :: Int × IntList -> IntList
where 1 is a unit set (set with one element) and A × B operation is a cross product of two sets A and B (that is, set of pairs (a, b) where a goes through all elements of A and b goes through all elements of B).
Disjoint union of two sets A and B is a set A | 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 both A and B, but with each of this elements 'marked' as belonging to either A or B, so when we pick any element from A | B we will immediately know whether this element came from A or from B.
We can 'join' Nil and Cons functions, so they will form a single function working on a set 1 | (Int × IntList):
Nil|Cons :: 1 | (Int × IntList) -> IntList
Indeed, if Nil|Cons function is applied to () value (which, obviously, belongs to 1 | (Int × IntList) set), then it behaves as if it was Nil; if Nil|Cons is applied to any value of type (Int, IntList) (such values are also in the set 1 | (Int × IntList), it behaves as Cons.
Now consider another datatype:
data IntTree = Leaf Int | Branch (IntTree, IntTree)
It has the following constructors:
Leaf :: Int -> IntTree
Branch :: (IntTree, IntTree) -> IntTree
which also can be joined into one function:
Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree
It can be seen that both of this joined functions have similar type: they both look like
f :: F T -> T
where F is a kind of transformation which takes our type and gives more complex type, which consists of x and | operations, usages of T and possibly other types. For example, for IntList and IntTree F looks as follows:
F1 T = 1 | (Int × T)
F2 T = Int | (T × T)
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), where T is some type and f is a function of type f :: F T -> T. In our examples F-algebras are (IntList, Nil|Cons) and (IntTree, Leaf|Branch). Note, however, that despite that type of f function is the same for each F, T and f themselves can be arbitrary. For example, (String, g :: 1 | (Int x String) -> String) or (Double, h :: Int | (Double, Double) -> Double) for some g and h 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 define fold-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:
foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10
It is possible to generalize such operation on any recursive datatype.
The following is a signature of foldr function:
foldr :: ((a -> b -> b), b) -> [a] -> b
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 applied foldr will have the following signature:
foldr ((+), 0) :: [Int] -> Int
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.
sumFold :: IntList -> Int
sumFold Nil = 0
sumFold (Cons x xs) = x + sumFold xs
We see that this function consists of two parts: first part defines this function's behavior on Nil part of IntList, and second part defines function's behavior on Cons 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:
reductor :: () | (Int × Int) -> Int
reductor () = 0
reductor (x, s) = x + s
It can be seen that reductor is a function of type F1 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 type T and for each function r :: F1 T -> T there exist a function, called catamorphism for r, which converts IntList to T, and such function is unique. Indeed, in our example a catamorphism for reductor is sumFold. Note how reductor and sumFold are similar: they have almost the same structure! In reductor definition s parameter usage (type of which corresponds to T) corresponds to usage of the result of computation of sumFold xs in sumFold 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:
(append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6]
This how it looks on our IntList:
appendFold :: IntList -> IntList -> IntList
appendFold ys () = ys
appendFold ys (Cons x xs) = x : appendFold ys xs
Again, let's try to write out the reductor:
appendReductor :: IntList -> () | (Int × IntList) -> IntList
appendReductor ys () = ys
appendReductor ys (x, rs) = x : rs
appendFold is a catamorphism for appendReductor which transforms IntList into IntList.
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:
data IntStream = Cons (Int, IntStream)
This is an infinite stream of integers. Its only constructor has the following type:
Cons :: (Int, IntStream) -> IntStream
Or, in terms of sets
Cons :: Int × IntStream -> IntStream
Haskell allows you to pattern match on data constructors, so you can define the following functions working on IntStreams:
head :: IntStream -> Int
head (Cons (x, xs)) = x
tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs
You can naturally 'join' these functions into single function of type IntStream -> Int × IntStream:
head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)
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 type
g :: T -> F T
where T is some type. From now on we will define
F1 T = Int × T
Now, F-coalgebra is a pair (T, g), where T is a type and g is a function of type g :: T -> F T. For example, (IntStream, head&tail) is an F1-coalgebra. Again, just as in F-algebras, g and T 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 type T and for every function p :: T -> F1 T there exist a function, called anamorphism, which converts T to IntStream, and such function is unique.
Consider the following function, which generates a stream of successive integers starting from the given one:
nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))
Now let's inspect a function natsBuilder :: Int -> F1 Int, that is, natsBuilder :: Int -> Int × Int:
natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)
Again, we can see some similarity between nats and natsBuilder. It is very similar to the connection we have observed with reductors and folds earlier. nats is an anamorphism for natsBuilder.
Another example, a function which takes a value and a function and returns a stream of successive applications of the function to the value:
iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))
Its builder function is the following one:
iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)
Then iterate is an anamorphism for iterateBuilder.
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.
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,
In general terms, a program in some programming language manipulates data. During the
development of computer science over the past few decades it became clear that an abstract
description of these data is desirable, for example to ensure that one's program does not depend on the particular representation of the data on which it operates. Also, such abstractness facilitates correctness proofs.
This desire led to the use of algebraic methods in computer science, in a branch called algebraic specification or abstract data type theory. The object of study are data types in themselves, using notions of techniques which are familiar from algebra. The data types used by computer scientists are often generated from a given collection of (constructor) operations,and it is for this reason that "initiality" of algebras plays such an important role.
Standard algebraic techniques have proved useful in capturing various essential aspects of data structures used in computer science. But it turned out to be difficult to algebraically describe some of the inherently dynamical structures occurring in computing. Such structures usually involve a notion of state, which can be transformed in various ways. Formal approaches to such state-based dynamical systems generally make use of automata or transition systems, as classical early references.
During the last decade the insight gradually grew that such state-based systems should not be described as algebras, but as so-called co-algebras. These are the formal dual of algebras, in a way which will be made precise in this tutorial. The dual property of "initiality" for algebras, namely finality turned out to be crucial for such co-algebras. And the logical reasoning principle that is needed for such final co-algebras is not induction but co-induction.
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.
Data which can be define as the information being processed by the program.
State which can be define as the information being shared by the program.
Then at this stage, I'd like to say that,
F-algebra is the study of functorial transformation acting over Data's Universe (as been defined here).
F-co-algebras is the study of functorial transformation acting on State's Universe (as been defined here).
During the life of a program, data and state co-exist, and they complete each other.
They are dual.
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
Induction is about finite data, co-induction is about infinite data.
The typical example of infinite data is the type of a lazy list (a
stream). For example, lets say that we have the following object in
memory:
let (pi : int list) = (* some function which computes the digits of
π. *)
The computer can’t hold all of π, because it only has a finite amount
of memory! But what it can do is hold a finite program, which will
produce any arbitrarily long expansion of π that you desire. As long
as you only use finite pieces of the list, you can compute with that
infinite list as much as you need.
However, consider the following program:
let print_third_element (k : int list) = match k with
| _ :: _ :: thd :: tl -> print thd
print_third_element pi
This program should print the
third digit of pi. But in some languages, any argument to a function is evaluated before being passed
into a function (strict, not lazy, evaluation). If we use this
reduction order, then our above program will run forever computing the
digits of pi before it can be passed to our printer function (which
never happens). Since the machine does not have infinite memory, the
program will eventually run out of memory and crash. This might not be the best evaluation order.
http://adam.chlipala.net/cpdt/html/Coinductive.html
In lazy functional programming languages like Haskell, infinite data structures
are everywhere. Infinite lists and more exotic datatypes provide convenient
abstractions for communication between parts of a program. Achieving similar
convenience without infinite lazy structures would, in many cases, require
acrobatic inversions of control flow.
http://www.alexandrasilva.org/#/talks.html
Relating the ambient mathematical context to usual programming tasks
What is "an algebra"?
Algebraic structures generally look like:
Stuff
What the stuff can do
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). A git 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 Durations can be added together. (But Dates cannot.) Durations live in a vector-space (not just a group) because they can also be scaled by outside numbers. (A type signature of scaling :: (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 leave Integers 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
dates/times (+ :: (Date,Duration) → Date),
places (Paris ≠ (+48.8567,+2.3508)! It's a shape, not a point.),
unstructured JSON which is supposed to be consistent in some sense,
wrong-but-close XML,
incredibly complex GIS data which should satisfy loads of sensible relations,
regular expressions which meant something to you, but mean considerably less to perl.
CRM that should hold all the executive's phone numbers and villa locations, his (now ex-) wife and kids' names, birthday and all the previous gifts, each of which should satisfy "obvious" relations (obvious to the customer) which are incredibly hard to code up,
.....
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, and Customer—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.

How to get the value of a Maybe in Haskell

I'm relatively new to Haskell and began to read "Real World Haskell".
I Just stumbled over the type Maybe and have a question about how to receive the actual value from a Just 1 for example.
I have written the following code:
combine a b c = (eliminate a, eliminate b, eliminate c)
where eliminate (Just a) = a
eliminate Nothing = 0
This works fine if I use:
combine (Just 1) Nothing (Just 2)
But if I change, for example, 1 to a String it doesn't work.
I think I know why: because eliminate has to give back one type, which is, in this case, an Int. But how can I change eliminate to deal at least with Strings (or maybe with all kind of types)?
From the standard Prelude,
maybe :: b -> (a -> b) -> Maybe a -> b
maybe n _ Nothing = n
maybe _ f (Just x) = f x
Given a default value, and a function, apply the function to the value in the Maybe or return the default value.
Your eliminate could be written maybe 0 id, e.g. apply the identity function, or return 0.
From the standard Data.Maybe,
fromJust :: Maybe a -> a
fromJust Nothing = error "Maybe.fromJust: Nothing"
fromJust (Just x) = x
This is a partial function (does not return a value for every input, as opposed to a total function, which does), but extracts the value when possible.
[edit from Author, 6 years later] This is a needlessly long answer, and I'm not sure why it was accepted. Use maybe or Data.Maybe.fromMaybe as suggested in the highest upvoted answer. What follows is more of a thought experiment rather than practical advice.
So you're trying to create a function that works for a bunch of different types. This is a good time to make a class. If you've programmed in Java or C++, a class in Haskell is kind of like an interface in those languages.
class Nothingish a where
nada :: a
This class defines a value nada, which is supposed to be the class's equivalent of Nothing. Now the fun part: making instances of this class!
instance Nothingish (Maybe a) where
nada = Nothing
For a value of type Maybe a, the Nothing-like value is, well, Nothing! This will be a weird example in a minute. But before then, let's make lists an instance of this class too.
instance Nothingish [a] where
nada = []
An empty list is kind of like Nothing, right? So for a String (which is a list of Char), it will return the empty string, "".
Numbers are also an easy implementation. You've already indicated that 0 obviously represents "Nothingness" for numbers.
instance (Num a) => Nothingish a where
nada = 0
This one will actually not work unless you put a special line at the top of your file
{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}
Or when you compile it you can set the flags for these language pragmas. Don't worry about them, they're just magic that makes more stuff work.
So now you've got this class and these instances of it...now let's just re-write your function to use them!
eliminate :: (Nothingish a) => Maybe a -> a
eliminate (Just a) = a
eliminate Nothing = nada
Notice I only changed 0 to nada, and the rest is the same. Let's give it a spin!
ghci> eliminate (Just 2)
2
ghci> eliminate (Just "foo")
"foo"
ghci> eliminate (Just (Just 3))
Just 3
ghci> eliminate (Just Nothing)
Nothing
ghci> :t eliminate
eliminate :: (Nothingish t) => Maybe t -> t
ghci> eliminate Nothing
error! blah blah blah...**Ambiguous type variable**
Looks great for values and stuff. Notice the (Just Nothing) turns into Nothing, see? That was a weird example, a Maybe in a Maybe. Anyways...what about eliminate Nothing? Well, the resultant type is ambiguous. It doesn't know what we are expecting. So we have to tell it what type we want.
ghci> eliminate Nothing :: Int
0
Go ahead and try it out for other types; you'll see it gets nada for each one. So now, when you use this function with your combine function, you get this:
ghci> let combine a b c = (eliminate a, eliminate b, eliminate c)
ghci> combine (Just 2) (Just "foo") (Just (Just 3))
(2,"foo",Just 3)
ghci> combine (Just 2) Nothing (Just 4)
error! blah blah Ambiguous Type blah blah
Notice you still have to indicate what type your "Nothing" is, or indicate what return type you expect.
ghci> combine (Just 2) (Nothing :: Maybe Int) (Just 4)
(2,0,4)
ghci> combine (Just 2) Nothing (Just 4) :: (Int, Int, Int)
(2,0,4)
Or, you could restrict the types that your function allows by putting its type signature explicitly in the source. This makes sense if the logical use of the function would be that it is only used with parameters of the same type.
combine :: (Nothingish a) => Maybe a -> Maybe a -> Maybe a -> (a,a,a)
combine a b c = (eliminate a, eliminate b, eliminate c)
Now it only works if all three Maybe things are the same type. That way, it will infer that the Nothing is the same type as the others.
ghci> combine (Just 2) Nothing (Just 4)
(2,0,4)
No ambiguity, yay! But now it is an error to mix and match, like we did before.
ghci> combine (Just 2) (Just "foo") (Just (Just 3))
error! blah blah Couldn't match expected type blah blah
blah blah blah against inferred type blah blah
Well, I think that was a sufficiently long and overblown answer. Enjoy.
I'm new to Haskell too, so I don't know if this exists in the platform yet (I'm sure it does), but how about a "get or else" function to get a value if it exists, else return a default?
getOrElse::Maybe a -> a -> a
getOrElse (Just v) d = v
getOrElse Nothing d = d
This is the answer I was looking for when I came to this question:
https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Maybe.html#v:fromJust
...and similarly, for Either:
https://hackage.haskell.org/package/either-unwrap-1.1/docs/Data-Either-Unwrap.html
They provide functions I would have written myself which unwrap the value from its context.
The eliminate function's type signature is:
eliminate :: Maybe Int -> Int
That's because it returns 0 on Nothing, forcing the compiler to assume that a :: Int in your eliminate function. Hence, the compiler deduces the type signature of the combine function to be:
combine :: Maybe Int -> Maybe Int -> Maybe Int -> (Int, Int, Int)
and that's precisely why it doesn't work when you pass a String to it.
If you wrote it as:
combine a b c = (eliminate a, eliminate b, eliminate c)
where eliminate (Just a) = a
eliminate Nothing = undefined
then it would have worked with String or with any other type. The reason relies on the fact that undefined :: a, which makes eliminate polymorphic and applicable to types other than Int.
Of course, that's not the aim of your code, i.e., to make the combine function total.
Indeed, even if an application of combine to some Nothing arguments would succeed (that's because Haskell is lazy by default), as soon as you try to evaluate the results you would get a runtime error as undefined can't be evaluated to something useful (to put it in simple terms).