On Mon, Mar 4, 2013 at 5:50 PM, Rob Stewart <robstewart57@gmail.com> wrote:
...
-----
import Control.Concurrent

-- API approach 1: Using type classes
class FooC a where
  mkFooC :: IO a
  readFooC :: a -> IO Int
  incrFooC :: a -> IO ()

I recommend taking 'mkFooC' out of the typeclass.  It keeps you from being able to (easily) construct a 'FooC' from dynamic data, e.g.:

    mkFoo :: Host -> Port -> IO MyFoo

After this change, the typeclass approach and the data constructor approach are nearly equivalent, except:

 * With the typeclass approach, the compiler passes the dictionary implicitly, which can be more convenient to use (e.g. `readFooC a` instead of `readFooC (getFoo a)`).

 * With the typeclass approach, you have to define a Foo type to contain the environment needed for Foo methods.  With the record approach, you can just construct and use a FooT record directly.

Either way, don't forget about simple encapsulation:

    data LineDevice -- abstract

    -- Some LineDevice constructors for common tasks
    stdio :: LineDevice
    openFile :: FilePath -> IO LineDevice
    connectTo :: HostName -> PortId -> IO LineDevice

    getLine :: LineDevice -> Int -> IO ByteString
    putLine :: LineDevice -> ByteString -> IO ()

This interface is very easy to understand.  If you want to let users make their own LineDevice objects, you can still provide an "internal" module with something like this:

    data Driver = Driver
        { getLine :: Int -> IO ByteString
        , putLine :: ByteString -> IO ()
        }

    newLineDevice :: Driver -> IO LineDevice

Hope this helps,
-Joey