Семейства типов вызывают неоднозначную ошибку переменной

Случай использования: я пишу игру в интерфейсе ghcjs и бэкэнде, которые используют по существу одно и то же состояние, чтобы я мог кодировать правила игры с обеих сторон и связываться с изменениями состояния. Для этого игровое состояние будет выглядеть

data GameState = GameState {
  gameTurn :: Int,            -- | Everyone sees
  gamePhase :: GamePhase,     -- | this
  boardState :: BoardState,   -- | stuff
  -- a lot more stuff everyone can see, followed by
  usHand :: [Card],
  ussrHand :: [Card]
}

Где каждый игрок представлен нами и ссср. У каждого игрока есть рука, и с точки зрения сервера он универсален и знает каждую карту в руках обоих игроков. Но с точки зрения американского игрока, состояние игры выглядит примерно так

data GameState = GameState {
  -- public stuff
  usHand :: [Card],
  ussrHand :: Int
}

То есть он может видеть свою собственную руку, но он может видеть только, сколько карт у его оппонента. Наблюдатель видит еще меньше. Правила игры сложны, и может случиться много вещей, поэтому было бы здорово один раз закодировать правила, чтобы правила, влияющие на руку игрока, такие как раздача новых карт, заставляли игрока показывать карта типа и т. д. будет влиять на каждую руку в зависимости от того, кто они. С этой целью я написал следующее, используя семейства типов, которые не работают

{-# LANGUAGE  TypeFamilies, RankNTypes #-}
module Test where

data Card = Card
data BoardState = BoardState
data GamePhase = GamePhase
data Country
data Player = PUS | PUSSR

data US
data USSR
data Observer
data Server

data Private = Private Int
data Public = Public [Card]

class HandType a where
  type USHand   a :: *
  type USSRHand a :: *
  toUS :: Public -> USHand a
--   toUSSR :: Public -> USSRHand a -- TODO

instance HandType Server where
  type USHand Server = Public
  type USSRHand Server = Public
  toUS (Public cs) = Public cs

instance HandType US where
  type USHand US = Public
  type USSRHand US = Private
  toUS (Public cs) = Public cs

instance HandType USSR where
  type USHand USSR = Private
  type USSRHand USSR = Public
  toUS (Public cs) = Private (length cs)

instance HandType Observer where
  type USHand Observer = Private
  type USSRHand Observer = Private
  toUS (Public cs) = Private (length cs)

data GameState a = GameState {
  gameTurn :: Int,            -- | Everyone sees
  gamePhase :: GamePhase,     -- | this
  boardState :: BoardState,   -- | stuff

  usHand :: USHand a,
  ussrHand :: USSRHand a
}

data Event a =
    PlaceInfluence Player Int Country -- | Most plays don't affect
  | PlayCard Player Card              -- | either hand
  | DealCards (USHand a) (USSRHand a) -- | This one does

-- Works
obsEvents :: [Event US]
obsEvents = [PlayCard PUS Card, PlayCard PUSSR Card, DealCards (Public [Card]) (Private 3)]

-- Works
serverEvents :: [Event Server]
serverEvents = [PlayCard PUS Card, PlayCard PUSSR Card, DealCards (Public [Card, Card]) (Public [Card])]

-- The server must send out the gamestate modified for the player's consumption.
-- serverToPlayerGS :: GameState Server -> GameState a
serverToPlayerGS (GameState turn phase bs us ussr) =
  GameState turn phase bs (toUS us) undefined -- | <- Doesn't work (line 75)

-- serverToPlayerEvent :: Event Server -> Event a
serverToPlayerEvent (PlaceInfluence p amt c) = PlaceInfluence p amt c
serverToPlayerEvent (PlayCard p c) = PlayCard p c
serverToPlayerEvent (DealCards us ussr) =
  DealCards (toUS us) undefined -- | <- Doesn't work (line 78)

Ошибка в обеих строках 75 и 78 - это что-то вроде

Couldn't match expected type ‘USHand a’
            with actual type ‘USHand a0’
NB: ‘USHand’ is a type function, and may not be injective
The type variable ‘a0’ is ambiguous
Relevant bindings include
  serverToPlayerGS :: GameState Server -> GameState a
    (bound at src/Test4.hs:74:1)
In the fourth argument of ‘GameState’, namely ‘(toUS us)’
In the expression: GameState turn phase bs (toUS us) undefined

Или если я опущу объявление типа

Could not deduce (USHand a0 ~ USHand a1)
from the context (HandType a1,
                  USHand a1 ~ USHand a,
                  USHand t ~ Public)
  bound by the inferred type for ‘serverToPlayerGS’:
             (HandType a1, USHand a1 ~ USHand a, USHand t ~ Public) =>
             GameState t -> GameState a
  at src/Test4.hs:(74,1)-(75,45)
NB: ‘USHand’ is a type function, and may not be injective
The type variable ‘a0’ is ambiguous
Expected type: USHand a
  Actual type: USHand a0
When checking that ‘serverToPlayerGS’ has the inferred type
  serverToPlayerGS :: forall t a a1.
                      (HandType a1, USHand a1 ~ USHand a, USHand t ~ Public) =>
                      GameState t -> GameState a
Probable cause: the inferred type is ambiguous

Я вижу некоторые другие ответы на сайте, которые похожи, но я не уверен, как объясненные исправления в конечном итоге приведут к ответу, на который я надеюсь, что это способ написать serverToPlayerGS и serverToPlayerEvent способами, которые проверяют тип и полезны,

1 ответ

Решение

Проблема в том, что ваш тип семьи не является инъективным: зная, что USHand a является Private например, не говорит вам, что именно a это: это может быть USSR но это также может быть Observer потому что оба экземпляра объявляют:

type USHand USSR = Private
type USHand Observer = Private

Потому что функция toUS имеет тип Public -> USHand a, a нужно как-то угадать, и мы просто увидели, что это невозможно.

Чтобы это исправить, вам нужно ввести прокси. Прокси - это простой тип данных, определяемый как:

data Proxy a = Proxy

Если у вас есть функция f :: F a для которого Хаскелл не может угадать aВы можете превратить его в f :: Proxy a -> F a для того, чтобы иметь возможность указать на сайте вызова, который a Вы имеете в виду, например, написав f (Proxy :: Proxy Int) в том случае, когда вы хотите a быть Int,

Вам понадобятся переменные типа scoped, потому что a вы будете использовать с toUs придет из аннотации типа вашей функции. Таким образом, вы должны добавить эти две строки вверху вашего файла:

{-# LANGUAGE ScopedTypeVariables #-}
import Data.Proxy

А затем измените тип toUS от Public -> USHand a чтобы:

toUS :: Proxy a -> Public -> USHand a

Не забудьте добавить фиктивный аргумент _ на все ваши объявления экземпляра toUs, Наконец, вы можете исправить свое определение serverToPlayerGS вот так:

serverToPlayerGS :: forall a. HandType a => GameState Server -> GameState a
serverToPlayerGS (GameState turn phase bs us ussr) =
  GameState turn phase bs (toUS (Proxy :: Proxy a) us) undefined 
Другие вопросы по тегам