Coinductive Data Type

Recursive (and mutually recursive) composite types for values in an eagerly evaluated language include two basic classes: InductiveDataType and CoinductiveDataType. These are closely related but can, in practice, be distinguished based on their constructors. Common examples of each are, respectively, the List (inductive), and the Stream (coinductive).

Here are example types in a notation familiar to me:

 type List A   = cons:(A,List A)->* | nil:()->*
 type Stream A = first:*->(A) & rest:*->(Stream A)

The use of '*' above represents the 'instance' of the value.

For the List, the '*' is on the right hand side of each production arrow because you may construct a list by either taking the value 'nil' or applying 'cons' around another list. One thing to note is that, by means of these constructors, one can inductively prove that 'cons:(a,l)' is a bigger list than 'l'. This further implies that lists must be finite. This is true of inductive types in general - by construction, they are always finite in an eagerly evaluated language.

For the Stream, the '*' is on the left hand side of each production arrow because the 'type' is defined in terms of what you can obtain from the instance (apply 'first' to obtain an 'A', apply 'rest' to obtain a 'Stream A') without regard to how it is constructed. Because of this, the Stream achieved from 'rest' could actually possess a wide variety of possible representations, so long as it was type-compatible with 'Stream A'.

The constructor for such a stream is based on use of codata - mutually dependent values that interweave in terms of functional expansion. A prototypical example might be the stream for the FibonacciSequence being constructed as:

  define fibseq = {first:\self x->(x.0), rest:\self x->(self <= (x.1,(x.0 + x.1))} <= (0,1)

With the <= being a special coinductive constructor (field) <= (seed), with 'field' being a record of functions or features, and 'seed' being the data to which the field is applied. The semantics is to interleave applications of field to the seed in order to retrieve attributes. This would allow:

  ; so I don't need to repeat it...
  let R = {first:\self x->(x.0),rest:\self x->(self <=(x.1,(x.0+x.1))}
  fibseq = (R <= (0,1))
  fibseq.first = (\self x ->(x.0)) R (0,1)
               = (\x -> [self->R](x.0)) (0,1)
               = (\x -> (x.0)) (0,1)
               = [x->(0,1)](x.0)
               = (0,1).0
               = 0
  fibseq.rest  = (\self x -> (self <=(x.1,(x.0+x.1)))) R (0,1)
               = (\x -> [self->R](self <= (x.1,(x.0+x.1))) (0,1)
               = (\x -> (R <= (x.1,(x.0+x.1))) (0,1)
               = [x->(0,1)] (R <= (x.1,(x.0+x.1)))
               = (R <= ((0,1).1 , ((0,1).0 + (0,1).1)))
               = (R <= (1,(0+1))
               = (R <= (1,1))

If you expand it a few more times, you'll quickly confirm that:
  fibseq.rest.rest = (R <= (1,2))
  fibseq.rest.rest.rest = (R <= (2,3))
  fibseq.rest.rest.rest.rest = (R <= (3,5))
  fibseq.rest^5 = (R <= (5,8))

And so on, with the corresponding sequence of 'first' items being: 0,1,1,2,3,5,8,13,... the FibonacciSequence.

Of course, the above Stream would be infinite because there is no termination clause. For a terminating stream, one would combine the two data types concepts and use something closer to:

  type TStream A = stream:(first:*`->A & rest:*`->(TStream A))->* | empty:()->*

At this point, of course, it becomes difficult to track the semantics of the '*', so it would usually be dropped. It is easier to use the implicit '&' types always being coinductive, with '|' types always being inductive:
  type TStream A = stream:(first:A & rest:(TStream A)) | empty:()

Note that with FirstClass functions, the coinductive type can return functions too... for example:
  type Set A = is_element:*->(A -> Bool) 
             & iterator:*->(TStream A) 
             & union:*->(Set A -> Set A) 
             & intersect:*->(Set A -> Set A)
             & difference:*->(Set A -> Set A)

This would be essentially an AbstractDataType for an immutable and potentially infinite but, due to iteration, non-invertible set - a collection of features useful for relational and logic programming. One couldn't directly mutate this set, but one could union/intersect/subtract to produce another set.

Some points of trivia:


what notation would this be? It's like no other functional notation, mathematical or programming, I've ever encountered. Thanks!

IIRC, I learned it when reading a tutorial on a CategoryTheory based programming language called Charity (CharityLanguage). It is quite similar in both syntax and semantics to Haskell and ML notations, if you are unfamiliar with them.

I'm actually very familiar with Haskell's semantics and syntax, but the whole '*' thing threw me off. I'll have to research Charity. Thanks for the lead.

As noted, the reference back to the instance (represented by '*' above) is usually implicit. It is used above only to help clarify the distinction between coinductive and inductive type constructors and accessors. Haskell, being a language that favors LazyEvaluation, pretty much makes every type potentially 'coinductive' by default (with the codata consisting of the function and the value in a lazy application).

Charity is a very pretty little language, but I doubt that, even in the years since I last looked at it, the language has matured into anything suitable for production labor.


See also CharityLanguage

NovemberZeroEight

CategoryTypeTheory


EditText of this page (last edited November 21, 2008) or FindPage with title or text search