9

次のコード例を検討してください。

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-} -- Is there a way to avoid this?

-- A generic class with a generic function.
class Foo a where
  foo :: a -> a

-- A specific class with specific functions.
class Bar a where
  bar :: a -> a
  baz :: a -> a

-- Given the specific class functions, we can implement the generic class function.
instance Bar a => Foo a where
  foo = bar . baz

-- So if a type belongs to the specific class...
instance Bar String where
  bar = id
  baz = id

-- We can invoke the generic function on it.
main :: IO ()
main =
  putStrLn (foo "bar")

(私の実際のコードはもっと複雑です。これは、パターンを示すための最小限の煮詰めたケースです。)

ここで が必要な理由は明確ではありませんUndecidableInstances。型パラメーターaは の両側に 1 回出現するBar a => Foo aため、「うまく機能する」と期待していました。ここで明らかに何かが欠けています。しかし、とにかく、を使用せずにこれを行う方法はありUndecidableInstancesますか?

4

2 に答える 2

8

いくつかの方法があります。どちらが最も適切かを判断するのに十分なコンテキストを提供していないと思います. GHC-7.4 を使用している場合は、このDefaultSignatures拡張機能を試してみてください。

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DefaultSignatures #-}

-- A generic class with a generic function.
class Foo a where
  foo :: a -> a
  default foo :: Bar a => a -> a
  foo = bar . baz

-- A specific class with specific functions.
class Bar a where
  bar :: a -> a
  baz :: a -> a

instance Bar String where
  bar = id
  baz = id

instance Foo String

main :: IO ()
main =
  putStrLn (foo "bar")

型が のインスタンスであることを宣言する必要がありますがFoo、既定の実装が使用されるため、メソッド宣言を繰り返す必要はありません。

別のかなり軽量なアプローチは、newtype を使用することです。インスタンスを必要とする関数がある場合は、インスタンスを newtype でFooラップできます。Bar

newtype FooBar a = FooBar { unFooBar :: a }

instance Bar a => Foo (FooBar a) where
    foo = FooBar . bar . baz . unFooBar

-- imported from a library or something...
needsFoo :: Foo a => a -> b

myFunc = needsFoo (FooBar someBar)

fooまたは、通常の関数に置き換えるか、Barインスタンス用に特化したバージョンを作成することでうまくいく場合があります。

-- if every `Foo` is also a `Bar`, you can just do this.  No need for `Foo` at all!
foo :: Bar a => a -> a
foo = bar . baz

-- if most `Foo`s aren't `Bar`s, you may be able to use this function when you have a `Bar`
fooBar :: Bar a => a -> a
foo = bar . baz

これらは、状況に適している場合、おそらく最良の解決策です。

もう 1 つのオプションは、すべてのFooインスタンスを手動で宣言することです。考えられるさまざまなインスタンスが多数存在する可能性がありますが、実際に使用されるインスタンスがほんの一握りしかないコードベースはかなり一般的です。ここでそれが本当なら、より一般的な解決策を実装しようとするよりも、必要な 3 つまたは 4 つのインスタンスを書き出す方がおそらく簡単です。

最後の手段として、元のコードのようなものを使用できますが、OverlappingInstancesそれを機能させる必要もあります ( が必要OverlappingInstancesない場合は、Fooクラスは必要ありません)。これは、使用可能な一致が複数ある場合に、GHC が「最も具体的なインスタンス」を選択できるようにする拡張機能です。これは多かれ少なかれ機能しますが、期待どおりにならない場合があります。

class Foo a where
  foo :: a -> a

class Bar a where
  bar :: a -> a
  baz :: a -> a

instance Bar String where
  bar = id
  baz = id

instance Bar a => Foo a where
  foo = bar . baz

instance Foo [a] where
  foo _ = []

main :: IO ()
main =
  print (foo "foo")

main空の文字列を出力するようになりました。と の 2 つのFooインスタンスがaあります[a]。後者はより具体的であるため、おそらく前者が必要だったとしてもfoo "foo"、文字列には type があるため、後者が選択されます。[Char]だから今、あなたも書く必要があります

instance Foo String where
  foo = bar . baz

その時点で、Bar a => Foo aインスタンスを完全に除外することもできます。

于 2012-07-29T09:12:52.797 に答える
0

上記の回答に加えて。ライブラリData.Traversableからのモジュールで使用される政治は魅力的です。base要するに、ライブラリにジェネリック インスタンスを与えると、エンド ユーザーはあなたの決定を受け入れざるを得なくなり、これが常に最善の方法とは限りません。Data.Traversableデフォルトの実装を提供する のような関数が含まれていますfoldMapDefaultが、特定の実装の決定は依然としてユーザー次第です。

于 2012-07-29T18:26:31.770 に答える