Я пишу приложение, использующее строки UTF-16, и чтобы использовать расширение перегруженных строк, я попытался создать для него экземпляр IsString
:
import Data.Word ( Word16 )
import Data.String ( IsString(fromString) )
type String16 = [Word16]
instance IsString [Word16] where
fromString = encodeUTF16
encodeUTF16 :: String -> String16
Проблема в том, что когда я пытаюсь скомпилировать модуль, GHC 7.0.3 жалуется:
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]'
Если я закомментирую объявление экземпляра, он компилируется успешно.
Почему это отклонено? Экземпляр для [ Char]
выглядит примерно так же, но компилируется нормально. Я что-то пропустил?
После просмотра руководств GHC и вики-страницы Haskell (особенно страницы List instance ) у меня появилось лучшее представление о том, как это работает. Вот краткое изложение того, что я узнал:
В отчете Haskell определяется объявление экземпляра следующим образом:
Тип ( T u 1 … u k ) должны принимать форму конструктора типа T , применяемого к переменным простого типа u 1 ,… u k ; более того, T не должен быть синонимом типа , а все u i должны быть различны.
Части, выделенные жирным шрифтом, - это ограничения, которые меня смутили. На английском языке это:
type
), чтобы обойти правило 1. Так как это связано с моей проблемой?
[Word16]
- это просто еще один способ написания [] Word16
. Другими словами, []
является конструктором, а Word16
является его аргументом.
Поэтому, если мы попытаемся написать:
instance IsString [Word16]
, что совпадает с
instance IsString ([] Word16) where ...
, это не сработает, потому что нарушает правило 1, как любезно указывает компилятор вне.
Попытка скрыть его в синониме типа с
type String16 = [Word16]
instance IsString String16 where ...
также не сработает, поскольку нарушает часть 2.
Таким образом, невозможно получить [Word16]
(или список чего-либо , если на то пошло) для реализации IsString
в стандартном Haskell.
Введите ... (пожалуйста, барабанную дробь)
newtype
Решение, предлагаемое @ehird, заключается в том, чтобы заключить его в newtype
:
newtype String16 = String16 { unString16 :: [Word16] }
instance IsString String16 where ...
Он обходит ограничения, потому что String16
больше не является псевдонимом, это новый тип (извините за каламбур)! Единственным недостатком этого является то, что мы должны обернуть и развернуть его вручную, что раздражает.
За счет переносимости мы можем снять ограничение вместе с гибкими экземплярами:
{-# LANGUAGE FlexibleInstances #-}
instance IsString [Word16] where ...
Это было решение, предложенное [Даниэлем Вагнером] .
(Кстати, в итоге я сделал обертку foldl'
вокруг Data.Text.Internal и записал хеш поверх этого.)