[Haskell-cafe] How to implement a source-sink pattern
Roland Lutz
rlutz at hedmen.org
Wed Apr 1 19:31:29 UTC 2015
Hi!
I'm trying to implement a source-sink type of pattern where there are a
number of sinks, each connected to exactly one source, and a number of
sources, each connected to zero or more sinks. The program consists of
some modules, each defining their own sources and sinks. To illustrate
this, here's what this would look like in C:
/* global.h */
struct source {
char *state;
/* some more fields */
}
struct sink {
struct source *source;
char *state;
/* some more fields */
}
struct sink **get_sinks_for_source(struct source *source);
/* module_a.c */
struct source a_source, another_source;
struct sink foo, bar, baz;
...
foo.source = &a_source;
...
Since getting the list of sinks for a source is a common operation, I'd
probably define some kind of reverse map which is updated when a sink is
remapped to a new source.
I tried to rebuild this in Haskell, but the result is ridiculously
complicated. Since I can't use pointers, I had to define an ordinal type
class to enumerate the sources and sinks and use this to look up the
actual data from the world state. But then I couldn't define the Sink
type properly as I can't do:
data SinkInfo = Source a => SinkInfo { sinkSource :: a,
sinkState :: String }
I have to add the source type as a type attribute, leading to a world
state with as many parameters as there are sinks. Also, I couldn't figure
out how to implement a function like
sinksForSource :: Source a => WorldState p q r -> a -> [Sink b => b]
since the source types won't match the type of the lookup key, and the
sinks can be from different modules. Now here is the actual code
(comments indicate where I intend to split it into individual files):
{- Global.hs -}
data SourceInfo = SourceInfo { sourceState :: String }
class Eq a => Source a where
getSourceInfo :: WorldState p q r -> a -> SourceInfo
data SinkInfo a = SinkInfo { sinkSource :: a, sinkState :: String }
class Sink a where
getSinkInfo :: Source x => WorldState x x x -> a -> SinkInfo x
-- should allow different arguments to WorldState
{- ModuleA.hs -}
data ModuleASource = ASource | AnotherSource deriving (Eq)
instance Source ModuleASource where
getSourceInfo world ASource = aSource $ moduleAState world
getSourceInfo world AnotherSource = anotherSource $ moduleAState world
data ModuleASink = Foo | Bar | Baz
instance Sink ModuleASink where
getSinkInfo world Foo = foo $ moduleAState world
getSinkInfo world Bar = bar $ moduleAState world
getSinkInfo world Baz = baz $ moduleAState world
data ModuleAState p q r = ModuleAState { aSource :: SourceInfo,
anotherSource :: SourceInfo,
foo :: SinkInfo p,
bar :: SinkInfo q,
baz :: SinkInfo r }
sinksForSourceInModuleA :: Source x => ModuleAState x x x -> x -> [ModuleASink]
-- should allow different arguments to ModuleAState and return Sink b => [b]
sinksForSourceInModuleA (ModuleAState _ _ foo bar baz) source =
(if sinkSource foo == source then [Foo] else []) ++
(if sinkSource bar == source then [Bar] else []) ++
(if sinkSource baz == source then [Baz] else [])
{- Main.hs -}
data WorldState p q r = WorldState { moduleAState :: ModuleAState p q r }
initState :: WorldState ModuleASource ModuleASource ModuleASource
initState = WorldState $ ModuleAState (SourceInfo "a source init state")
(SourceInfo "another source init state")
(SinkInfo ASource "foo init state")
(SinkInfo ASource "bar init state")
(SinkInfo AnotherSource "baz init state")
remapBar :: WorldState p q r -> WorldState p ModuleASource r
remapBar (WorldState a) =
WorldState $ a { bar = SinkInfo AnotherSource (sinkState $ bar a) }
sinksForSource :: Source x =>
WorldState x x x -> x -> [ModuleASink]
-- should allow different arguments to ModuleAState and return Sink b => [b]
sinksForSource (WorldState a) source = sinksForSourceInModuleA a source
main :: IO ()
main = let before = initState
after = remapBar before in
do
putStrLn $ "Number of sinks for another source before: " ++
(show $ length $ sinksForSource before AnotherSource)
putStrLn $ "Number of sinks for another source after: " ++
(show $ length $ sinksForSource after AnotherSource)
There are some problems with this code:
* I couldn't figure out how to resolve the circular references which are created by splitting this into individual files.
* Source and sink ordinals can't be mixed between modules, so I wouldn't be able to add a module B anyway.
* Looking up the source/sink states by ordinal keys is kind of cumbersome but works.
* To get the list of sinks connected to a given source, the whole world state has to be polled. I expect this to be grossly inefficient (unless Haskell does some magic here) but I'm not sure how to add a cache in a consistent way.
I can't believe this should be so complicated in Haskell, so I guess I'm
trying to do this in an un-Haskell-ish way, or maybe there's something
obvious I haven't seen. I'd be happy about any suggestions.
Roland
More information about the Haskell-Cafe
mailing list