Поле данных Haskell как экземпляр класса типов

Я хотел бы выразить тип данных с полем sType как что-нибудь, что является экземпляром SType (от zeromq-haskell). SType - это тип сокета zeromq. Вот один пример из исходного кода zeromq-haskell:

data Pair = Pair
instance SType Pair where
    zmqSocketType = const pair

Вот что у меня есть сейчас

data SocketOpts = SocketOpts
 { end :: SocketEnd
 , sType :: SType st => st
 }

Но когда я так использую socket ctx $ sType so, я получаю:

Ambiguous type variable `a0' in the constraint:
(SType a0) arising from a use of `sType'

(подпись сокета socket :: SType a => Context -> a -> IO (Socket a)

Когда я пытаюсь создать SocketOpts в ghci, я получаю:

let so = SocketOpts (Bind "some") Pull

<interactive>:1:35:
Could not deduce (st ~ Pull)
from the context (SType st)
  bound by a type expected by the context: SType st => st
  at <interactive>:1:10-38
  `st' is a rigid type variable bound by
       a type expected by the context: SType st => st
       at <interactive>:1:10
In the second argument of `SocketOpts', namely `Pull'
In the expression: SocketOpts (Bind "some") Pull
In an equation for `so': so = SocketOpts (Bind "some") Pull

Из этого я понимаю, что SType является более общим, чем то, что я просил (Pull, который является экземпляром SType). Как мне выразить здесь то, что я хочу?


Изменить

Этот:

data SocketOpts st = SocketOpts
     { end :: SocketEnd
     , sType :: st
     }

используется вместе с:

zmqSource :: (ResourceIO m, SType st) => Context -> SocketOpts st -> Source m a
zmqSource ctx so = sourceIO
          mkSocket
          recvSock
          (\x -> undefined)
          where
              recvSock = undefined
              mkSocket = socket ctx $ sType so

Кажется, работает, но я оставлю вопрос открытым, если есть более элегантный способ сделать это?


Изменить 2

Ребята, большое спасибо за ответы. Основываясь на ваших отзывах, у меня теперь есть следующее (я не буду публиковать его здесь, так как его легче читать на github)

https://github.com/boothead/zeromq-conduit/blob/master/Data/Conduit/ZMQ.hs

Я использовал GADT (я думаю), чтобы попытаться выразить разницу между настройкой обычного сокета и вспомогательного сокета, но на данный момент есть морщинка: я мог бы использовать тип SockOpts для настройки вспомогательного сокета, в этом случае subscribe не будет вызываться, и это не сработает:

SockOpts (Connect "tcp://127.0.0.1:9999") Sub  -- This would be bad

Есть ли способ заставить систему типов запретить это? Что-то вроде того, что у меня в угловых скобках?

SockOpts :: (SType st, <not SubsType st>) => SocketEnd -> st -> SocketOpts st

person Ben Ford    schedule 29.12.2011    source источник


Ответы (2)


Я хотел бы выразить тип данных с полем sType как что-нибудь, что является экземпляром SType (от zeromq-haskell).

Есть некоторая двусмысленность в том, что вы подразумеваете под словом «что угодно».

Вы хотите просто создать SocketOpts значения с параметрическим типом и обеспечить выполнение требования для экземпляра SType, когда используется значение SocketOpts? Тогда подойдет нормальный параметризованный тип, как в вашем редактировании.

Вы хотите гарантировать, что любое значение SocketOpts будет иметь экземпляр для указанного типа, обеспечивая соблюдение ограничения, когда значение создается, а не используется? Тогда определение GADT будет работать:

data SocketOpts st where
    SocketOpts :: (SType st) => SocketEnd -> st -> SocketOpts st

Вы хотите использовать какой-либо экземпляр для создания значения SocketOpts без его параметризации определенным типом? Вот экзистенциальный тип будет работать:

data SocketOpts where
    SocketOpts :: (SType st) => SocketEnd -> st -> SocketOpts

... но имейте в виду, что экзистенциалы могут быть неудобными, и что все, что вы можете делать с чем-то вроде этого, - это сопоставление с образцом на SocketOpts, а затем использовать методы, определенные для SType. Знания о конкретном типе теряются.

Или, наконец, вы хотите выбрать какой-либо экземпляр при использовании значения SocketOpts, отказавшись от возможности создать экземпляр с определенным типом? Это универсально определяемый тип:

data SocketOpts where
    SocketOpts :: SocketEnd -> (forall st. SType st => st) -> SocketOpts

... что, как мне кажется, было вашей исходной версией. В этом случае вы не можете использовать мономорфное значение для создания значения SocketOpts, только полиморфное значение - подумайте о разнице между Int значением 11 и числовым литералом 11 :: Num a => a. Имейте в виду, что в этом случае GHC по-прежнему необходимо знать, какой экземпляр выбрать, поэтому при использовании содержимого SocketOpts вам понадобится определенный тип перед использованием каких-либо SType методов.

Обе ошибки, которые вы видели, возникли из-за вышеперечисленных предупреждений об универсально определяемых типах: «неоднозначный тип», потому что вы применили что-то, что необходимо для выбора SType экземпляра, не предоставив GHC достаточно информации для этого, и ошибка «не удалось определить», потому что вы пытался построить SocketOpts с мономорфным значением.

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

person C. A. McCann    schedule 29.12.2011

Вы пробовали GADT?

data SocketOpts where
    SocketOpts :: SType st => SocketEnd -> st -> SocketOpts

Вы также можете попробовать GADT с экзистенциалами:

data SocketOpts where
    SocketOpts :: SocketEnd -> (forall st . SType st => st) -> SocketOpts
person nponeccop    schedule 29.12.2011