Saurabh Nanda
Saurabh Nanda

Reputation: 6793

How to use PackageImports extension to "shadow" a module?

I'm trying to "shadow" the Scotty module using the PackageImports extension.

Use case: For someone using a library X make instrumentedX available. So, simply by changing X to instrumentedX in their cabal file, they can use the instrumented version of the library -- without any other change in code.

Here's the module that I want to export, which has the same name, i.e. Web.Scotty.Trans as the original module exported by Scotty.

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PackageImports #-}

module Web.Scotty.Trans
  (
    module OS
  , get
  , post
  , put
  , delete
  , patch
  , options
  , addroute
  )
where

import qualified "scotty" Web.Scotty.Trans as OS hiding (get, post, put, delete, patch, options, addroute)
import qualified "scotty" Web.Scotty.Trans as S

import InstrumentedCore
import Control.Monad.Trans.Class(lift)

instrumentedAction original action = original action_
  where
    action_ = do
      st <- liftIO $ getCurrentTime
      result <- action
      en <- liftIO $ getCurrentTime
      logInstrumentationData InstrumentationData{instrStart=st, instrEnd=en, instrPayload=Render}
      return result


get route action = instrumentedAction (S.get route) action
post route action = instrumentedAction (S.post route) action
put route action = instrumentedAction (S.put route) action
delete route action = instrumentedAction (S.delete route) action
patch route action = instrumentedAction (S.patch route) action
options route action = instrumentedAction (S.options route) action
addroute method route action = instrumentedAction (S.addroute method route) action

Here's what my cabal file looks like. While my package depends on scotty it uses a different package name for itself, i.e. instrumentedopaleye (don't get into the semantics -- I'm trying to shadow a lot of other modules as well!)

name:                instrumentedopaleye
version:             0.1.0.0
synopsis:            Initial project template from stack
description:         Please see README.md
homepage:            https://github.com/githubuser/dashboard#readme
license:             BSD3
author:              Author name here
maintainer:          [email protected]
copyright:           2016 Author name here
category:            Web
build-type:          Simple
extra-source-files:  README.md
cabal-version:       >=1.10

library
  hs-source-dirs:      src
  exposed-modules:     InstrumentedOpaleye
                     , Instrumented
                     , InstrumentedOpaleye.Internal.PGTypes
                     , InstrumentedOpaleye.Internal.Column
                     , InstrumentedOpaleye.Internal.RunQuery
                     , InstrumentedOpaleye.Internal.HaskellDB.PrimQuery
                     , InstrumentedLucid
                     , Web.Scotty.Trans
                     , InstrumentedCore
  build-depends:       base >= 4.7 && < 5
                     , opaleye
                     , profunctors
                     , product-profunctors
                     , postgresql-simple
                     , mtl
                     , time
                     , stm
                     , text
                     , uuid
                     , lucid
                     , scotty
                     , transformers
  default-language:    Haskell2010

The actual error

And here's what happens when I try to import my version of Web.Scotty.Trans in the GHCi. Given that I have explicitly imported only my version of Web.Scotty.Trans why is GHCi getting confused in resolving the get function? Even :browse Web.Scotty.Trans is getting confused.

Saurabhs-MacBook-Pro:instrumentedopaleye saurabhnanda$ stack exec ghci
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Prelude> :set -fobject-code
Prelude> :set -XPackageImports
Prelude> import "instrumentedopaleye" Web.Scotty.Trans
Prelude Web.Scotty.Trans> :i get

<interactive>:1:1: error:
    Ambiguous interface for ‘Web.Scotty.Trans’:
      it was found in multiple packages:
      scotty-0.11.0 instrumentedopaleye-0.1.0.0
Prelude Web.Scotty.Trans> :browse Web.Scotty.Trans

<no location info>: error:
    Ambiguous module name ‘Web.Scotty.Trans’:
      it was found in multiple packages:
      [email protected] instrumentedopaleye-0.1.0.0@instrumentedopaleye-0.1.0.0-EAOrgrhUGi83yaJJ8gDGX4

Upvotes: 2

Views: 431

Answers (2)

Saurabh Nanda
Saurabh Nanda

Reputation: 6793

I was unable to make this work in GHCi without explicitly hiding scotty as per [crockeea's answer][1]

Also, due to [Haskell's module limitation][2] one cannot re-export a qualified import. Therefore, here's how I managed to solve my original problem of shadowing scotty:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PackageImports #-}

module Web.Scotty.Trans
  (
    module Web.Scotty.Trans -- which is the current module
  , module Web.Scotty.TransOriginal -- the original module MINUS the shadowed methods
  )
where

import Web.Scotty.TransOriginal
import qualified "scotty" Web.Scotty.Trans as S

import InstrumentedCore
import Control.Monad.Trans.Class(lift)

instrumentedAction original action = original action_
  where
    action_ = do
      st <- liftIO $ getCurrentTime
      result <- action
      en <- liftIO $ getCurrentTime
      logInstrumentationData InstrumentationData{instrStart=st, instrEnd=en, instrPayload=Render}
      return result


get route action = instrumentedAction (S.get route) action
post route action = instrumentedAction (S.post route) action
put route action = instrumentedAction (S.put route) action
delete route action = instrumentedAction (S.delete route) action
patch route action = instrumentedAction (S.patch route) action
options route action = instrumentedAction (S.options route) action
addroute method route action = instrumentedAction (S.addroute method route) action

The boilerplate Web.Scotty.TransOriginal which is required only to get around Haskell's limitation around re-exporting qualified imports:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PackageImports #-}

module Web.Scotty.TransOriginal
  (
    module Web.Scotty.Trans
  )
where

import "scotty" Web.Scotty.Trans hiding (get, post, put, delete, patch, options, addroute)

Upvotes: 1

crockeea
crockeea

Reputation: 21811

I'm seeing this behavior even before you do a package-qualified import. For example, if I have both cryptonite and crypto-api installed with stack previously, then

> stack exec ghci
$ :browse Crypto.Random

causes an ambiguous module name error. This is a problem I've run into a lot; my solution is to use the -hide-package flag:

> stack exec ghci
$ :set -hide-package cryptonite
$ :browse Crypto.Random

As an added bonus, you no longer need PackageImports at all: ghci is completely ignoring cryptonite.

Upvotes: 2

Related Questions