Store type-class polymorphic values generically

It's very easy to state this problem without enough details and mislead people into providing a solution for a different problem, so I'll try to include all the information and use-case. I need a function that can store a value in a concrete opaque type. I know the type of the value when I store it, and I know the type of the value when I extract it, insofar as I know the type inclusive of a class constraint context. But the container must store many different things at once. Given that I know the type of the thing when I store it and when I extract it, there ought to be a “safe’ way to do this, in the same way that Data.Dynamic is “safe”. The use-case is I want to store a table of IO-like actions in an opaque like [(Name,Dynamic)] and I write into it statically, e.g. in GHCi. The reason is due to this approach: go_ref = newIORef go_ go_ = putStrLn "Hello, World!" go = do go' <- liftIO $ readIORef go_ref go' main = forkIO $ forever $ go then in GHCi I writeIORef go_ (putStrLn "What's up?") then go will now start printing "What's up?" This works now, I can do this presently. However, I want to write this as a core-to-core translation as a ghc-plugin. I want the definition go = putStrLn "Hello World!" to be translated to what I wrote above. Core cannot generate new names to be exported from a module, so go_ is now gone. So how does the `go' function read and write the IORef? With a table: table = unsafePerformIO $ newIORef [("go",Dynamic go_ref)] Hurrah! I can now put table in some module like DynamicUpdate.table and then read/write to it from Core in the generated definition of go. Done. But how do I update it from the GHCi REPL? What follows is where I'm stuck. So with Data.Dynamic, I have: toDyn :: Typeable a => a -> Dynamic With toDyn I can store concrete values and functions and then extract them: λ> fmap ($ 12) (fromDynamic (toDyn ((*2) :: Int -> Int)) :: Maybe (Int -> Int)) Just 24 But the problem with toDyn is that it can only store concrete values due to the Typeable constraint. So then I turn to existentials and unsafeCoerce: λ> data Anything = forall a. Anything a λ> let x = Anything id λ> case x of Anything (unsafeCoerce -> (id' :: Int -> Int)) -> id' 123 123 Great, I was able to store a value which contained non-concrete types. But now consider the case of a value with type-class polymorphic type in it: y = Anything (print :: Show a => a -> IO ()) Which cannot be type checked, because the Show instance is ambiguous. So you might propose to use rank-N types, like this: data Anything = Anything (Show a => a -> IO ()) Now I can store print in there happily. But now the type is not Anything, it's not generic anymore. Maybe I want to store `print', maybe I want to store `readLn'. Dead end. I don't want to have to generate existential wrappers for all functions I might possibly want to store. The support of Constraints in this page http://blog.omega-prime.co.uk/?p=127 makes me think that it's still possible. It would be cool to somehow store a tuple of the dictionary of the constraint and the value itself and then I'd later be able to extract it. But I'm finding it difficult developing anything even simple. Any ideas/help appreciated!

Christopher Done wrote:
It's very easy to state this problem without enough details and mislead people into providing a solution for a different problem, so I'll try to include all the information and use-case. I need a function that can store a value in a concrete opaque type. I know the type of the value when I store it, and I know the type of the value when I extract it, insofar as I know the type inclusive of a class constraint context. But the container must store many different things at once. Given that I know the type of the thing when I store it and when I extract it, there ought to be a “safe’ way to do this, in the same way that Data.Dynamic is “safe”.
[..]
I have to ashamedly admit that I didn't read your problem description in its entirety, but maybe my vault package can help? http://hackage.haskell.org/package/vault In particular, the Locker stores arbitrary values like Dynamic , except that values are extracted and removed with the help of a Key . This gets rid of the Typeable constraint. Note that there is a fundamental problem with storing polymorphic types, which is related to the "value restriction for reference types". One of the main points of the Typable class is actually that it enforces monomorphic types. Similarly, the vault library enforces monomorphic types by having newKey in the IO monad. Best regards, Heinrich Apfelmus -- http://apfelmus.nfshost.com

