[Haskell] ambiguous (partially defined) type problem / active objects

Maarten maarten at snowlion.nl
Tue Sep 12 07:18:51 EDT 2006


Dear all,

I'm trying to create active nodes that have a common base part and a 
user definable custom part, run in their own thread and can 
communicate/send message to each other. For some reason the compiler 
complains about an ambiguous type, while I feel, though the type may be 
partially ambiguous, I'm not using that particular part in related 
function calls. So in some ways I think there is nothing to complain. Is 
there a way to resolve this? Are there better/easier solutions to this 
problem?

My solution so far is as follows (see code is below):
The active nodes have different types that have a common base state and 
a changing top state. The base state will be responsible for connection 
nodes to each other, the top state is user defined.
1. I 'piggy-bag' two State transformers (and IO) onto each other, one 
with the base state and one with the custom state
2. In their own thread, the top state transformer processes the contents 
of a list (in which each element is an action) that comes from a channel.
3. The channel is basically the active object and can receive message.
4. Since 'active objects' need to be stored in a list in the base state 
they are wrapped in a general node data structure (GN) that hides the 
type of the active object.

In this way, actions can be fed to the channel and are executed in the 
thread and context of the state monad. One can define a changing 
topstate and add basic functionality to it. This works reasonably well, 
except that when I store a node into another node (using the base 
functionality) and later on retrieve it and then want to apply some 
other suitably typed functions functions using only the base 
functionality, the compiler complains that the type is ambiguous. 
However I feel it shouldn't complain, since the functions doesn't use 
the ambiguous part. Of course the problem is resolved as soon as a 
function establishes the type. However, in many cases I don't know (and 
don't want to know the type). Can this be resolved?

The code below is unfortunately rather long. Of course it doesn't 
compile, unless the last comment is removed. If removed it works fine.

Any comments and pointers appreciated. Thanks. (I'm quite new to 
haskell, so I may be missing some obvious things.)

Maarten

P.S. (Sorry for the long code sample below, but I don't know how to 
condens is into something smaller, while retaining the relevant properties).


