2

以下の関数fは、特定のタイプ'a'に対して、タイプ'c'のパラメーターを取ります。さまざまなタイプの「a」、「c」はさまざまな方法で制限されます。具体的には、「a」が任意の積分型である場合、「c」は任意の「実数」型であることが許可されるべきです。'a'がFloatの場合、'c'はFloatのみになります。

1つの試みは次のとおりです。

{-# LANGUAGE
MultiParamTypeClasses,
FlexibleInstances,
FunctionalDependencies,
UndecidableInstances #-}

class AllowedParamType a c | a -> c

class Foo a where
    f :: (AllowedParamType a c) => c -> a

fIntegral :: (Integral a, Real c) => c -> a
fIntegral = error "implementation elided"

instance (Integral i, AllowedParamType i d, Real d) => Foo i where
    f = fIntegral

何らかの理由で、GHC 7.4.1は、「fIntegralの使用から生じる(Real c)を推測できなかった」と不満を述べています。関数従属性はこの推論を可能にするはずだと私には思えます。この場合、aはiと統合されるため、関数従属性により、dはcと統合される必要があります。この場合、cは「Real」であると宣言されます。ここで何が欠けていますか?

機能従属性はさておき、このアプローチは上記の制限を適用するのに十分表現力がありますか、それともより良い方法がありますか?'a'にはいくつかの異なる値しか使用していないため、次のようなインスタンスがあります。

instance (Integral i, Real c) => AllowedParamType i c
instance AllowedParamType Float Float

ありがとう

4

3 に答える 3

3

おそらくより良い方法は、制約の種類と型族を使用することです(GHC拡張、GHC 7.4が必要だと思います)。これにより、クラスインスタンスの一部として制約を指定できます。

{-# LANGUAGE ConstraintKinds, TypeFamilies, FlexibleInstances, UndecidableInstances #-}

import GHC.Exts (Constraint)

class Foo a where
   type ParamConstraint a b :: Constraint
   f :: ParamConstraint a b => b -> a

instance Integral i => Foo i where
   type ParamConstraint i b = Real b
   f = fIntegral

編集:さらに実験すると、これが期待どおりに機能しないことを意味する微妙な点がいくつかあります。具体的にtype ParamConstraint i b = Real bは、一般的すぎます。私は今解決策を知りません(または解決策が存在するかどうか)。

于 2012-09-13T07:58:09.337 に答える
1

OK、これは私をしつこくしています。多種多様なインスタンスがあるので、全体を調べて、インスタンスの存在以外のソースとターゲットのタイプ間の関係を取り除きましょう。

{-# LANGUAGE OverlappingInstances, FlexibleInstances,TypeSynonymInstances,MultiParamTypeClasses #-}

class Foo a b where f :: a -> b

これで、タイプのペアをそれらの間に一致させることができます。fたとえば、次のようになります。

instance Foo Int Int where f = (+1)
instance Foo Int Integer where f = toInteger.((7::Int) -)
instance Foo Integer Int where f = fromInteger.(^ (2::Integer))
instance Foo Integer Integer where f = (*100)
instance Foo Char Char where f = id
instance Foo Char String where f = (:[])  -- requires TypeSynonymInstances
instance (Foo a b,Functor f) => Foo (f a) (f b) where f = fmap f -- requires FlexibleInstances
instance Foo Float Int where f = round
instance Foo Integer Char where f n = head $ show n

No instance for...これは、Ambiguous typeエラーメッセージを回避するための多くの明示的な型注釈を意味します。たとえば、できませんがmain = print (f 6)、できますmain = print (f (6::Int)::Int)

必要な標準タイプのすべてのインスタンスを一覧表示できます。これにより、非常に多くの繰り返しが発生する可能性があります。青いタッチペーパーに火をつけて、次のことを実行できます。

instance Integral i => Foo Double i where f = round -- requires FlexibleInstances
instance Real r => Foo Integer r where f = fromInteger -- requires FlexibleInstances

注意:これは、「整数型がある場合は、この便利なラウンド関数を使用して無料でインスタンスを作成できる」という意味ではなく、「任意の型を使用するたびに、間違いなくインスタンスになる 」という意味です。ちなみに、私はこれに使っているので、あなたのタイプがそうでない限り、私たちは脱落するでしょう。」たとえば、これはインスタンスにとって大きな問題です。iFoo Double iiFoo Double iroundiIntegralFoo Integer Char

これにより、他のインスタンスが簡単に壊れる可能性があるため、ここで入力f (5::Integer) :: Integerすると、

Overlapping instances for Foo Integer Integer
  arising from a use of `f'
Matching instances:
  instance Foo Integer Integer
  instance Real r => Foo Integer r

プラグマを変更して、OverlappingInstancesを含めることができます。

{-# LANGUAGE OverlappingInstances, FlexibleInstances,TypeSynonymInstances,MultiParamTypeClasses #-}

つまり、500が返されるので、より具体的なインスタンスf (5::Integer) :: Integerを使用していることは明らかです。Foo Integer Integer

この種のアプローチは、多くのインスタンスを手作業で定義し、標準型クラスからインスタンスを作成するときに完全にワイルドになる時期を慎重に検討することで、うまくいくと思います。(あるいは、それほど多くの標準タイプはありません。そして、私たち全員が知っているnotMany choose 2 = notIntractablyManyように、、あなたはそれらすべてをリストすることができます。)

于 2012-09-18T20:16:46.427 に答える
0

これは、具体的な問題ではなく、より一般的な問題を解決するための提案です(最初に詳細が必要です。後で確認することをお約束します)。他の人があなたと同じような問題の解決策を探している場合に備えて書いています。私がSOを発見する前は、確かに過去のことでした。SOは、根本的に新しいアプローチを試すのに役立つ場合に特に優れています。

私は以前は仕事の習慣がありました:

  1. マルチパラメータ型クラスを導入します(型はいたるところにぶら下がっているので...)
  2. 機能従属性を導入します(それを片付ける必要がありますが、それから私は必要になります...)
  3. FlexibleInstancesを追加します(アラームベルが鳴り始めます。コンパイラがデフォルトでこれをオフにする理由があります...)
  4. UndecidableInstancesを追加します(GHCは、設定している課題に対応しているとは確信していないため、自分でいると言っています。)
  5. すべてが爆破します。どういうわけかリファクタリング。

それから私は型族の喜びを発見しました(型の関数型プログラミング(hooray)-マルチパラメーター型クラスは(少し似ている)型の論理プログラミングです)。私のワークフローは次のように変更されました。

  1. 関連する型を含む型クラスを導入します。つまり、置換します。

    class MyProblematicClass a b | a -> b where
      thing :: a -> b
      thang :: b -> a -> b
    

    class MyJustWorksClass a where
      type Thing a :: * -- Thing a is a type (*), not a type constructor (* -> *)
      thing :: a -> Thing a
      thang :: Thing a -> a -> Thing a
    
  2. 神経質にFlexibleInstancesを追加します。何も問題はありません。

  3. 場合によっては、ghcを支援する代わり(MyJustWorksClass j,j~a)=>に、(MyJustWorksClass a)=>または(Show t,t ~ Thing a,...)=>代わりにのような制約を使用して問題を修正します。(Show (Thing a),...) =>~基本的に'は'と同じタイプであることを意味します)
  4. 神経質にFlexibleContextsを追加します。何も問題はありません。
  5. すべてが機能します。

「何も問題がない」理由は、ghcが、関数があり、それを実行できるはずであるという単なるアサーションの束を使用してタイプを推測しようとするのではなく、タイプ関数を使用してタイプを計算するためです。Thing aThang

試してごらん!マニュアルを読む前に、タイプ関数の楽しみを 読んでください!

于 2012-09-13T15:37:57.420 に答える