On 4 October 2013 10:56, Heinrich Apfelmus
In particular, the Locker stores arbitrary values like Dynamic , except that values are extracted and removed with the help of a Key . This gets rid of the Typeable constraint.
lock :: Key a -> a -> Locker I can't pass anything with class constraints to that.

Christopher Done wrote:
On 4 October 2013 10:56, Heinrich Apfelmus
wrote: In particular, the Locker stores arbitrary values like Dynamic , except that values are extracted and removed with the help of a Key . This gets rid of the Typeable constraint.
lock :: Key a -> a -> Locker
I can't pass anything with class constraints to that.
I don't know what "something with a class constraint" means. But I guess you want to pass a value with a *polymorphic* type? This is no problem, but requires impredicative polymorphism: a = (forall b. Show b => b -> IO ()) lock :: Key (forall b. Show b => b -> IO ()) -> (forall b. Show b => b -> IO ()) -> Locker Unfortunately, GHC's support for that is a little shaky, but a solution that always works is to put it in a new data type. data Dummy = Dummy { unDummy :: forall b. Show b => b -> IO () } lock :: Key Dummy -> Dummy -> Locker It seems to me that your problem decomposes into two problems: 1. A heterogenous store for values of different types. 2. Values with polymorphic instead of monomorphic types. Solution for problem 1 are usually restricted to monomorphic types, but you can work around it. Best regards, Heinrich Apfelmus -- http://apfelmus.nfshost.com

Hi Chris, Maybe this package (from Edward Kmett, surprisingly) could help: http://hackage.haskell.org/package/constraints-0.3.3/docs/Data-Constraint.ht... Considering it kind of reifies the type class constraints, I'm wondering whether you could use this to carry the constraints along the value you're storing? I haven't given it a lot of thoughts for now, but maybe you can get something decent working with this? On Fri, Oct 4, 2013 at 12:41 PM, Heinrich Apfelmus < apfelmus@quantentunnel.de> wrote:
Christopher Done wrote:
On 4 October 2013 10:56, Heinrich Apfelmus
wrote: In particular, the Locker stores arbitrary values like Dynamic , except that values are extracted and removed with the help of a Key . This gets rid of the Typeable constraint.
lock :: Key a -> a -> Locker
I can't pass anything with class constraints to that.
I don't know what "something with a class constraint" means. But I guess you want to pass a value with a *polymorphic* type? This is no problem, but requires impredicative polymorphism:
a = (forall b. Show b => b -> IO ())
lock :: Key (forall b. Show b => b -> IO ()) -> (forall b. Show b => b -> IO ()) -> Locker
Unfortunately, GHC's support for that is a little shaky, but a solution that always works is to put it in a new data type.
data Dummy = Dummy { unDummy :: forall b. Show b => b -> IO () }
lock :: Key Dummy -> Dummy -> Locker
It seems to me that your problem decomposes into two problems:
1. A heterogenous store for values of different types. 2. Values with polymorphic instead of monomorphic types.
Solution for problem 1 are usually restricted to monomorphic types, but you can work around it.
Best regards, Heinrich Apfelmus
-- http://apfelmus.nfshost.com
______________________________**_________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/**mailman/listinfo/haskell-cafehttp://www.haskell.org/mailman/listinfo/haskell-cafe
-- Alp Mestanogullari

| However, I want to write this as a core-to-core | translation as a ghc-plugin. I want the definition go = putStrLn "Hello | World!" to be translated to what I wrote above. Core cannot generate new | names to be exported from a module, so go_ is now gone. Wait... what do you mean "Core cannot generate new names to be exported". I think a core-to-core plugin can certainly generate new top-level function definitions. Maybe you mean that you want your plugin to transform module M( f ) where f = e into module M( f_ ) where f_ = ...f... f = e That seems pretty drastic, because now the programmer's API for the module has changed. Are you sure you don't want to do this module M( f ) wehre f_ = e f = ...f_... by renaming the existing f with some local name. I don't like all this unsafe hackery! Simon
participants (4)
-
Alp Mestanogullari
-
Christopher Done
-
Heinrich Apfelmus
-
Simon Peyton-Jones