Thursday, October 16, 2008

Turtle Graphics :: Changing Interface

Recalling, my first idea of the Turtle Graphics posts was to see if it was likely that a beginner in functional programming would have invented monads by himself/herself.

Let's change the interface a bit, such that a command is a function from log to log.

(I will reuse definitions from the original post, i.e. turtle definition and step)

> type Command = [Turtle] -> [Turtle]

> start = Turtle { x=0, y=0, alpha=0 }

> go, left, right :: Command
> go [] = []
> go (t:ts) = t {x = x t + step * cos (alpha t),
>   y = y t + step * sin (alpha t),

>   alpha = alpha t} : t : ts

> rotate :: Double -> Command

> rotate v [] = []

> rotate v (t:ts) = t {alpha = alpha t + v} : t : ts

> left = rotate (pi/2)
> right = rotate (-pi/2)

> execute :: [Command] -> Turtle -> [Turtle]
> execute cs l = (foldr (.) id cs) [l]

Basically, the primitive commands have to work on the whole list and consider the case of an empty list, whereas commands defined in terms of other commands don't have that restriction. It's also easy to define new commands:

> go3 = go . go . go

But the primitive commands don't look good at all! But, it's an easy fix..

The problem is of course with the type of Command. A primitive command only cares about the first turtle in the list (the newest one), so why should a command take a whole list as parameter?

> type Command = Turtle -> [Turtle]


> go, left, right :: Command

> go t = return $ t {x = x t + step * cos (alpha t),

>
  y = y t + step * sin (alpha t),
>   alpha = alpha t}

> rotate :: Double -> Command

> rotate v t = return $ t {alpha = alpha t + v}

> left = rotate (pi/2)
> right = rotate (-pi/2)


> -- [Turtle] -> (Turtle -> [Turtle]) -> [Turtle]

> |>| :: [Turtle] -> Command -> [Turtle]
> [] |>| f = []

> (l:ls) |>| f = (f l) ++ (l:ls)


So, for go and rotate, we use a little trick, using return instead of putting the result in a list. What is much more interesting is that we now have a little asymmetry in the type of a command. It is not possible anylonger to use (.) for combining commands, so we define |>| to do that for us.

We must use a little lambda to define new commands..

> go3 = \t -> t |>| go |>| go |>| go

..but that shouldn't be too much of a problem.

We're not done yet, but at least we took care about our little logging problem we had the previous post.

No comments: