Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hide a constructor but not the type on import

Tags:

module

haskell

I've got an internal module I'd like to provide an external API for

module Positive.Internal where

newtype Positive a = Positive { getPositive :: a }
  deriving (Eq, Ord)

-- smart constructor
toPositive :: (Num a, Ord a) => a -> Maybe (Positive a)
toPositive a | a <= 0    = Nothing
             | otherwise = Just $ Positive a
-- ...

I want to hide the dumb constructor, and replace it with a unidirectional pattern so users can still pattern match values, they just have to use the smart constructor to use new values.

Since I want the pattern and the dumb constructor to use the same name, I need to hide the dumb constructor to prevent namespace clashes.

However, since the dumb constructor and the type share names, it's a little tricky to import the everything BUT the dumb constructor.

Currently I'm doing this, which works ok:

{-# LANGUAGE PatternSynonyms #-}
module Positive
  ( module Positive.Internal, pattern Positive
  ) where

import Positive.Internal (Positive())
import Positive.Internal hiding (Positive)
import qualified Positive.Internal as Internal

pattern Positive :: a -> Positive a
pattern Positive a <- Internal.Positive a

I could simplify my imports by just using the qualified import, but I'm curious.

Is there a way to, in a single import statement, import all of Positive.Internal except the dumb constructor?

I tried hiding (Positive(Positive)), but that hid both the type and the dumb constructor. I've poked about the wiki, but I haven't noticed any way to differentiate between constructors and types in hiding lists.

like image 539
rampion Avatar asked Apr 25 '17 15:04

rampion


Video Answer


1 Answers

Correct me if I am wrong, but I am almost certain this is what you are looking for:

{-# LANGUAGE PatternSynonyms #-}
module Positive
  ( module Positive.Internal, pattern Positive, foo
  ) where

import Positive.Internal hiding (pattern Positive)
import qualified Positive.Internal as Internal (pattern Positive)

pattern Positive :: a -> Positive a
pattern Positive a <- Internal.Positive a

foo :: Positive Int
foo = Internal.Positive 5

Internal module stays the same way as it is defined so far. And for the sake of example:

module Negative where

import Positive

bar :: Maybe Int
bar = getPositive <$> toPositive 6

Let's double check in GHCi:

Prelude> :load Negative
[1 of 3] Compiling Positive.Internal ( Positive/Internal.hs, interpreted )
[2 of 3] Compiling Positive         ( Positive.hs, interpreted )
[3 of 3] Compiling Negative         ( Negative.hs, interpreted )
Ok, modules loaded: Negative, Positive, Positive.Internal.
*Negative> bar
Just 6
*Negative> getPositive foo
5
*Negative> :i Positive
newtype Positive a = Positive.Internal.Positive {getPositive :: a}
    -- Defined at Positive/Internal.hs:3:1
instance [safe] Ord a => Ord (Positive a)
  -- Defined at Positive/Internal.hs:4:17
instance [safe] Eq a => Eq (Positive a)
  -- Defined at Positive/Internal.hs:4:13
*Negative> :t Positive

<interactive>:1:1: error:
    • non-bidirectional pattern synonym ‘Positive’ used in an expression
    • In the expression: Positive
like image 115
lehins Avatar answered Oct 14 '22 02:10

lehins