April 8, 2013 - Tagged as: haskell, en.
I’ve been skimming over gotour recently. I think it’s great introduction to language; it’s short, but concise and it shows some of the interesting features of language.
Last part of the tour is about goroutines and channels. My concurrent programming experience is very limited, and since Haskell is my favorite language, I decided to port goroutine examples to Haskell as a learning exercise.
Any criticism would be appreciated.
This is a very basic example of a program creating two threads and printing some strings.
module Main where
import Control.Concurrent
import Control.Monad
say :: String -> IO ()
= forM_ [1..5] $ \ _ -> do
say s 100000
threadDelay putStrLn s
main :: IO ()
= do
main $ say "world"
forkIO "hello" say
Simple channels example
module Main where
import Control.Concurrent
sum' :: [Int] -> Chan Int -> IO ()
= writeChan chan (sum ints)
sum' ints chan -- alternative, `pointfree` style:
-- sum' = flip writeChan . sum
main :: IO ()
= do
main let lst = [7, 2, 8, -9, 4, 0]
<- newChan
chan let (l1, l2) = splitAt (floor $ fromIntegral (length lst) / 2) lst
$ sum' l1 chan
forkIO $ sum' l2 chan
forkIO <- readChan chan
x <- readChan chan
y putStrLn $ unwords [ show x, show y, show $ x + y ]
This is where porting started getting tricky. Haskell channels are basically linked lists, and do not have length or size. In order to get a similar effect, I created a new channel type:
{-# LANGUAGE NamedFieldPuns #-}
module Main where
import Control.Concurrent
-- Buffered chan
data BChan a = BChan { chan :: Chan a, size :: MVar Int, limit :: Int }
newBChan :: Int -> IO (BChan a)
= do
newBChan bufsize <- newChan
chan <- newMVar 0
bvar return BChan{chan=chan, size=bvar, limit=bufsize}
readBChan :: BChan a -> IO a
BChan{chan, size, limit} = do
readBChan <- readChan chan
ret $ \i -> return (i-1)
modifyMVar_ size return ret
writeBChan :: BChan a -> a -> IO ()
@BChan{chan, size, limit} val = do
writeBChan bchan<- readMVar size
size' if size' == limit
then do
100000
threadDelay
writeBChan bchan valelse do
$ \i -> return (i+1)
modifyMVar_ size
writeChan chan val
main :: IO ()
= do
main <- newBChan 2
chan 1
writeBChan chan 2
writeBChan chan
<- readBChan chan
v1 print v1
<- readBChan chan
v2 print v2
Here one difference is that Haskell doesn’t fail with a “deadlock!!” error when an extra writeBChan
operation is added(or one of them is removed), but just waits forever(like in a real deadlock :-P ). I wonder whether there is a way to get an exception like that, it’s awesome.
Same as above, Haskell channels are not working like Go channels. I had to simulate Go channels’ behavior.
{-# LANGUAGE NamedFieldPuns, MultiWayIf #-}
module Main where
import Control.Concurrent
import Control.Monad (liftM)
-- Closable channel
data CChan a = CChan (MVar ([a], Int, Bool))
newCChan :: IO (CChan a)
= liftM CChan (newMVar ([], 0, False))
newCChan
readCChan :: CChan a -> IO (Maybe a)
CChan mvar) = do
readCChan (<- takeMVar mvar
(contents, size, closed) if | size == 0 && not closed -> do
putMVar mvar (contents, size, closed)CChan mvar)
readCChan (| size == 0 -> do
putMVar mvar (contents, size, closed)return Nothing
| otherwise -> do
let r = head contents
tail contents, size-1, closed)
putMVar mvar (return $ Just r
writeCChan :: CChan a -> a -> IO ()
CChan mvar) val = do
writeCChan (<- takeMVar mvar
(contents, size, closed) if closed
then error "writing to a closed chan"
else putMVar mvar (val : contents, size+1, closed)
forChan_ :: CChan a -> (a -> IO ()) -> IO ()
= do
forChan_ cchan f <- readCChan cchan
v case v of
Nothing -> return ()
Just v' -> f v' >> forChan_ cchan f
closeCChan :: CChan a -> IO ()
CChan mvar) =
closeCChan ($ \(contents, size, closed) -> return (contents, size, True)
modifyMVar_ mvar
fib :: Int -> CChan Int -> IO ()
= do
fib i chan 0 1
iter i
closeCChan chanwhere iter 1 x y = writeCChan chan x
= do
iter n x y
writeCChan chan x-1) y (x+y)
iter (n
main :: IO ()
= do
main <- newCChan
chan $ fib 10 chan
forkIO print forChan_ chan
This example still doesn’t quite work like Go code. This is because I used a stack instead of a queue. It should be trivial to fix this code though.
Now this is hard. In 66, example program listens multiple channels, and runs some code when any of the channels is ready. If multiple channels are ready at the same time, one of them is chosen randomly. 67 is similar, only difference is when none of the channels are ready, some default action is taken.
I’m actually not sure if it’s implementable with Haskell Chans, isEmptyChan :: Chan a -> IO Bool
is deprecated, and users are directed to TChans(I think it’s mostly same as a Chan, but working on STM).
Anyway, that’s it for now. I’ll go learn(pun intended) some STM, why we need them and what’s different about them, and then maybe I can implement this last two examples.