2016-09-13 19 views
0

タプルに基づいて再帰的インスタンス型を作成したいと思います。私が探しているものは、これと似たものです:Haskell再帰型クラス

class Provider a b where 
    getInstance :: a -> b 

instance Provider a b => Provider (x, a) b where 
    getInstance (x, a) = getInstance a 

instance Provider (b, x) b where 
    getInstance (b, _) = b 

tryFunc1 :: Int 
tryFunc1 = 
    let provider = ("test", (10,())) :: (String, (Int,())) 
    in getInstance provider 

tryFunc2 :: String 
tryFunc2 = 
    let provider = ("test", (10,())) :: (String, (Int,())) 
    in getInstance provider 

不幸にも、haskellはインスタンスを解決できません。何らかの理由?

+2

可能であれば、2番目のインスタンスよりも最初のインスタンスを使用することをお勧めします。残念ながら、複数の候補がある場合にどのインスタンスを使用するかを決定しようとすると、GHCは最も具体的なものを特定しようとするためにインスタンスヘッドを調べます。 – Alec

+0

しかし、私が書いたケースでは、tryFunc2では2番目のインスタンスが2番目の型引数としてStringを持つ唯一のものであり、tryFunc1の場合は最初のものと2番目のものの組み合わせ。可能なソリューションは1つのみです。 –

+2

@FernandoRinconハスケルのtypeclassシステムの設計は、(バックトラック)検索をせずに解決策を見つけることができるというユニークな解決策以上のものを保証します。これは効率性と表現力とのトレードオフであり、私の意見では言語にうまく対応しています。一般に、提案されたインスタンスは、たとえそれが一意であっても解決策を見つけるためにコンパイラに検索を要求し、拒否されます。 –

答えて

5

推奨されないOverlappingInstancesプラグマの使用を中止し、インスタンスOVERLAPPINGおよびOVERLAPPABLEプラグマごとに使用を開始することです。ちょうどこの変更により:

instance {-# OVERLAPPABLE #-} Provider a b => Provider (x, a) b where 
    getInstance (x, a) = getInstance a 

instance {-# OVERLAPPING #-} Provider (b, x) b where 
    getInstance (b, _) = b 

私はtryFunc1"test"する10tryFunc2であることを取得します。両方この場合には...また、私は、これはあなたが望む行動であると仮定しますが、ご注意


技術を使用すると、いずれか一方のみOVERLAPPABLEまたはOVERLAPPINGプラグマを必要とするが、私はそれを持っているのは良い習慣であると信じていますこれはちょうどあなたが探しているものは何でもタイプの最初取得する情報の

グッドソース機能の作成を追跡ticketである(そうgetInstance (10, (20,())) :: Int10はない20が私に与えます)。

0

私は一部の人がUndecidableInstancesを好きではないことを知っていますが、これは私がこのような状況でやりたいことです。クローズドtype familyを使用して選択肢が完全に確定的になるようにします。

アイデアは、型のファミリにブール型フラグを計算させて、どのクラスを型変換の仕組みに使うべきかを明確にすることです。無害ですが、Provider a b (AtHead a b) =>という制約のため、UndecidableInstanceの拡張が必要です。

{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FlexibleInstances  #-} 
{-# LANGUAGE FlexibleContexts  #-} 
{-# LANGUAGE UndecidableInstances #-} 
{-# LANGUAGE TypeFamilies   #-} 
{-# LANGUAGE DataKinds    #-} 
{-# LANGUAGE TypeOperators   #-} 
{-# LANGUAGE ScopedTypeVariables #-} 

module Provider where 

import Data.Proxy 
import Data.Type.Equality 

class Provider a b (f :: Bool) where 
    getInstance' :: Proxy f -> a -> b 

type family AtHead x y :: Bool where 
    AtHead (x, a) y = x == y 

instance Provider a b (AtHead a b) => Provider (x, a) b 'False where 
    getInstance' _ (x, a) = getInstance' (Proxy :: Proxy (AtHead a b)) a 

instance Provider (b, x) b 'True where 
    getInstance' _ (b, _) = b 

getInstance :: forall a b. Provider a b (AtHead a b) => a -> b 
getInstance = getInstance' (Proxy :: Proxy (AtHead a b)) 

tryFunc1 :: Int 
tryFunc1 = 
    let provider = ("test", (10,())) :: (String, (Int,())) 
    in getInstance provider 

tryFunc2 :: String 
tryFunc2 = 
    let provider = ("test", (10,())) :: (String, (Int,())) 
    in getInstance provider 
+0

リプレイに感謝します。 Alecの実装と比較して実装のメリットはありますか? –

+0

アレックが使用するプラグマについてはあまりよく分かりませんが、オーバーラップとオーバーラップの区別は、解決策を試す順序を記述する方法と考えています。 2つのケースがあるようです:ベースのもの(オーバーラップ可能)と特殊なもの(オーバーラップ)。 タイプファミリでは、2つの選択肢だけでなく3つ以上の柔軟性が得られます。例えば。 [この例では](https://gist.github.com/gallais/3b7c7d9488b25978caa278bc18617f79)、製品の左コンポーネントだけでなく、コンポーネントも同じであるかどうかをチェックします。 'tryFunc3'はその新しい能力を紹介します。 – gallais