Yesterday, we looked at newtype deriving in PureScript, which landed in compiler version 0.10.1. Today, we're going to look at the other sort of newtype deriving which was released in the same version, also written by @garyb. That's right - PureScript 0.10.1 supports not just one, but two sorts of built-in newtype class deriving!
Wrapping and unwrapping type classes is very common, but quite tedious, as we saw yesterday. Before the 0.10.1 release, the PureScript core libraries were full of unwrapping functions to accompany newtypes: runAdditive
, runMultiplicative
, runConj
, runDisj
, to name just a few.
In the latest releases, there is a new type class in the purescript-newtype
package to abstract over these wrapping and unwrapping functions:
class Newtype new old | new -> old where
wrap :: old -> new
unwrap :: new -> old
This definition is inspired by the class of the same name in the Haskell newtype
library.
Writing instances for Newtype
is simple, by providing the newtype constructor (e.g. Additive
) and a function to unwrap the constructor (like runAdditive
). But we don't need to write instances by hand - just like for the Eq
and Ord
classes that we saw a couple of days ago, the PureScript compiler will derive these instances automatically:
newtype EmailAddress = EmailAddress String
instance showEmailAddress :: Show EmailAddress where
show (EmailAddress s) = "(EmailAddress " <> show s <> ")"
derive instance newtypeEmailAddress :: Newtype EmailAddress _
Notice that when we derive a Newtype
instance, we only have to provide the first type argument, representing the newtype itself. We don't have to provide the second argument, which represents the type inside the newtype, because the compiler can figure it out. Often, this is quite useful, since the type inside the newtype can be quite large (think of yesterday's monad transformer example).
So wrapping and unwrapping newtypes is all well and good, but what else can we do with the Newtype
class? Well, quite a lot as it turns out. Several useful functions turn out to be special cases of other functions from the core libraries, modulo some newtype wrapping and unwrapping, so the purescript-newtype
library provides generic versions of those combinators.
For example, suppose we want to apply a function on strings to a function on email addresses. The over
function provides a simple way to do this. For example, to upper-case an email address, we can simply lift the toUpper
function on strings:
upperEmail :: EmailAddress -> EmailAddress
upperEmail = over EmailAddress toUpper
Similarly, suppose we want to lift a function on containers of strings to a function on containers of email addresses. overF
allows us to do this. For example, to search for an email address by looking for a domain in the wrapped string:
byDomain :: String -> Array EmailAddress -> Maybe EmailAddress
byDomain domain = overF EmailAddress (find (contains (wrap domain)))
You can try out these examples using Try PureScript, here.
Data.Newtype
is also a good fit for programming with lenses, since we can write a generic lens (actually an Iso
) which works inside any newtype.
So Data.Newtype
is another example of where type class deriving can really help us to reduce the boilerplate code in our PureScript applications, and achieve a good amount of code reuse.