Question

I'm trying to send a message via the serial port to a Lego NXT using Haskell's interactive mode but I cannot figure out how to use the serialport functions correctly.

I have a message that should play a tone on the NXT which is of type ByteString

> let message = pack ([6, 0 ,0, 3, 224, 1, 208, 7]::[Word8])

I can open the serial port using openSerial.

openSerial :: FilePath -> SerialPortSettings -> IO SerialPort
> let mybrick = openSerial "/dev/tty.NXT-DevB" defaultSerialSettings

But then I get stuck. How should I use the send function?

send :: SerialPort -> B.ByteString -> IO Int
> send mybrick message

This gives my the following error message.

<interactive>:31:6:
    Couldn't match expected type `SerialPort'
                with actual type `IO SerialPort'
    In the first argument of `send', namely `mybrick'
    In the expression: send mybrick message
    In an equation for `it': it = send mybrick message
Was it helpful?

Solution

You need to sequence your Monad computations. I'll write it generally and simply then specifically for your situation.


The problem you have is you have a function f :: A -> IO B and another function g :: B -> IO C which feel like they ought to be combinable, but aren't quite---the second function needs a plain B, not the IO B that the first one returns.

This is exactly where the power of Monads comes into play. Knowing that IO is a monad, we can use a function like (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c to combine these Monadic functions. In fact, already, f >=> g :: A -> IO C like we require.

We can also use do notation which would require us to "bind" the return type of f and then apply that to g to get the output.

\a -> do b <- f a
         g b

which gives us a function of type A -> IO C again. In fact, this do notation is basically the definition of (>=>).


So how does this apply in your particular circumstance? Well,

let mybrick = openSerial "/dev/tty.NXT-DevB" defaultSerialSettings

gives you a mybrick :: IO SerialPort value. In order to use send :: SerialPort -> ByteString -> IO Int we need to "unwrap" mybrick from the IO Monad. So we can use do notation

do sp <- mybrick
   send sp message

Or, to make everything neater, we can just run the entire computation using do notation

do mybrick <- openSerial "/dev/tty.NXT-DevB" defaultSerialSettings
   send mybrick message

OTHER TIPS

openSerial path settings is an IO action, which produces a serial port. To get access to the serial port, you have to execute that action inside the IO monad. Your main could look something like this:

main = do 
    mybrick <- openSerial "/dev/tty.NXT-DevB" defaultSerialSettings
    let message = pack ([0, 3, 224, 1, 208, 7] :: [Word8])
    send mybrick message

The difference is, that the let binding just creates a new name for whatever comes after the equals sign. In this case, this leads to mybrick having type IO SerialPort, just like the error message says.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top