After having a look through the GHC manuals and around the Haskell wiki (especially the List instance page), I've got a better idea of how this works. Here's a summary of what I've learned:
Problem
The Haskell Report defines an instance declaration like this:
The type (T u1 … uk) must take the form of a type constructor T applied to simple type variables u1, … uk; furthermore, T must not be a type synonym, and the ui must all be distinct.
The parts highlighted in bold are the restrictions that tripped me up. In English, they are:
- Anything after the type constructor must be a type variable.
- You can't use a type alias (using the
type
keyword) to get around rule 1.
So how does this relate to my problem?
[Word16]
is just another way of writing [] Word16
. In other words, []
is the constructor and Word16
is its argument.
So if we try to write:
instance IsString [Word16]
which is the same as
instance IsString ([] Word16) where ...
it won't work, because it violates rule 1, as the compiler kindly points out.
Trying to hide it in a type synonym with
type String16 = [Word16]
instance IsString String16 where ...
won't work either, because it violates part 2.
So as it stands, it is impossible to get [Word16]
(or a list of anything, for that matter) to implement IsString
in standard Haskell.
Enter... (drumroll please)
Solution #1: newtype
The solution @ehird suggested is to wrap it in a newtype
:
newtype String16 = String16 { unString16 :: [Word16] }
instance IsString String16 where ...
It gets around the restrictions because String16
is no longer an alias, it's a new type (excuse the pun)! The only downside to this is we then have to wrap and unwrap it manually, which is annoying.
Solution #2: Flexible instances
At the expense of portability, we can drop the restriction altogether with flexible instances:
{-# LANGUAGE FlexibleInstances #-}
instance IsString [Word16] where ...
This was the solution @[Daniel Wagner] suggested.
Solution #3: Equality constraints
Finally, there's an even less portable solution using equality constraints:
{-# LANGUAGE TypeFamilies #-}
instance (a ~ Word16) => IsString [a] where ...
This works better with type inference, but is more likely to overlap. See Chris Done's article on the topic.
(By the way, I ended up making a foldl'
wrapper around Data.Text.Internal and writing the hash on top of that.)