Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What solutions are available if I apparently want to access to a hidden data constructor?

Tags:

haskell

I'm using a package that carries around things in a convenient ReaderT monad. Their runReaderT/unwrapping function is in a hidden module. I'd like to be able to unwrap it arbitrariy/manually with an arbitrary environment. So I don't have any qualms with doing it manually -- however, the environment type has a hidden data constructor, so I can't generate an environment that will typecheck with runReaderT.

Is there any way I can get around this? Or should I give this up as impossible and attempt to restructure my program? I'm planning on deploying this project to a remote system with an automated build system so I'm not sure if I want to edit the source of the package directly/fork it.

In specific, I'm using scotty, and I'm trying to emulate runActionM from the Web.Scotty.Action module. I trying to emulate a page request and capture the results, and also to emulate a page request that normally has IO side effects.

Specific example:

-- LibraryPackage.hs
module LibraryPackage (SpecialReader) where

import Control.Monad.Reader

type SpecialReader a = Reader Env a
data Env = Env Int

runSpecialReader r = runReader r (Env 5)


-- Main.hs
import LibraryPackage

main = do
  let
    r = return 10 :: SpecialReader Int

  -- problem here -- cannot construct an `Env`
  print $ runReader r (Env 5)
like image 959
Justin L. Avatar asked Dec 09 '22 13:12

Justin L.


2 Answers

You're pretty well and truly stuck. Here are the options

  1. Talk to the package maintainer and see if there's another solution
  2. Use undefined if you don't actually need meaningful values
  3. Don't use this version of the library (cut and paste, or fork in very extreme cases)
  4. If this is just a one-off, you could sacrifice a goat and try unsafeCoerce

Haskell doesn't let you do things like creating types your not supposed to. Imagine what cause there would be if you could pattern match on IO for example.

like image 52
Daniel Gratzer Avatar answered May 12 '23 23:05

Daniel Gratzer


Many packages use the module system to provide safety guarantees or optimizations that would be impossible if the hidden objects were able to be used in arbitrary ways by client code (the most obvious example being IO!). The module system is designed to make it impossible to do what you're trying to do.

However, many packages also have "secret" exposed modules (often with Internal in the name) that export the hidden names anyway. These often won't show up in the official docs, but you'll see them in the source/cabal files.

If Scotty is such a package then you're in luck, but using an internal module is tricky. Treat them like the unsafe* family of functions; in general they cause things to go horribly wrong, and the onus is on you to make sure it won't in this particular case. After all, the constructor for Env may be deliberately hidden specifically to stop you running the monad stack on arbitrary environments because that would go wrong. Also, the package author is likely to not consider breaking changes to the internal module to be "backwards incompatible", so you may have to do forward ports of your code even on minor version updates to Scotty.

If there really is no module exporting the required constructor at all (not even a unadvertised one), then there's no way to get at it without changing the package's source code. You can ask for it to be exposed (publicly or as a back door), or fork the package, or try to redesign your code so you don't need it.

like image 29
Ben Avatar answered May 12 '23 21:05

Ben