Personal tools

User:Benmachine/New network package

From HaskellWiki

< User:Benmachine(Difference between revisions)
Jump to: navigation, search
m (New ideas)
Line 37: Line 37:
newtype SocketDebug = SocketDebug Bool
newtype SocketDebug = SocketDebug Bool
instance SocketOption (SocketDebug a) where
instance SocketOption SocketDebug where
optionLevel _ = (#const IPPROTO_TCP)
optionLevel _ = (#const IPPROTO_TCP)
optionName _ = (#const SO_DEBUG)
optionName _ = (#const SO_DEBUG)

Revision as of 06:42, 21 June 2011

1 Origin

This concept originally arose as "fix up the network package" to address the following perceived flaws:

  • Timeout socket options are unusable because setSockOpt takes an Int but they want a struct timeval. This can't be worked around because the underlying C import is not exposed and anyway is imported with the wrong type.
  • Only Network.URI uses parsec, and most users of network probably don't use that module, so the extra dependency is probably unnecessary.
  • The strange behaviour of UnixSocket with connectTo and accept leads me to believe that perhaps the address datatypes aren't well thought-out, as the API design allows for nonsensical requests to be made.
  • The API is conditionally exposed based on what symbols are or are not defined on the compilation platform: in a sense this is good because unavailable APIs are caught at compile time, but the error message you get in this case is awkward and has led to spurious bug reports and confusion. It also doesn't seem possible to account for these API differences without yourself using CPP, which seems clumsy to me.
  • The PortNumber newtype defines a Num instance despite the fact that it really doesn't often make sense to subtract ports. It doesn't define a Read instance.

The last few points especially started to suggest that a change to the existing package would probably be quite sweeping and API-breaking, so the idea mutated into "design a new network package minus the above flaws", since that was likely to be less troublesome for upgraders.

2 New ideas

  • Essentially a three-tiered API:
    • the lowest-level FFI interface to the C API, exposing foreign imports and datatypes directly, along with their Storable instances, etc.
    • something akin to the current Network package, that is organised to be equivalent in expressivity to the FFI interface but using more idiomatic types and signatures, and taking care of all the marshalling under the hood.
    • nicer Haskell wrappers around the above that capture common or encouraged usage patterns. For example, many socket options only need to be set once, when the socket is created, so we might just add a [SocketOption] (or whatever) parameter to the socket creation function. This makes things less stateful and neater. Depending on how far we want to extend this idea, we could potentially make it a separate package so that we can take advantage of a wider range of dependencies (e.g. iteratee, safer-file-handles, etc.)
  • Socket options:
    • Instead of dispatching on a SocketOption ADT, use multiple functions: instead of
      setSocketOption sock Debug 1
      we have
      setSocketDebug sock True
      or something similar.
    • Or how about a class-based approach?
type SockOptName  = CInt
type SockOptLevel = CInt -- or maybe an Enum
type SockOptValue = VoidPtr
type SockOptLen   = CSockLen -- don't know what this is
type SockOpt      = (SockOptValue, SockOptLen)
class SocketOption a where
  optionLevel     :: a -> SockOptLevel
  optionName      :: a -> SockOptName
  optionValue     :: a -> SockOptValue
  fromSockOpt     :: SockOpt -> a
newtype SocketDebug = SocketDebug Bool
instance SocketOption SocketDebug where
  optionLevel _               = (#const IPPROTO_TCP)
  optionName  _               = (#const SO_DEBUG)
  optionValue (SocketDebug v) = (toVoidPtr v,
                                 sizeof v)
                                -- whatever that means
  fromSockOpt (v, len)        = SocketDebug $ fromVoidPtr len v
setSocketOption :: SocketOption so => so -> Socket -> IO ()
getSocketOption :: SocketOption so => Socket -> IO so
-- And maybe…
newtype SocketOption = forall so. SocketOption so => SocketOption so
setSocketOptions  :: [SocketOption] -> Socket -> IO ()
withSocketOptions :: [SocketOption] -> Socket -> (Socket -> IO r) -> IO r
-- the idea is that the type, and therefore class instance, is inferred from use of the newtype constructor:
twiddleDebug :: Socket -> IO ()
twiddleDebug s = do
  SocketDebug b <- getSocketOption s
  putStrLn $ "Socket debug is: " ++ show b
  setSocketOption s $ SocketDebug True
    • Both of these are more extensible with "pseudo-options" than the ADT approach, which is nice. The latter may aid encapsulation of the Socket type:
<Twey> Say the SocketOption class contains methods to transform it to the ints C 
<Twey> Not without knowing the internal details of the Socket type
<benmachine> ah but the C API uses void*
<Twey> void*, whatever
<benmachine> the point being that how you translate things to void* might depend 
             a little on which socket option it is
<Twey> Yeah
<benmachine> and hence can't necessarily be done in a uniform way
<Twey> Which is why it's in the class
<benmachine> you mean in the instance methods?
<Twey> Yes
<benmachine> so you're suggesting that there's a function which takes a socket 
             and a void* and does the FFI call
<benmachine> and the class instances take care of accepting an Integer and 
             turning it into a void*
<Twey> Not necessarily an Integer
<Twey> Whatever argument(s) they accept
<benmachine> sure
<benmachine> just an example
<Twey> *nod*
  • String and ByteString need to be given equal consideration. Whether this is done by module boundaries (as in Network.Socket.ByteString) or a type-class approach is still under discussion.
    • Why does String need equal consideration? It's only a legacy option.
      • At least equal? :) but strings are actually quite convenient to pattern match on and stuff.
    • I think that it encourages people to think about encoding and stuff if we use a concrete ByteString type everywhere. But maybe we want to let people not think about encoding sometimes? We don't want encoding-boilerplate all over the place.
      • How about abstracting Socket into a typeclass and having separate instances for encoded/raw sockets?
      • Or, having a filter mechanism in the Socket type
      • Or, exposing a Handle somehow and letting the clever new GHC7 IO stuff take care of it.

3 See also

network-fancy on Hackage