4

Gooたとえば、C++、Java、C# などの言語で同等のインターフェイスであると主張されることが多い、私の型クラスを考えてみましょう。

class Goo goo where ham :: goo -> String

data Hoo = Hoo
instance Goo Hoo where ham _ = "Hoo!"
                       mustard _ = "Oh oh."

data Yoo = Yoo
instance Goo Yoo where ham _ = "Yoo!"
                       mustard _ = "Whew"

しかし、私は返すことができませんGoo:

paak :: (Goo goo) => String -> goo
paak g = (Yoo)

-- Could not deduce (goo ~ Yoo)
-- from the context (Goo goo)
--  bound by the type signature for paak :: Goo goo => String -> goo
--  at frob.hs:13:1-14
--  `goo' is a rigid type variable bound by
--        the type signature for paak :: Goo goo => String -> goo
--        at frob.hs:13:1
-- In the expression: (Yoo)
-- In an equation for `paak': paak g = (Yoo)

この啓発的な声明を見つけました。これは、その理由を説明しています。

paak :: (Goo goo) => String -> gooは、関数が必要なものを返す可能性があることを意味するものではありませんGooGooこれは、関数がユーザーが望むものを返すことを意味します。

(ここでsepp2kの回答から音訳)

しかし、では、Goo制約を満たすが、、、、またはその他の何かを返すまたは保存するにはどうすればよいでしょうか?HooYooMooBooGoo

私は自分のプログラミングのバックグラウンドに巻き込まれすぎて、C のようなインターフェイスに頼るなど、まったく別の考え方をする必要があるのでしょうか。

data WhewIamAGoo = WhewIamAGoo {
    ham' :: String
    mustard' :: String
}

paak :: String -> WhewIamAGoo
paak g = let yoo = Yoo 
         in WhewIamAGoo { ham' = ham yoo
                          mustard' = mustard ham
                        }

しかし、それは厄介なようです。

私の特定のケースでは、次のように使用したいと思いGooます。

let x = someGoo ....
in ham x ++ mustard x

Yooつまり、呼び出し元はすべてのs などについて知る必要はありません。


編集:明確にするために:Haskellプログラマーがそのような状況に陥る方法を探しています。慣用的な方法でそれをどのよう処理しますか?

4

5 に答える 5

8

この問題を解決するには、慣用的な Haskell と考える方法が 2 つあります。

  1. 代数データ型

    data Goo = Hoo | Yoo
    
    ham Hoo = "Hoo!"
    ham Yoo = "Yoo!"
    
    mustard Hoo = "Oh oh."
    mustard Yoo = "Whew"
    

    長所: 新しい操作を簡単に追加できる
    短所: 新しい「タイプ」を追加するには、多くの既存の関数を変更する必要がある可能性があります

  2. 対応実績

    data Goo = Goo { ham :: String, mustard :: String }
    
    hoo = Goo { ham = "Hoo!", mustard = "Oh oh." }
    yoo = Goo { ham = "Yoo!", mustard = "Whew" }
    

    長所: 新しい「タイプ」を簡単に追加できる
    短所: 新しい操作を追加するには、多くの既存の関数を変更する必要がある可能性がある

もちろん、これらを組み合わせて使用​​することもできます。インターフェイス、実装、継承ではなく、関数、データ、構成について考えることに慣れたら、ほとんどの場合、これらで十分です。

型クラスはオーバーロード用に設計されています。Haskell でオブジェクト指向プログラミングを模倣するためにそれらを使用することは、通常は間違いです。

于 2013-04-19T10:34:50.137 に答える
5

型クラスは Java スタイルのインターフェースに少し似ていますが、実際にはインターフェースを使用するのとまったく同じ方法で使用するわけではないため、それらを学習するのに最適な方法ではありません。

インターフェイス型です (オブジェクト指向言語にはサブタイプがあるため、他の型がインターフェイスのサブタイプになる可能性があり、これが何かを成し遂げる方法です)。Haskell ではすべての型が互いに素であるため、型クラスは型ではありません。これは型のセットです (インスタンス宣言は、セットのメンバーが何であるかを宣言する場所です)。このように考えてみてください。これにより、型シグネチャをより自然に正しく読み取ることができます ( String -> a「 を取り、String必要な型の値を返す」、および「 を取り、そのメンバーである任意の型の値を返すSomeTypeClass a => String -> a」という意味です)。StringSomeTypeClass

