[Haskell-cafe] Library API design: functional objects VS type classes

Joey Adams joeyadams3.14159 at gmail.com
Tue Mar 5 21:47:10 CET 2013


On Mon, Mar 4, 2013 at 5:50 PM, Rob Stewart <robstewart57 at 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20130305/44fddbce/attachment.htm>


More information about the Haskell-Cafe mailing list