Debut with a Catamorphism

Hello everyone,

Many of you know I have been battling health problems for the last 2 years subsequent to a sporting injury. After a total of six surgical procedures, my most recent about 3 weeks ago, I am glad to be almost completely cured. Today I lodged a formal complaint to the Medical Health Board of Queensland against six doctors (coincidentally, the same number of operations — there were a total of 19 doctors mentioned).

As a result of the aforementioned, I haven’t written in a while, so I plan to return to my usual from now on.

I thought I’d write a little about a pervasive problem on the Scala mailing lists. Specifically, the misunderstanding of the purpose of static typing. Of course, there will always be lots of myths and proponents willing to ensure their survival, however, I am a strong believer that education is the only means by which we can advance the software development industry, despite the task often appearing insurmountable (ala Scala mailing list).

Some discussions recently on the Scala mailing list include the relationship between scala.Option data type and null and another discussion about documentation and types that are once-inhabited such as forall a b c. (a -> b) -> (b -> c) -> a -> c

I thought I’d propose a small piece of code and leave any potential insights unstated so as not to destroy anything for the observer. There is a Haskell version toward the end.

I propose the following data type:

trait MyOption[+A] {
  // single abstract method
  def cata[X](some: A => X, none: => X): X
}

Astute observers will notice that the cata method is similar to a combination of the map and getOrElse methods on scala.Option. This topic has also arisen on the Scala mailing list in the past. In other words, I could have written this:

trait MyOption[+A] {
  // two abstract methods
  def map[X](f: A => X): MyOption[X]
  def getOrElse[AA >: A](a: => AA): AA
}

…and I’d have a data structure that is exactly the same as the previous one. What may not be obvious is that MyOption is also exactly the same as scala.Option. The correct term for “exactly the same” is isomorphic. Yes, this is despite not using case classes or subclasses — coincidentally, another area where much mythology is prevalent.

I could also write construction functions that are akin to scala.None and scala.Some:

object MyOption {
  def none[A] = new MyOption[A] {
    def cata[X](s: A => X, n: => X) = n
  }
 
  def some[A](a: A) = new MyOption[A] {
    def cata[X](s: A => X, n: => X) = s(a)
  }
}

As an exercise, I propose filling out the Option API, however, using the single abstract method cata and the none/some constructor functions for additional convenience. I promise you it can be done and that anything scala.Option can do, MyOption can do also (and vice versa), since they are isomorphic.

trait MyOption[+A] {
  // single abstract method
  def cata[X](some: A => X, none: => X): X
 
  def map[B](f: A => B): MyOption[B] = error("todo")
 
  def flatMap[B](f: A => MyOption[B]): MyOption[B] = error("todo")
 
  def getOrElse[AA >: A](e: => AA): AA = error("todo")
 
  def filter(p: A => Boolean): MyOption[A] = error("todo")
 
  def foreach(f: A => Unit): Unit = error("todo")
 
  def isDefined: Boolean = error("todo")
 
  def isEmpty: Boolean = error("todo")
 
  // WARNING: not defined for None
  def get: A = error("todo")
 
  def orElse[AA >: A](o: MyOption[AA]): MyOption[AA] = error("todo")
 
  def toLeft[X](right: => X): Either[A, X] = error("todo")
 
  def toRight[X](left: => X): Either[X, A] = error("todo")
 
  def toList: List[A] = error("todo")
 
  def iterator: Iterator[A] = error("todo")
}
 
object MyOption {
  def none[A] = new MyOption[A] {
    def cata[X](s: A => X, n: => X) = n
  }
 
  def some[A](a: A) = new MyOption[A] {
    def cata[X](s: A => X, n: => X) = s(a)
  }
}

If you get stuck, the answer is base-64 encoded below, however, I encourage you to follow the types and to the extent that there may be ambiguity, follow the existing Scala API specification for scala.Option. If need be, please ask questions. Best of luck!

