2011-07-15 9 views
22

私は最近少し遅れてhobby projectを開始しました。ここでは、トリックプレイのカードゲームSkat(3人のプレイヤー)を実装しようとしています。 ハスケル - 何度も同じ文脈を繰り返すのを避けるには?

class Monad m => Player p m | p -> m where 
    playerMessage :: Message answer -> p -> m (Either Error answer,p) 

が、私はこれらの3人のプレーヤーをラップする StateTを使用します:

一緒に遊ん(AI、ネットワークおよびローカルのような)選手の異なる種類を持っていることを可能にするために、私は型クラス Playerを使用して interfaceをデザイン
type PST a b c m x = StateT (Players a b c) m x 

しかし、今、私はそれぞれの型シグネチャにコンテキストの大きな山を記述する必要があります。

dealCards :: (Player a m, Player b m, Player c m, RandomGen g) 
    => g -> PST a b c m (SomeOtherState,g) 

がどのように回避することができますこの大きな文脈を何度も繰り返し書いていますか?

+0

あなたのコードはどこにいらっしゃいますか?より多くの文脈でより有用な助言を得るかもしれません。ここのコードは、実際に必要なものよりも厳密に一般的なものが好きだと思っていますし、意味をなさないものに特化して簡略化することもできますが、大きな文脈がなければそのことを確信できません。 –

+0

@camccann [GitHub](https://github.com/fuzxxl/Unter)に公開されています。 github上のコードは、現在私の人生をより楽にするために多くのリファクタリングを行っているため、少し異なります。 – fuz

+2

@FUXxxl - ランダムなコメント - モジュールごとに拡張子を指定する必要があります – alternative

答えて

11
  • あなたがプレイヤークラスから観察することができる唯一のことは、

    playerMessage' :: Message answer -> m (Either Error answer, p) 
    

    したがってタイプの関数であり、 、あなたは完全にクラスを排除し、これは基本的にmy previous answerある

    data Player m = Player { playerMessage' 
           :: Message answer -> m (Either Error answer, Player m) } 
    

    通常のデータ型を使用することができます。

  • 代わりの方法は、GADTを使用してコンテキストをデータ型に移動することです。

    data PST a b c m x where 
        PST :: (Player a m, Player b m, Player c m) 
         => StateT (Players a b c) m x -> PST a b c m x 
    

    つまり、制約はデータ型の一部になります。

  • 私の意見では、TicTacToe exampleの行に沿って全体をスクラップし、operational packageから再設計するのが最善の解決策です。このデザインでは、各プレイヤー(人、AI、リプレイなど)を特殊なモナドに書き込んだり、後ですべてを共通のインタプリタに注入することができます。

+2

B ..しかし..それは基本的に私が言ったことです。 – yairchu

+1

私はこのアプローチが好きです。だから、プレーヤーの状態を処理する方法は、 'playerMessageAI :: AiState - > Message answer - > m(エラー回答、プレーヤー)のようなもっと一般的な関数を使い、部分的なアプリケーションを使うのですか?さて、私はそれを得たと思う。 PS: 'data player'の後に' m'がありませんが、意図的なのかどうかわかりません。 (おそらく 'newtype'も同様に動作しますか?) – fuz

+0

@yairchu Heinrich Apfelmusがいくつか余分な説明をしてくれたので、私はあなたの答えの両方をupvotedしました。 – fuz

6

アップデート:私はdealCardsを実装しようとしたとき 私は私の解決策は、選手たちは交換することによって、型の安全性を減少させることを実現しています。この方法で、一方のプレイヤーをもう一方のプレイヤーの代わりに簡単に使用できます。これは望ましくない可能性があります。


あなたがExistentialQuantificationを使用して気にしない場合、私はそれができると思う(とすべきでは?)ここで使用されます。結局のところ、dealCards関数は、abcについて気にするべきではありません。

{-# LANGUAGE ExistentialQuantification #-} 
{-# LANGUAGE MultiParamTypeClasses #-} 
{-# LANGUAGE FunctionalDependencies #-} 

import Control.Monad.State 
import System.Random 

type Message answer = answer 
type Error = String 

class Monad m => Player p m | p -> m where 
    playerMessage :: Message answer -> p -> m (Either Error answer,p) 

data SomePlayer m = forall p. Player p m => SomePlayer p 

data Players m = Players (SomePlayer m) (SomePlayer m) (SomePlayer m) 

type PST m x = StateT (Players m) m x 

dealCards :: (RandomGen g, Monad m) => g -> PST m x 
dealCards = undefined 

私は同様の方法でMonad制約を排除することが可能であるべきだと思います。

実際、このような場合、タイプクラスが過度に使用されているように感じます。たぶんそれは私に話してHaskellの初心者ですが、私が代わりにこれを記述します

data Player m = Player { playerMessage :: Message answer -> m (Either Error answer, Player m) } 
+0

合意。私と[Luke Palmerの意見](http://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/)では、存在量の定量化は、通常のデータ型が行う記号です。 –

+0

また、タイプ・クラスがインスタンス・メンバーのレコードによって直接的に表現できる場合、その表現は、迷惑な存在を混乱させるよりも、常に常に優れていると言えます。私はいつもルキのブログをここに引用していますが、それは@Heinrichがすでにそれを世話してくれたようです。 –

+2

部分的なアプリケーションによって関数の型から取り除かれた引数型は、概念的な意味で存在することに注意してください。たとえそれで何もできないとしても、それは存在する。実在型を直接使用することは、その概念を明らかにすることであり、ほとんど必要ではありません。 –

2

明らかに、より良い答えは、最初にすべてのタイプパラメータを必要としないデザインを持つことです。

class (Storable.Storable (X, y), Y y) => Signal y 

今書い「(信号y)=> ...」します:あなたが実際にそれらを取り除くことができない、と提起としての質問に答えるためにしかし、もし、ここで私がプレイしたトリックですすべての他のタイプメスを暗示し、Storableのような実装の詳細がすべてのAPIに入るのを防ぐ。ただし、Signalのインスタンスを宣言する必要があります。メソッドを持たないので簡単ですが、ほとんどインスタンスがなくても多くの機能がある場合に最適です。

関連する問題