Using newtype deriving and purescript-newtype it’s possible to work with newtypes without a lot of the boilerplate.
Newtype deriving
newtype EmailAddress = EmailAddress String
instance eqEmailAddress :: Eq EmailAddress where
eq (EmailAddress s1) (EmailAddress s2) = eq s1 s2
-- Thanks to newtype deriving, we can replace it with:
derive newtype instance eqEmailAddress :: Eq EmailAddress
Any instance can be derived for any newtype, as long as there is an instance for the same class for the underlying type.
Data.Newtype
Inside of Data.Newtype
there are a lot of helper functions that make working with newtypes much easier.
Fist we have to derive a Newtype instance. We only have to provide the first type argument, representing the newtype itself.
derive instance newtypeEmailAddress :: Newtype EmailAddress _
Now we have access to:
wrap
andunwrap
:
unwrap (EmailAddress "foo") -- "foo"
wrap "foo" :: EmailAddress -- EmailAddress "foo"
over
: lifts a function to operate over a newtype, similary to howmap
works withFunctor
.
upperEmailAddress :: EmailAddress -> EmailAddress
upperEmailAddress = over EmailAddress toUpper
over2
: the same, but for binary operations.
concatEmails :: EmailAddress -> EmailAddress -> EmailAddress
concatEmails = over2 EmailAddress (<>)
overF
andoverF2
: they are similar toover
andover2
, but the lifted function operates on values in aFunctor
.
byDomain :: String -> Array EmailAddress -> Maybe EmailAddress
byDomain domain = overF EmailAddress (find (contains (wrap domain)))
under
,underF
,under2
andunderF2
: they are the opposite of theover
functions. They lower a function that operates on a newtype to work on the wrapped value instead.
It’s important to note that in all these function (under
and over
variants) the Newtype
is polymorphic, so the return type can be a different newtype (but with the same underlying type).
newtype Degrees = Degrees Number
derive instance newtypeDegrees :: Newtype Degrees _
newtype NormalDegrees = NormalDegrees Number
derive instance newtypeNormalDegrees :: Newtype NormalDegrees _
normaliseDegrees :: Degrees -> NormalDegrees
normaliseDegrees (Degrees deg) = NormalDegrees (deg % 360.0)
asNormalDegrees :: Number -> Number
asNormalDegrees = under Degrees normaliseDegrees -- this works because the `Newtype` is polymorphic
ala
: it’s used when you have a higher order function that you want to use in the context of some newtype.
ala additive foldMap [1, 2, 3, 4] -- 10
alaF
: useful for cases where you want to use an additional projection with the higher order function.
alaF Additive foldMap length ["hello", "world"] -- 10
traverse
: traverses over a newtype.
traverse Degrees (\x -> Just (x * 2.0)) (Degrees 1.0) -- Just (Degrees 2.0)