dHJhaXQgTXlPcHRpb25bK0FdIHsKICAvLyBzaW5nbGUgYWJzdHJhY3QgbWV0aG9kCiAgZGVmIGNh
dGFbWF0oc29tZTogQSA9PiBYLCBub25lOiA9PiBYKTogWAoKICBpbXBvcnQgTXlPcHRpb24uXwoK
ICBkZWYgbWFwW0JdKGY6IEEgPT4gQik6IE15T3B0aW9uW0JdID0gY2F0YShmIGFuZFRoZW4gc29t
ZSwgbm9uZSkKCiAgLy8gQWxzbwogIC8vIGRlZiBtYXBbQl0oZjogQSA9PiBCKTogTXlPcHRpb25b
Ql0gPSBmbGF0TWFwKGYgYW5kVGhlbiBzb21lKQoKICBkZWYgZmxhdE1hcFtCXShmOiBBID0+IE15
T3B0aW9uW0JdKTogTXlPcHRpb25bQl0gPSBjYXRhKGYsIG5vbmUpCgogIGRlZiBnZXRPckVsc2Vb
QUEgPjogQV0oZTogPT4gQUEpOiBBQSA9IGNhdGEocyA9PiBzLCBlKQoKICBkZWYgZmlsdGVyKHA6
IEEgPT4gQm9vbGVhbik6IE15T3B0aW9uW0FdID0gY2F0YShhID0+IGlmKHAoYSkpIHNvbWUoYSkg
ZWxzZSBub25lLCBub25lKQoKICBkZWYgZm9yZWFjaChmOiBBID0+IFVuaXQpOiBVbml0ID0gY2F0
YShmLCAoKSkKCiAgZGVmIGlzRGVmaW5lZDogQm9vbGVhbiA9IGNhdGEoXyA9PiB0cnVlLCBmYWxz
ZSkKCiAgZGVmIGlzRW1wdHk6IEJvb2xlYW4gPSBjYXRhKF8gPT4gZmFsc2UsIHRydWUpCgogIC8v
IFdBUk5JTkc6IG5vdCBkZWZpbmVkIGZvciBOb25lCiAgZGVmIGdldDogQSA9IGNhdGEoYSA9PiBh
LCBlcnJvcigiTm9uZS5nZXQiKSkKCiAgZGVmIG9yRWxzZVtBQSA+OiBBXShvOiBNeU9wdGlvbltB
QV0pOiBNeU9wdGlvbltBQV0gPSBjYXRhKF8gPT4gdGhpcywgbykKCiAgZGVmIHRvTGVmdFtYXShy
aWdodDogPT4gWCk6IEVpdGhlcltBLCBYXSA9IGNhdGEoTGVmdChfKSwgUmlnaHQocmlnaHQpKQoK
ICBkZWYgdG9SaWdodFtYXShsZWZ0OiA9PiBYKTogRWl0aGVyW1gsIEFdID0gY2F0YShSaWdodChf
KSwgTGVmdChsZWZ0KSkKCiAgZGVmIHRvTGlzdDogTGlzdFtBXSA9IGNhdGEoTGlzdChfKSwgTmls
KQoKICBkZWYgaXRlcmF0b3I6IEl0ZXJhdG9yW0FdID0gY2F0YShJdGVyYXRvci5zaW5nbGUoXyks
IEl0ZXJhdG9yLmVtcHR5KQp9CgpvYmplY3QgTXlPcHRpb24gewogIGRlZiBub25lW0FdID0gbmV3
IE15T3B0aW9uW0FdIHsKICAgIGRlZiBjYXRhW1hdKHM6IEEgPT4gWCwgbjogPT4gWCkgPSBuCiAg
fQoKICBkZWYgc29tZVtBXShhOiBBKSA9IG5ldyBNeU9wdGlvbltBXSB7CiAgICBkZWYgY2F0YVtY
XShzOiBBID0+IFgsIG46ID0+IFgpID0gcyhhKQogIH0KfQoK

For Haskell users:

