Подтвердить что ты не робот

Haskell - Как избежать повторения одного и того же контекста снова и снова?

Недавно я начал небольшой , где я пытаюсь реализовать трюковую карточную игру Skat (для 3 игроков). Чтобы иметь возможность играть с разными игроками (например, AI, network и local), я разработал interface с помощью typeclass Player:

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

Я использую StateT для обертывания этих трех игроков:

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)

Как я могу избежать записи этого большого контекста снова и снова?

4b9b3361

Ответ 1

Ответ 2

Update: Когда я попытался реализовать dealCards, я понял, что мое решение снижает безопасность типа, заставляя игроков взаимозаменяемыми. Таким образом, вы можете легко использовать одного игрока вместо другого, что может быть нежелательным.


Если вы не против использования ExistentialQuantification, я думаю, что он может (и должен?) использоваться здесь. В конце концов, функция dealCards не должна заботиться или знать о a, b и c, правильно?

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

Ответ 3

Ясно, что лучший ответ - иметь дизайн, который не нуждается во всех этих параметрах типа в первую очередь. Однако, если вы действительно не можете избавиться от них и ответить на поставленный вопрос, вот трюк, который я сыграл:

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

Теперь запись '(Signal y) = > ...' будет означать все остальные типы и не позволяет деталям реализации, таким как Storable, попасть в каждый API. Однако вы должны объявлять экземпляры для Signal. Это легко, потому что у него нет методов, но он, вероятно, в основном подходит для случаев, когда у вас мало примеров, но много функций.