Предложение Haskell do с несколькими типами монад
Я использую графическую библиотеку в Haskell под названием Threepenny-GUI. В этой библиотеке основная функция возвращает UI
монадный объект. Это вызывает у меня сильную головную боль, как при попытке распаковать IO
значения в локальные переменные я получаю сообщения об ошибках различных типов монад.
Вот пример моей проблемы. Это немного измененная версия стандартной основной функции, как показано в примере кода Threepenny-GUI:
main :: IO ()
main = startGUI defaultConfig setup
setup :: Window -> UI ()
setup w = do
labelsAndValues <- shuffle [1..10]
shuffle :: [Int] -> IO [Int]
shuffle [] = return []
shuffle xs = do randomPosition <- getStdRandom (randomR (0, length xs - 1))
let (left, (a:right)) = splitAt randomPosition xs
fmap (a:) (shuffle (left ++ right))
Обратите внимание на пятую строку:
labelsAndValues <- shuffle [1..10]
Который возвращает следующую ошибку:
Couldn't match type ‘IO’ with ‘UI’
Expected type: UI [Int]
Actual type: IO [Int]
In a stmt of a 'do' block: labelsAndValues <- shuffle [1 .. 10]
Что касается моего вопроса, как мне распаковать IO
функция с использованием стандартной стрелки обозначения (<-
), и продолжайте иметь эти переменные как IO ()
скорее, чем UI ()
так что я могу легко передать их другим функциям.
В настоящее время единственным решением, которое я нашел, было использование liftIO
, но это вызывает преобразование в UI
тип монады, в то время как я на самом деле хочу продолжать использовать IO
тип.
3 ответа
do
Блок предназначен для определенного типа монады, вы не можете просто изменить тип в середине.
Вы можете трансформировать действие или вкладывать его внутрь do
, В большинстве случаев преобразования будут готовы для вас. Вы можете, например, иметь вложенный do
это работает с io
а затем конвертировать его только в точке взаимодействия.
В вашем случае liftIOLater
Для этого вам предлагается функция пакета ThreePennyUI.
liftIOLater :: IO () -> UI ()
Запланируйте действие ввода-вывода, которое будет выполнено позже.
Чтобы выполнить обратное преобразование, вы можете использовать runUI
:
runUI :: Window -> UI a -> IO a
Выполнить действие пользовательского интерфейса в определенном окне браузера. Также запускаются все запланированные действия ввода-вывода.
Это более расширенный комментарий - он не затрагивает основной вопрос, но ваша реализация shufffle
, Есть 2 проблемы с этим:
- Ваша реализация неэффективна - O (n ^ 2).
IO
не подходит для этого - у shuffle нет общих побочных эффектов, просто нужен источник случайности.
Для (1) есть несколько решений: Одно из них заключается в использовании Seq
И его index
, который является O (log n), который сделал бы shuffle
O (n log n). Или вы могли бы использовать ST
массивы и один из стандартных алгоритмов для получения O (n).
Для (2) все, что вам нужно, это поточная генерация случайных чисел, а не полная мощность IO
, Уже есть хорошая библиотека MonadRandom, которая определяет монаду (и класс типов) для рандомизированных вычислений. И другой пакет уже обеспечивает shuffle
функция поскольку IO
это пример MonadRandom
Вы можете просто использовать shuffle
непосредственно в качестве замены для вашей функции.
Под прикрытием do это просто синтаксический сахар для >>= (bind) и пусть:
do { x<-e; es } = e >>= \x -> do { es }
do { e; es } = e >> do { es }
do { e } = e
do {let ds; es} = let ds in do {es}
И тип привязки:
(>>=) :: Monad m => a -> (a -> m b) -> m b
Так что да, это только "поддерживает" одну монаду