Ограничения функции записи Haskell

#haskell

#haskell

Вопрос:

Как в Haskell выразить что-то вроде:

 data SinkBuilder
  { openSink :: MonadIO m => m Sink
  }

data Sink = Sink
  { writeSink :: MonadIO m => Value -> m ()
  , closeSink :: MonadIO m => m ()
  }
  

Я предполагаю, что мне нужен набор функций, которые удобно «объединены» вместе в тип данных, чтобы этот тип данных создавался и передавался как единое целое?

Это кажется полезным по соображениям композиции, например, я мог бы реализовать что-то вроде

 (: :) :: SinkBuilder -> SinkBuilder -> SinkBuilder
a : : b = SinkBuilder $ do
  sa <- openSink a
  sb <- openSink b
  return $ Sink (v -> writeSink sa >> writeSink sb) (closeSink sa >> closeSink sb)
  

Это не работает, потому что у меня не может быть ограничений на функции записи, но это также выглядит очень некрасиво, поэтому я уверен, что должен быть гораздо лучший способ сделать это.

Комментарии:

1. Не MonadIO m => m Sink эквивалентно IO Sink ?

2. @Michael Я думаю, что они эквивалентны, если m они определены количественно повсеместно.

3. На самом деле это не эквивалент, у меня есть своя собственная монада, которая является MonadIO. Также я хотел иметь больше ограничений, например (MonadIO m, MonadState MyState m) => ...

4. @AlexeyRaga да, но вы не сможете создать Sink or SinkBuilder , обернув вещи из своей собственной монады, или я в замешательстве? Они могут быть созданы только с помощью liftIO и общих операций с монадами, то есть они просто реплицируются IO .

5. @Michael Существует разница между IO и произвольным стеком монад, построенным поверх IO .

Ответ №1:

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

 {-# LANGUAGE ScopedTypeVariables, RankNTypes #-}

import Control.Monad.Trans

type Value = Int

data SinkBuilder = SB
  { openSink :: forall m. MonadIO m => m Sink
  }

data Sink = Sink
  { writeSink :: forall m. MonadIO m => Value -> m ()
  , closeSink :: forall m. MonadIO m => m ()
  }

(# #) :: SinkBuilder -> SinkBuilder -> SinkBuilder
a # # b = SB $ do
  sa <- openSink a
  sb <- openSink b
  return $ Sink (v -> writeSink sa v >> writeSink sb v) (closeSink sa >> closeSink sb)
  

Комментарии:

1. Спасибо, я попробую это. Я хотел иметь некоторое представление об эффекте Sink : что-то, что можно открыть, затем я мог бы отправить ему некоторые значения и закрыть его. Например, у меня может быть приемник, который записывает в файл XML, приемник, который записывает в файл Json и т. Д. Я также хочу иметь возможность составлять их таким образом Sink Sink = Sink , чтобы при отправке в приемник результатов записывались оба (или несколько) файла. Ограничение здесь было введено, потому что некоторые приемники могут быть «с сохранением состояния» таким образом, что для них потребуется MonadState S m => ограничение. Я просто не знаю, как это лучше смоделировать…