Поле данных Haskell как экземпляр класса типов - PullRequest
1 голос
/ 29 декабря 2011

Я хотел бы выразить тип данных с полем 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). Как мне выразить то, что я хочу здесь?


Редактировать

This:

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 (я думаю), чтобы попытаться выразить разницу между настройкой обычного сокета иSсокет ub, но на данный момент есть морщинка: я мог бы использовать тип SockOpts, чтобы настроить под-сокет, если подписка не будет вызываться и не будет работать:

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

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

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

Ответы [ 2 ]

3 голосов
/ 29 декабря 2011

Я хотел бы выразить тип данных с полем 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. Знание определенного типа теряется.

Или, наконец, вы хотите выбрать какой-либо экземпляр, когда с использованием значения a 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, вероятно, является лучшим выбором.

0 голосов
/ 29 декабря 2011

Ты пробовал ГАДЦ?

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

Вы также можете попробовать GADT с существующими:

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