
On Mon, Mar 4, 2013 at 5:50 PM, Rob Stewart
... ----- 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