{-# OPTIONS -fglasgow-exts -cpp -fallow-undecidable-instances #-}

module Main where

import Data.Generics
import Control.Monad.State

import Data.Unique

import Control.Concurrent (forkIO, forkOS)
import Control.Concurrent.Chan
import Control.Concurrent.MVar


instance Show Unique where
    show a = show (hashUnique a)

-- default node state; same for all states; nodes are active objects 
(see below)
data DefNodeState = DefNodeState { id::Unique,  nodes::ActiveObjects }
    deriving Show

-- make this default state a base state transformer
type BaseStateT m = StateT DefNodeState m
type BaseStateTIO = BaseStateT IO

-- TopStateT with parameterizable state
type TopStateT ts a = StateT ts BaseStateTIO a

instance Show (TopStateT b ()) where
    show a = "TopStateT Monad"

tsTc = mkTyCon "TopStateT"
instance forall ts. (Show ts, Typeable ts) => Typeable1 (StateT ts 
BaseStateTIO) where
    typeOf1 _ = mkTyConApp tsTc []


-- hide top state type so we can put them in a list; typeable so it can 
be cast back
data GN = forall o. (Show o,Typeable o) => GN o

instance Show (GN) where
    show (GN o) = "GN " ++ show o

gnTc = mkTyCon "GN"
instance Typeable GN where
    typeOf _ = mkTyConApp gnTc []

-- convenience type
type ActiveObjects = [GN]

-- active objects
-- make topstate an active object by putting it in a chan
data ActiveObject a = ActiveObject (Chan (TopStateT a ()))

-- make active object instance of show
instance Show (ActiveObject o) where
    show o = "ActiveObject Chan StateT"

-- make active object typeable
activeObjectTc = mkTyCon "ActiveObject"
instance Typeable1 ActiveObject where
    typeOf1 _ = mkTyConApp activeObjectTc []

-- make new active object over some state
newActiveObject action state t = do
    chan <- newChan
    contents <- getChanContents chan   
    forkIO (start (action contents) state)
    return (ActiveObject chan)


-- send an action if we are not interested in the result
send :: forall a . (Typeable a, Show a) => ActiveObject a -> TopStateT a 
() -> IO ()
send (ActiveObject chan) act = do
    writeChan chan act


-- sync an action if we are interested in the result
sync :: forall a b. (Typeable a, Show a) => ActiveObject a -> TopStateT 
a b -> IO b
sync ao f = do
    mv <- newEmptyMVar
    send ao (f' mv)
    a <- takeMVar mv
    return a
    where
    f' mv = do
        a <- f
        lift $ lift $ putMVar mv a



class SendSync t where
    sendM :: forall a. (Typeable a, Show a) =>
    t a -> TopStateT a () -> IO ()
    syncM :: forall a b. (Typeable a, Show a) =>
    t a -> TopStateT a b -> IO b


instance SendSync (ActiveObject) where
    sendM ao m = send ao m
    syncM ao m = sync ao m

{- unforunately doesn't work
instance SendSync (GN) where
    sendM (GN ao) m = sendM ao m
    syncM (GN ao) m = syncM ao m
-}

castGN :: forall a. (Show a,Typeable a) => GN -> ActiveObject a
castGN (GN o) = mcast o
    where
    mcast o = case (cast o) of
        Nothing -> error "No cast possible"
        Just a -> a


-- some test states for toplevel       
data TestState = TestState { val::Int }
    deriving (Show,Typeable)

data TestState2 = TestState2 { val2::Int }
    deriving (Show,Typeable)


 
uid :: forall b. TopStateT b Unique
uid = lift $ gets Main.id

changeUid :: forall b. Unique -> TopStateT b ()
changeUid n = do
    lift $ modify (\s -> s { Main.id = n })

newUid :: forall b. TopStateT b ()
newUid = do
    n <- lift $ lift $ newUnique
    lift $ modify (\s -> s { Main.id = n })


-- add a node to list in basestate
addNode :: forall o a. (Typeable a, Show o,Typeable o) => ActiveObject o 
-> TopStateT a ()
addNode n = do
    ns <- lift $ gets nodes
    lift $ modify (\s -> s { nodes = (GN n):ns })

-- get a node from list in basestate
getNode :: forall a o. (Show o,Typeable o) => Integer -> (TopStateT a 
(ActiveObject o))
getNode i = do
    ns <- lift $ gets nodes
    let ao = ns !! (fromInteger i)
    return (castGN ao)


-- action that executes messages send to it
action [] = return ()
action (e:es) = do
-- just for testing
    i <- uid
    s <- get
    ns <- lift get
    lift $ lift $ putStrLn $ "event (" ++ show i ++ "):" ++ " state:" ++ 
show s ++ " nstate:" ++ show ns
--
    e
    action es


-- create a new node state with a unique id
newNodeState = do
    u <- newUnique
    return (DefNodeState u [])
   
-- start evaluation
start action state = do
    ns <- newNodeState
    execStateT (execStateT action state) ns
    return ()

  
main = do
    ao <- newActiveObject action (TestState 5) TestState
    ao2 <- newActiveObject action (TestState2 5) TestState2

-- simple test
    u1 <- syncM ao (gets val)
    putStrLn $ "u1:" ++ show u1
       
-- show complete state
    r <- syncM ao (get)
    putStrLn $ show r

-- connect ao to ao2
    sendM ao (addNode ao2)

-- get node back
    ao2' <- syncM ao (getNode 0)
   
-- get unique id
    u <- syncM ao2' (uid)
    putStrLn $ "u:" ++ show (hashUnique u)

-- set a new id
    sendM ao2' (newUid)
 
-- show it
    u' <- syncM ao2' (uid)
    putStrLn $ "u':" ++ show (hashUnique u')
   
--    u2 <- syncM ao2' (gets val2)

    getChar
    return ()






More information about the Haskell mailing list