instance MyKlass [String] where... はNG
JavaScriptなどで見られるArray#join(String) みたいな関数がほしくて手習いついでにコーディングしていたら、面白いことを知った。
module Study.Join(join) where import Data.ByteString.Char8 as B import Data.ByteString.Lazy.Char8 as L import Prelude as P import Data.Text as T class Join a where join :: a -> [a] -> a {-- このインスタンス宣言はコンパイルエラー instance Join [String] where join _ [] = [] join d (x:xs) = (if P.length xs > 0 then x ++ d else x) ++ join d xs --} {-- そしてこれならOK --} instance Join [a] where join _ [] = [] join d (x:xs) = (if P.length xs > 0 then x ++ d else x) ++ join d xs instance Join B.ByteString where join _ [] = B.empty join d (x:xs) = (if P.length xs > 0 then x `B.append` d else x) `B.append` join d xs instance Join L.ByteString where join _ [] = L.empty join d (x:xs) = (if P.length xs > 0 then x `L.append` d else x) `L.append` join d xs instance Join Text where join _ [] = T.empty join d (x:xs) = (if P.length xs > 0 then x `T.append` d else x) `T.append` join d xs
Join ByteString や Join Textはおまけなのであまり気にしないように……。問題は instance Join [String] where ... (あるいは instance Join [Char] where ... とか instance Join ([] Char) where ... )がコンパイルエラーとなるということ。
Study/Join.hs:11:10: Illegal instance declaration for `Join [String]' (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 `Join [String]'
これはようするにインスタンス宣言ではリストの要素は型変数でないといけないということらしい。ふむ。わかるようなわからぬような……。抽象型から生成された具象型一般に言えることなのだろうか? いずれ検証してみよう。
それはそれとしてコンパイルエラーに対する解決策は2つ。1つは TypeSynonymInstances 言語拡張を使うこと。もう一つは instance Join [a] where... というインスタンス宣言でよしとすること。いずれにしてもコンパイルが通り、晴れて String([Char]) や ByteString が Join クラスのインスタンスになる。
……が、もちろん後者の方法をとると、join 関数は意図せずString型以外のあらゆるリストに適用可能になってしまうという副作用を発生させてしまう。まあ「だからどうした」と開き直るかどうかである。