I’m writing an application that uses UTF-16 strings, and to make use of the overloaded strings extension I tried to make an IsString instance for it:
import Data.Word ( Word16 )
import Data.String ( IsString(fromString) )
type String16 = [Word16]
instance IsString [Word16] where
fromString = encodeUTF16
encodeUTF16 :: String -> String16
The problem is, when I try to compile the module, GHC 7.0.3 complains:
Data/String16.hs:35:10:
Illegal instance declaration for `IsString [Word16]'
(All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
Use -XFlexibleInstances if you want to disable this.)
In the instance declaration for `IsString [Word16]'
If I comment out the instance declaration, it compiles successfully.
Why is this rejected? The instance for [Char] looks pretty much like the same thing, yet it compiles fine. Is there something I’ve missed?
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 parts highlighted in bold are the restrictions that tripped me up. In English, they are:
typekeyword) 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 andWord16is its argument.So if we try to write:
which is the same as
it won’t work, because it violates rule 1, as the compiler kindly points out.
Trying to hide it in a type synonym with
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 implementIsStringin standard Haskell.Enter… (drumroll please)
Solution #1:
newtypeThe solution @ehird suggested is to wrap it in a
newtype:It gets around the restrictions because
String16is 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:
This was the solution @[Daniel Wagner] suggested.
Solution #3: Equality constraints
Finally, there’s an even less portable solution using equality constraints:
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.)