やりたいことを思い通りにできないのに、どうしてやりたいようにしなければならないのか、私にはわかりません。paakタイプだけを持てないのはなぜString -> Yooですか?

あなたは次のようなことをしようとしていると言います:

let x = someGoo ....
in ham x ++ mustard x

の場合、タイプは にsomeGoo ...なりpaak "dummy string"ます。しかしは のメンバーなので、やのようなメソッドを呼び出すことができます。後で別の型の値を返すように変更した場合、コンパイラは特定の機能を使用したすべての場所を通知し、関数を呼び出したが使用しただけの場所を変更せずに喜んで受け入れます。xYooYooGooGoohammustardpaakGooYoopaakGoo

「のメンバーである未知のタイプ」と入力する必要があるのはなぜGooですか?基本的に、 の呼び出し元は の型を操作paak せず、実際に返される.Gooのみを操作します。paakYoo

具象型を操作する関数がいくつかあります。これらの関数は、それらの具象型の関数と、具象型がメンバーである型クラスからの関数を呼び出すことができます。または、型クラスのメンバーである任意の型を操作する関数がある場合、呼び出すことができるのは型クラス内の任意の型を操作する関数だけです。

于 2013-04-19T10:44:41.350 に答える
2

まず第一に、それは一般的に必要ありません!あなたのWhenIAmGooアプローチは問題ありません。Haskell は怠惰なので、実際の欠点はありませんが、多くの場合、はるかに明確です。

しかし、それはまだ可能です:

{-# LANGUAGE RankNTypes              #-}

paak' :: String -> (forall goo . Goo goo => goo -> r) -> r
paak' g f = f Yoo

複雑に見えますか?

この問題を理解するには、Haskell のHindley-Milnerベースの型システムが、C++ や Java などとは根本的に異なる仕組みを知る必要があります。これらの言語では、ご存じのように、ポリモーフィズムは基本的に一種の限定された動的型付けです。「インターフェイス タイプ」を持つオブジェクトを渡す場合、実際には、インターフェイス メソッドがどのように実装されているかを知っているオブジェクトの周りにラッパーを渡します。初期化。

Haskell では違います。明示的に記述されたポリモーフィック署名は次のようになります。

paak :: { forall goo . (Goo goo) } => {String -> goo}

つまり、実際には、関数には完全に別の追加の引数、「辞書引数」があります。これは、インターフェイスにアクセスするために使用されるものです。そして、これは確かに関数に渡される引数であるため、関数は明らかにそれを選択できません。

関数から辞書を渡すには、で行ったような邪悪なトリックを使用する必要があります。ポリモーフィックな結果を直接返すのではなく、呼び出し元に「どのように使用するのですか? 「取得する具体的な型を教えてはいけません...」つまり、選択した具体的な型を挿入できる多相関数を自分自身に提供するように要求します。

このような関数は、次のように使用できます。

myHamAndMustard = paak' arg (\myGoo -> ham myGoo ++ mustard myGoo )

これはまったくいいことではありません。繰り返しますが、通常より良い方法は、すべての可能な出力に対して透過的で非ポリモーフィックなコンテナーを用意することです。多くの場合、それはまだ最適ではありません。オブジェクト指向の角度から問題全体にアプローチした可能性があります。

于 2013-04-19T09:39:39.473 に答える
2

これまでに提供された情報に基づくと、C スタイルのインターフェース (== 関数を含むレコード) が適しているようです。

ただし、使いやすくするには、スマート コンストラクターを追加して、次AnyGooのインスタンスを作成しますGoo

data AnyGoo = AnyGoo {
    ham' :: String 
}
instance Goo AnyGoo where
    ham = ham' 


anyGoo :: (Goo goo) => goo -> AnyGoo 
anyGoo goo = AnyGoo { ham' = ham goo }

ham次に、すべてを一様に呼び出すことができますGoo

> let x = anyGoo Hoo
> let y = anyGoo Yoo
> ham x
"Hoo!"
> ham y
"Yoo!"

paakAnyGooの代わりに を返しGooます:

paak :: String -> AnyGoo
paak _ = anyGoo Yoo

しかし、その後、あなた (私) は再び特定のタイプを渡すことになるため、hammar の提案をより適切に返すことができます。

于 2013-04-19T10:20:16.000 に答える