Haskell has a special keyword specifically for this case, which is where the newtype pattern described probably comes from.
Type Synonyms
The type keyword in Haskell works exactly as it does in rust, where we use an alias to the type, but the compiler will still type check it the same as the aliased type.
"Learn You a Haskell for Great Good" as the following example:
type String = [Char]
This is called a type synonym because it's just a different name for [Char]
, and it can be used interchangeably. You would use this because you want to give names to concrete types made from higher kinded types, as in the string example, or to give short-hand names to types.
newtype
Again, from "Learn You a Haskell for Great Good":
In Haskell we could wrap a type, much like in rust, by creating the new type with the Data
keyword.
data ZipList a = ZipList [a]
However, creating a new data type has some overhead involved as the compiler has to do extra work compiling a new separate type if we want to use it with the base type, and extra work wrapping and unwrapping that type every time it's converted back to a regular list ([a]
). Instead, in cases where we want to wrap a type we use
newtype ZipList a = ZipList [a]
This is the newtype pattern. You still have to explicitly derive
the typeclasses (Traits) that you want to use for the newtype; however, newtype will type-check differently than the base type, and it allows you the flexibility to specify a different implementation of a typeclass (Trait).
Haskell's typeclasses (Traits) are very flexible and can be interpreted differently for some data types.
For example: the Monoid typeclass.
The main point of interest here is the function mconcat
which can take a list of Monoid values and get a single value from them. However, for certain types this can be interpreted multiple ways: for Boolean values we can think of this as mapping either ||
or &&
on the list, because both take two Boolean values and return one.
newtype Any = Any { getAny :: Bool }
deriving (Eq, Ord, Read, Show, Bounded)
instance Monoid Any where
mempty = Any False
Any x `mappend` Any y = Any (x || y) -- Boolean or
newtype All = All {getAll :: Bool }
deriving (Eq, Ord, Read, Show, Bounded)
instance Monoid All where
mempty = Any False
All x `mappend` All y = All (x && y) -- Boolean and
This lets you modify the behavior of types on the fly as in:
getAny . mconcat . map Any
$ [False, False, False, True]
will return True
getAll . mconcat . map All
$ [False, False, False, True]
will return False
This is a toy example, but it's cool because there isn't any run-time overhead for selecting different typeclass (Trait) implementations for your data.
Note:
I would have provided more links but I'm limited to two.