Haskell: perform an IO action wrapped in Data.Dynamic

Suppose I have a Data.Dynamic.Dynamic object that wraps an IO action (that is, something like IO a for some, possibly unknown a ). I feel that I have to perform this I / O action and get its result wrapped in Dynamic (which will be of type a ). Is there a standard library function that does this? (Something like dynApply , but for I / O performance instead of application-to-application).

A function implementation might look something like this:

 dynPerform :: Dynamic -> Maybe IO Dynamic dynPerform (Dynamic typ act) = if (typeRepTyCon typ) /= ioTyCon then Nothing else Just $ do result <- (unsafeCoerce act :: IO Any) return Just . Dynamic (head $ typeRepArgs typ) $ result exampleIOAction = putChar typeOfIOAction = typeOf exampleIOAction ioTyCon = typeRepTyCon typeOfIOAction 

but it is obvious that this uses several unsafe operations, so I would rather get it out of the library. (Actually, what I wrote will not work outside of Data.Dynamic due to the opacity of the Data.Dynamic.Dynamic type.)

+6
source share
1 answer

I do not believe that you can safely do what you are trying to do. Let me suggest an alternative approach.


Perhaps phantom types can help you here. Suppose you provide some kind of cron job service where the user performs an action every x microseconds, and the user can request at any time to see the result of the last run of this action.

Suppose you have access to the following primitives:

 freshKey :: IO Key save :: Key -> Dynamic -> IO () load :: Key -> IO (Maybe Dynamic) 

You must plan tasks and plan to save the results, while you still “know” in the type system what type of action.

 -- do not export the internals of PhantomKey data PhantomKey a = PhantomKey { getKey :: Key getThread :: Async () } -- This is how your user acquires phantom keys; -- their phantom type is tied to the type of the input action schedule :: Typeable a => Int -> IO a -> IO (PhantomKey a) schedule microseconds m = do k <- freshKey let go = do threadDelay microseconds a <- m save k (toDyn a) go thread <- async go return $ PhantomKey k thread unschedule :: PhantomKey a -> IO () unschedule pk = cancel (getThread pk) -- This is how your user uses phantom keys; -- notice the function result type is tied to the phantom key type peekLatest :: PhantomKey a -> IO (Maybe a) peekLatest pk = load (getKey pk) >>= \md -> case md of Nothing -> return Nothing -- Nothing stored at this key (yet?) Just dyn -> case fromDynamic dyn of Nothing -> return Nothing -- mismatched data type stored at this key -- hitting this branch is probably a bug Just a -> return (Just a) 

Now, if I am a user of your API, I can use it with my own data types, which you know nothing about, if they are Typeable:

 refreshFoo :: IO Foo main = do fooKey <- schedule 1000000 refreshFoo -- fooKey :: PhantomKey Foo mfoo <- peekLatest fooKey -- mfoo :: Maybe Foo 

So what have we achieved?

  • Your library takes an IO user action and executes it at arbitrary points in time
  • Your library saves your user data through Dynamic blobs
  • Your library loads your user data through dynamic drops

All this if your library does not know anything about your user data types.

It seems to me that if you put something that you know is an I / O action in a dynamic blob, you have lost information in the type system about this thing in context, when instead you should use the specified type information, TypeRep can get the information about type at the level of values, but (as far as I know) cannot bring this information to the level of type.

+2
source

Source: https://habr.com/ru/post/982721/


All Articles