-- The power of infinity Infinite streams are great. For one thing, they can be used to record or "memo-ise" the values of a function whose domain is the natural numbers, so you can look up a value by indexing into the sequence, and so avoid recomputation of values. In fact, as Thorsten Altenkirch and Ralf Hinze have pointed out, lazily infinite objects of kinds other than streams are useful for memoising functions on any concrete (ie. first-order) datatype. The program below illustrates the usefulness of final coalgebras for functors other than the one which gives us infinite streams. It makes two applications of final coalgebras, as a type of states of a "tape" mechanism of some kind. There are two kinds of tape. 1. A Turing machine tapes. Such a tape is doubly infinite (like the integers). The tape is represented as an infinitary object in a final coalgebra. The object is in fact a Moore machine. The remainder of the Turing machine. ie. the finite automaton or control unit, which may be represented by an association list is a Mealey machine, modulo some beaurocracy concerning halting states. 2. A further example gives a wierd kind of "infinitely bifurcating tape". The inspiration for the representation of this tape is Gerard Huet's zipper structure for representing linear contexts in data structures. There's an extra twist though, as we are \emph{infinitely} in our data structure. The twist here can obviously be generalised to quite a lot of data structures (beyond regular ones - we could even have the data structure be an non-wellfounded, rootless, countably branching tree). The most general type of "tape" is a client process, which computes a "latest" command, paired with a function which dispatches on responses to the command. In the case of a genuine tape, the commands are to rewrite the current command and/or move to a next position, where a new command is stored. We think of the commands as "spread out in space", in a memory which is accessed "locally", with respect to a current position -- "PC relative". -- Mealy and Moore functors > newtype MealyF cmd rsp state = MealyF (cmd -> (rsp,state)) > instance Functor (MealyF cmd rsp) where > fmap f (MealyF g) = MealyF g' > where g' c = let (r,s') = g c in (r, f s') > newtype MooreF cmd rsp state = MooreF (cmd,rsp -> state) > instance Functor (MooreF cmd rsp) where > fmap f (MooreF (c,k)) = MooreF (c,f . k) -- greatest and least fixed points > data Functor f => Mu f = Mu (f (Mu f)) > data Functor f => Nu f = Nu (f (Nu f)) > unMu (Mu t) = t > unNu (Nu t) = t -- iteration, coiteration > iter :: (Functor f) => (f a -> a) -> Mu f -> a > coit :: (Functor f) => (a -> f a) -> a -> Nu f > iter alg = alg . fmap (iter alg) . unMu > coit coa = Nu . fmap (coit coa) . coa -- recursion , corecursion This is a slightly more complicated form of iteration and coiteration, that brings product types in (negatively) to iteration, and sum types in (positively) to coiteration. > rec :: (Functor f) => (f (Mu f,a) -> a) -> Mu f -> a > cor :: (Functor f) => (a -> f (Either (Nu f) a)) -> a -> Nu f > rec ih = ih . fmap (id `conj` rec ih) . unMu > cor hi = Nu . fmap (id `either` cor hi) . hi > conj :: (c -> a) -> (c -> b) -> c -> (a,b) > conj a b = \c -> (a c, b c) -- back to our example Here Char is being used as a set of "commands" from the tape to the finite state automaton, and Rsp is a set of responses from this automaton, which cause the automaton to roll left, roll right, or overwrite the current cell with a given character. > type Cmd = Char > data Rsp = GoLeft | GoRight | Write Char We first define an coalgebra for MooreF. The states are like those for an editor, with a stream of characters before the cursor, and a stream of characters after it. > type State = (Str Char, Str Char) > tape_co :: State -> MooreF Cmd Rsp State > tape_co (bef@(Str b bef'),aft@(Str a aft')) > = MooreF (a,\r -> case r of > GoLeft -> (bef',Str b aft) > GoRight -> (Str a bef,aft') > Write d -> (bef,Str d aft')) This gives us, by coiteration, a map from initial states to elements of the final coalgebra for MooreF > tape :: State -> Nu (MooreF Cmd Rsp) > tape = coit tape_co We make the coalgebra into a pointed coalgebra, with a distinguished initial tape which is empty. > empty :: State > empty = let blanks = bang ' ' in (blanks,blanks) Now we can define the tape as an element of the final coalgebra. > emptyTape :: Nu (MooreF Cmd Rsp) > emptyTape = tape empty At this point it would be good to define a Turing machine automaton as a coalgebra for the corresponding Mealy functor. -- General things Type of infinite streams. > data Str a = Str a (Str a) > bang :: a -> Str a > bang a = let x = Str a x in x > fight :: Nu (MooreF c r) -> Nu (MealyF c r) -> Str (c,r) > fight (Nu (MooreF (c,g))) (Nu (MealyF f)) > = Str (c,r) (fight (g r) me') > where (r,me') = f c -- Another kind of "tape" The idea here is that we navigate around in the "middle" of a infinite, rootless binary tree. We represent the position by a pair of two things: \begin{enumerate} \item A stream, representing "how to get here from above". \item An infinite (rooted) tree, representing the stuff below where we are. \end{enumerate} > data Tree = Tree Char Tree Tree -- non wf binary tree > type Cmd2 = Char > data Rsp2 = GoDownL | GoDownR | Up | Write2 Char > type State2 = (Str (Char,Either Tree Tree), Tree) The coalgebra, which lets us navigate around, and change the content of nodes. > co2 :: State2 -> MooreF Cmd2 Rsp2 State2 > co2 (above@(Str (ca,lrt) above'),t@(Tree c belowL belowR) ) > = MooreF (c,\r -> case r of > GoDownL -> (Str (c,Left belowR) above, belowL) > GoDownR -> (Str (c,Right belowL) above, belowR) > Up -> case lrt of > Left t' -> (above', Tree ca t t') > Right t' -> (above', Tree ca t' t) > Write2 c' -> (above,Tree c' belowL belowR)) > tape2 = coit co2 > blankTree = Tree ' ' blankTree blankTree > blankTape2 = let above = (' ', Left blankTree) > in (above,blankTree)