Предложение 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 проблемы с этим:

  1. Ваша реализация неэффективна - O (n ^ 2).
  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

Так что да, это только "поддерживает" одну монаду

Другие вопросы по тегам