#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
orSinkBuilder
, обернув вещи из своей собственной монады, или я в замешательстве? Они могут быть созданы только с помощью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 =>
ограничение. Я просто не знаю, как это лучше смоделировать…