M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

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型以外のあらゆるリストに適用可能になってしまうという副作用を発生させてしまう。まあ「だからどうした」と開き直るかどうかである。