Showing posts with label continuations. Show all posts
Showing posts with label continuations. Show all posts

02 March, 2016

Coroutines and first order functions to flash an LED on an ESP8266

The Adafruit tutorial for flashing an LED using Lua on an ESP8266 is buggy: The main loop does not yield to the underlying operating system, and instead hogs the CPU causing eventual crash - usually after about 10 seconds for me.

Here's the code reproduced from the tutorial:

    while 1 do
    gpio.write(3, gpio.HIGH)
    tmr.delay(1000000) -- waits a second, without allowing OS to run
    gpio.write(3, gpio.LOW)
    tmr.delay(1000000) -- and again
    end

The NodeMCU people advocate using a node.js async callback style, where instead of delaying your thread, you would instead set an alarm for a callback to do the next thing. Using tmr.delay and looping a lot is strongly discouraged because it upsets the OS.

I hate that callback style of coding (for reasons).

Lua has co-routines, though, apparently because people wanted to do things like write scripts that kept yielding back to a game engine and then resuming. (See this history of Lua)

I've recently been playing with effect systems in Haskell (see blog tag extensible-effects) and realised that Lua co-routines provide enough infrastructure for (some of) this.

So I hacked up a prototype.

The "user program" looks very much like the Adafruit example:

flashDelay = 200 -- ms
function flasher()
  while 1 do
    gpio.write(3, gpio.HIGH)
    coroutine.yield(flashDelay)
    gpio.write(3, gpio.LOW)
    coroutine.yield(flashDelay)
  end
end
and can be run like this;
driveCoroutineGood(flasher)

You can also use driveCoroutineBad which uses the blocking tmr.delay instead of the asynchronous tmr.alarm, and get the same ESP-crashing behaviour as the original example.

The main difference is that calls to tmr.delay are replaced by a call to yield. In effect system language, that means the code is asking the effect handler (driveCoroutineGood or driveCoroutineBad) to perform an effect (to delay by the appropriate time) rather than performing the effect itself. How that actually happens is down to the effect handler: in the bad case, just calls tmr.delay; and in the good case, does all the wrapping up of callbacks for tmr.alarm.

This ends up looking almost identical to the code in this GitHub issue, tmr.delay() is synchronous and blocks the network stack.

On a very simple example, this is a bit overkill, but on a bigger example is probably more interesting: you can call coroutine.yield deep down inside a whole stack of functions, which becomes tangly if you're trying to build this manually as an async callback.

Other callback style stuff is hopefully amenable to this - for example, socket handling.

08 October, 2012

yield zipper

Oleg wrote about converting an arbitrary traversable into a zipper.

His code uses delimited continuations, and I puzzled a while (years...) before starting to understand what was going on.

I just read Yield: mainstream delimited continuations.

It looked to me like I could easily change Oleg's zipper can be expressed using "yield" which gives a different view, that I think I might have understood more easily - because I know yield from other languages, and don't properly have my head around continuations (which is basically the point of the "Yield" paper, I think)

So then, my altered version of the zipper on Oleg's page, using yield:

>  import Data.Traversable as T


>  type Zipper t a = Iterator (Maybe a) a (t a)

>  make_zipper :: T.Traversable t => t a -> Zipper t a
>  make_zipper t = run $ T.mapM f t
>   where
>   f a = do
>     r <- yield a
>     return $ maybe a id r

This is run and yield pretty much as defined on page 10 of the yield paper:

>  data Iterator i o r = Result r | Susp o (i -> Iterator i o r)
>  yield x = shift (\k -> return $ Susp x k)
>  run x = reset $ x >>= return . Result

and some test code:

>  sample = [1,2,3]

>  main = do
>    let (Susp a1 k1) = make_zipper sample
>    print a1
>    let (Susp a2 k2) = k1 Nothing
>    print a2
>    let (Susp a3 k3) = k2 $ Just 100
>    print a3
>    let (Result end) = k3 Nothing
>    print end

and below, to make this posting properly executable, here's Oleg's library code for shift/reset:

> -- The Cont monad for delimited continuations, implemented here to avoid
> -- importing conflicting monad transformer libraries

>  newtype Cont r a = Cont{runCont :: (a -> r) -> r}


>  instance Monad (Cont r) where
>     return x = Cont $ \k -> k x
>     m >>= f  = Cont $ \k -> runCont m (\v -> runCont (f v) k)

>  reset :: Cont r r -> r
>  reset m = runCont m id

>  shift :: ((a -> r) -> Cont r r) -> Cont r a
>  shift e = Cont (\k -> reset (e k))

Update 1: Changed types from Oleg's Z | ZDone to the yield paper's Susp | Result