2011-07-15 85 views
22

我最近開始了一個小小的hobby project,在那裏我嘗試實施詭計多端的卡牌遊戲Skat(3名玩家)。Haskell - 如何避免一遍又一遍地輸入相同的上下文?

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

我用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) 
    

    因此,你可以完全消除類,並使用普通的數據類型

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

    這基本上是my previous answer

  • 另一種解決方案是通過使用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 
    

    換句話說,約束成爲數據類型的一部分。

  • 在我看來,最好的解決方案是廢除整個東西,並根據我的operational package沿着TicTacToe example的線重新設計它。這種設計可以讓你將每個玩家(人類,AI,重放......)寫入一個專門的monad中,然後將所有東西注入到一個普通的解釋器中。

+2

B ..但是..這基本上是我說的.. – yairchu

+1

我喜歡這種方法。因此,處理玩家狀態的方法是使用更通用的函數,如'playerMessageAI :: AiState - > Message answer - > m(錯誤答案,播放器)'並使用部分應用程序?好吧,我想我明白了。 PS:在'data Player'之後你錯過了'm',但我不知道這是否是故意的。 (也許'newtype'的作品呢?) – fuz

+0

@yairchu我提高了你的答案,正如Heinrich Apfelmus給出了一些額外的解釋。 – fuz

6

更新: 當我試圖實現dealCards我已經意識到我的解決方案降低了通過使球員互換的類型安全。通過這種方式,您可以輕鬆使用一個玩家而不是其他可能不合意的玩家。


如果你不介意使用ExistentialQuantification,我認爲它可以(而且應該?)用在這裏。畢竟,dealCards功能不應該關心或知道關於a,bc,對不對?

{-# 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

我也傾向於說,如果一個類型類可以通過實例成員的記錄直接表示,那麼這種表示幾乎總是比煩人的存在更好。我通常在這裏引用luqui的博客,但是......看起來@海因裏希已經照顧過了,嘿。 –

+2

請注意,從概念意義上講,通過部分應用從函數的類型中消除的任何參數類型變成存在的;它存在,即使你無法用它做任何事情。直接使用存在類型主要是關於重構這個概念,這是很少需要的。 –

2

顯然,最好的答案是有一個設計並不需要所有這些類型參數。但是,如果你真的不能擺脫他們,並回答,因爲提出的問題,這裏是我打過一招:

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

現在寫「(信號Y)=> ...」將暗示所有其他類型類,並阻止像Storable這樣的實現細節進入每個API。但是,您必須爲Signal聲明實例。這很容易,因爲它沒有方法,但可能最適合於當你有很少的實例但功能很多時。

相關問題