I have a newtype that needs to satisfy some validation logic in order to be constructed. This is why I don’t want to export the constructor outside of the module. But I also want to use the newtype with functions that accept the wrapped value. To give a simplified example, I have in one module:
module Username (Username, validateUsername) where
newtype Username = Username String
validateUsername :: String -> Maybe Username
and in another module
module StringStuff where
doStringStuff :: String -> Effect Unit
What options do I have for doing the closest to doStringStuff (username :: Username)
?
Some things I’ve looked into so far:
-
derive instance Newtype Username _
gives meunwrap
which is nice, but it also gives mewrap
which allows me to circumvent the validation function, which is not nice. -
derive newtype instance Coercible String Username
doesn’t work since compiler complains that the constructor needs to be in scope when usingcoerce
. - Manually writing
toString :: Username -> String
in the newtype module works, but if I have a number of other newtypes, it’s slightly ugly having all thesetoString
,toInt
etc. functions. - Writing my own
class Unwrappable t a | t -> a where unwrap :: t -> a
, implementing it forUsername
and other newtypes and then havingdoStringStuff :: forall t. Unwrappable t String => t -> Effect Unit
. This is the nicest solution, though I’m somewhat reluctant to roll a type class just for this.
Is there another option I’m missing? What have you tried in your code for a similar problem?