{-# LANGUAGE RankNTypes #-}
 
-- Data.Maybe
newtype Option a = Option { cata :: forall x. (a -> x) -> x -> x }
 
-- Just
some :: a -> Option a
some a = Option (\s _ -> s a)
 
-- Nothing
none :: Option a
none = Option (const id)
 
-- fmap
map' :: (a -> b) -> Option a -> Option b
map' f m = error "todo"
 
-- (>>=)
flatMap :: (a -> Option b) -> Option a -> Option b
flatMap f m = error "todo"
 
-- fromMaybe
getOrElse :: Option a -> a -> a
getOrElse = error "todo"
 
filter :: Option a -> (a -> Bool) -> Option a
filter m p = error "todo"
 
-- mapM_
foreach :: Option a -> (a -> IO ()) -> IO ()
foreach m f = error "todo"
 
-- isJust
isDefined :: Option a -> Bool
isDefined m = error "todo"
 
-- isNothing
isEmpty :: Option a -> Bool
isEmpty m = error "todo"
 
-- WARNING: not defined for None
-- fromJust
get :: Option a -> a
get m = error "todo"
 
-- mplus
orElse :: Option a -> Option a -> Option a
orElse m n = error "todo"
 
toLeft :: Option a -> x -> Either a x
toLeft m x = error "todo"
 
toRight :: Option a -> x -> Either x a
toRight m x = error "todo"
 
-- maybeToList
toList :: Option a -> [a]
toList m = error "todo"
 
iterator = error "bzzt. This is Haskell silly."
ey0jIExBTkdVQUdFIFJhbmtOVHlwZXMgIy19CgotLSBEYXRhLk1heWJlCm5ld3R5cGUgT3B0aW9u
IGEgPSBPcHRpb24geyBjYXRhIDo6IGZvcmFsbCB4LiAoYSAtPiB4KSAtPiB4IC0+IHggfQoKLS0g
SnVzdApzb21lIDo6IGEgLT4gT3B0aW9uIGEKc29tZSBhID0gT3B0aW9uIChccyBfIC0+IHMgYSkK
Ci0tIE5vdGhpbmcKbm9uZSA6OiBPcHRpb24gYQpub25lID0gT3B0aW9uIChjb25zdCBpZCkKCi0t
IGZtYXAKbWFwJyA6OiAoYSAtPiBiKSAtPiBPcHRpb24gYSAtPiBPcHRpb24gYgptYXAnIGYgbSA9
IGNhdGEgbSAoc29tZSAuIGYpIG5vbmUKCi0tICg+Pj0pCmZsYXRNYXAgOjogKGEgLT4gT3B0aW9u
IGIpIC0+IE9wdGlvbiBhIC0+IE9wdGlvbiBiCmZsYXRNYXAgZiBtID0gY2F0YSBtIGYgbm9uZQoK
LS0gZnJvbU1heWJlCmdldE9yRWxzZSA6OiBPcHRpb24gYSAtPiBhIC0+IGEKZ2V0T3JFbHNlID0g
ZmxpcCBjYXRhIGlkCgpmaWx0ZXIgOjogT3B0aW9uIGEgLT4gKGEgLT4gQm9vbCkgLT4gT3B0aW9u
IGEKZmlsdGVyIG0gcCA9IGNhdGEgbSAoXGEgLT4gaWYgcChhKSB0aGVuIHNvbWUgYSBlbHNlIG5v
bmUpIG5vbmUKCi0tIG1hcE1fCmZvcmVhY2ggOjogT3B0aW9uIGEgLT4gKGEgLT4gSU8gKCkpIC0+
IElPICgpCmZvcmVhY2ggbSBmID0gY2F0YSBtIGYgKHJldHVybiAoKSkKCi0tIGlzSnVzdAppc0Rl
ZmluZWQgOjogT3B0aW9uIGEgLT4gQm9vbAppc0RlZmluZWQgbSA9IGNhdGEgbSAoY29uc3QgVHJ1
ZSkgRmFsc2UKCi0tIGlzTm90aGluZwppc0VtcHR5IDo6IE9wdGlvbiBhIC0+IEJvb2wKaXNFbXB0
eSBtID0gY2F0YSBtIChjb25zdCBGYWxzZSkgVHJ1ZQoKLS0gV0FSTklORzogbm90IGRlZmluZWQg
Zm9yIE5vbmUKLS0gZnJvbUp1c3QKZ2V0IDo6IE9wdGlvbiBhIC0+IGEKZ2V0IG0gPSBjYXRhIG0g
aWQgKGVycm9yICJOb25lLmdldCIpCgotLSBtcGx1cwpvckVsc2UgOjogT3B0aW9uIGEgLT4gT3B0
aW9uIGEgLT4gT3B0aW9uIGEKb3JFbHNlIG0gbiA9IGNhdGEgbSAoY29uc3QgbSkgbgoKdG9MZWZ0
IDo6IE9wdGlvbiBhIC0+IHggLT4gRWl0aGVyIGEgeAp0b0xlZnQgbSB4ID0gY2F0YSBtIExlZnQg
KFJpZ2h0IHgpCgp0b1JpZ2h0IDo6IE9wdGlvbiBhIC0+IHggLT4gRWl0aGVyIHggYQp0b1JpZ2h0
IG0geCA9IGNhdGEgbSBSaWdodCAoTGVmdCB4KQoKLS0gbWF5YmVUb0xpc3QKdG9MaXN0IDo6IE9w
dGlvbiBhIC0+IFthXQp0b0xpc3QgbSA9IGNhdGEgbSAoOltdKSBbXQoKaXRlcmF0b3IgPSBlcnJv
ciAiYnp6dC4gVGhpcyBpcyBIYXNrZWxsIHNpbGx5LiIK

11 Responses to “Debut with a Catamorphism”

  1. retronym Says:

    this:
    def getOrElse[AA : A](a: => AA): AA
    should be:
    def getOrElse[AA :> A](a: => AA): AA

  2. Tony Morris Says:

    Thanks Jason.

  3. Eric Says:

    Excellent exercise, that made my day!

    Now there is a trait defining the behavior of an Option[A], I’m wondering. In which design situation would that be useful to have “class A extends MyOption[A]“? (excepted to create some and none of course).

    Eric.

  4. Lachlan O'Dea Says:

    Glad to hear things are looking up for you on the medical front.

  5. Steven Shaw Says:

    Hi Tony, enjoyed the Scala version of this exercise. Learned some lessons from your Scala implementation too (won’t spoil for others). Cheers! On to try the Haskell version :)

  6. Steven Shaw Says:

    Haskell was a bit harder for me.

    Tony, question - what *is* a catamorphism?

    I saw that thread on the Scala mailing list about “once-inhabited type”. Google gives me nothing except the Scala thread on nabble :). Does once-inhabited mean that there’s only one solution for the function implementation?

  7. admin Says:

    G’day Steven,
    I don’t see much benefit in repeating much of the literature about what is a catamorphism, though I understand it may be difficult to digest. However, I could point out that:

    option.cata(Some, None) == option.

    In other words, plonking the constructors into cata gives you an identity function.

    Yes, once-inhabited means there is only one solution. Finding inhabitants for a type is equivalent to proving a logical theorem (see Curry-Howard Isomorphism), however, some languages allow you to “tell lies” in your proof e.g. bottom values, non-termination, side-effects. This does not negate the fact of the matter.

    The Scala mailing list has all sorts :) but education is paramount!

  8. Channing Walton Says:

    Hi Tony,
    glad to know your health problems are being resolved. I’ve been through something similar: 4 years of (at times extreme) pain from trapped nerves. A dozen doctors, two surgeons and physios didn’t find it. A pain specialist found it in 10 mins and his physio made significant improvements in 1/2 hour!!!

    Thanks for this example, its taught me a lot.

    Channing

  9. Arnaud Bailly Says:

    Hello,

    @Steven not sure this will really help you, but the first mention I read on such strange beasts was in the classical artical “Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire” (http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.41.125). As far as I understand it, a catamorphism is pure functional way to express behavior of instances of a type, something like an interface in OO, I guess, by encapsulating in a single HOF the “constructors” of the type.

    HTH
    Arnaud

  10. Steven Shaw Says:

    Hi Tony, on the meaning of catamorphism - no need to repeat the literature. A link would be good :). Perhaps a book recommendation.

    For instance, I get nothing from http://en.wikipedia.org/wiki/Catamorphism though I am reading the blog articles linked at the bottom.

  11. Tony Morris Says:

    Hi Channing,
    My sixth operation was for decompression of a trapped nerve (superficial peroneal 10cm proximal to lateral malleolus as it exits the deep fascia). It took 19 doctors two years to not find it and it took me about a year. Then it took a 15 page essay, citing several text books and medical journals, to convince 1 of 3 doctors, who upon performing the surgery, found that the site of entrapment was precisely where I’d said (I drew a dot with a marker).

    Thank medical science I found it in the end. Still recovering from the nerve damage (it was extremely compressed and caused a myriad of symptoms).

Leave a Reply