代数型データコンストラクターの「パターンマッチング」
-
26-09-2019 - |
質問
多くのコンストラクターを持つデータ型を考えてみましょう。
data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int
同じコンストラクターで 2 つの値が生成されるかどうかを確認する関数を作成したいと思います。
sameK (Alpha _) (Alpha _) = True
sameK (Beta _) (Beta _) = True
sameK (Gamma _ _) (Gamma _ _) = True
sameK _ _ = False
メンテナンス sameK
あまり楽しいものではありませんし、正確性を簡単にチェックすることもできません。たとえば、新しいコンストラクターが追加された場合、 T
, 、更新を忘れがちです。 sameK
. 。例を示すために 1 行省略しました。
-- it’s easy to forget:
-- sameK (Delta _) (Delta _) = True
問題は、定型文をどのように回避するかです。 sameK
?または、すべてをチェックすることを確認する方法 T
コンストラクター?
私が見つけた回避策は、コンストラクターごとに個別のデータ型を使用して、 Data.Typeable
, 、そして共通の型クラスを宣言していますが、この解決策はあまり好きではありません。なぜなら、この解決策ははるかに読みにくく、それ以外の場合は単純な代数型だけで十分だからです。
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Typeable
class Tlike t where
value :: t -> t
value = id
data Alpha = Alpha Int deriving Typeable
data Beta = Beta Int deriving Typeable
data Gamma = Gamma Int Int deriving Typeable
data Delta = Delta Int deriving Typeable
instance Tlike Alpha
instance Tlike Beta
instance Tlike Gamma
instance Tlike Delta
sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool
sameK a b = typeOf a == typeOf b
解決
Data.Data モジュールを見てください。 toConstr
特に機能。とともに {-# LANGUAGE DeriveDataTypeable #-}
これにより、インスタンスである任意の型に対して機能する 1 行のソリューションが得られます。 Data.Data
. 。SYB のすべてを理解する必要はありません。
何らかの理由 (Hugs に行き詰まった?) でそれができない場合は、非常に醜くて非常に遅いハックを次に示します。データ型が次の場合にのみ機能します。 Show
できる(例:を使用して deriving (Show)
- これは、たとえば、内部に関数型がないことを意味します)。
constrT :: T -> String
constrT = head . words . show
sameK x y = constrT x == constrT y
constrT
の最も外側のコンストラクターの文字列表現を取得します。 T
それを見せて言葉に切り刻み、最初の言葉を取得することで価値を高めます。他の型でそれを使用する (そして単型性の制限を回避する) ことを避けるために、私は明示的な型署名を与えています。
いくつかの注目すべき欠点:
- 型に中置コンストラクター (次のような) がある場合、これはひどく壊れます。
data T2 = Eta Int | T2 :^: T2
) - 一部のコンストラクターに共有プレフィックスがある場合、文字列の大部分を比較する必要があるため、処理が遅くなります。
- カスタムのあるタイプでは機能しません
show
, 、多くのライブラリタイプなど。
そうは言っても、それは は ハスケル98…でも、それについて私が言える唯一の良い点はそれくらいです!
他のヒント
他の可能な方法:
sameK x y = f x == f y
where f (Alpha _) = 0
f (Beta _) = 1
f (Gamma _ _) = 2
-- runtime error when Delta value encountered
ランタイムエラーが理想的ではありませんが、静かに間違った答えを与えるよりも優れています。
あなたは、一般的にこれを行うにはジェネリックスクラップのようなライブラリあなたのボイラープレートまたはユニプレートを使用する必要があります。
:あなたはとても重い利きにしたくない場合は、、あなたは一緒に空のレコードのショートカットで、デイブヒントンのソリューションを使用することができます
...
where f (Alpha {}) = 0
f (Beta {}) = 1
f (Gamma {}) = 2
あなたは、各コンストラクタが持っているどのように多くの引数を知っている必要はありませんので。しかし、それは明らかにまだ希望する何かを残しています。
いくつかのケースでは、「スクラップあなたの定型」ライブラリ意志の助けます。
あなたは間違いなく、定型を排除するためにジェネリックを使用することができます。あなたのコードは、なぜ私(および他の多くのののトップレベルに_
のワイルドカードを使用することはありません)教科書の例です。それはすべてのケースを書き出す面倒ですが、それはバグに対処するよりも少ない退屈です。
この幸せな例では、私はデイブヒントンのソリューションを使用するだけでなく、補助機能f
にINLINEプラグマを平手打ちでしょう。