Йесод/постоянное тестирование с шаблоном скобок

Я новичок в Haskell, поэтому у меня возникли проблемы с освоением всех дополнительных функций, используемых в Yesod, таких как экземпляры типов и ограничения равенства. Я пытаюсь реализовать шаблон скобок в тестовой среде Yesod, чтобы получить функциональность setUp/tearDown. Вот что у меня есть (обновлено через редактирование):

module FishMother where

import Control.Exception.Lifted

import TestImport
import Database.Persist
import Database.Persist.GenericSql

import Model

insertYellowfinTuna :: OneSpec Connection FishId
insertYellowfinTuna = runDB . insert $ Fish "Yellowfin Tuna"

deleteFish :: FishId -> OneSpec Connection ()
deleteFish = runDB . delete

withYellowfinTuna :: FishId -> OneSpec Connection ()
withYellowfinTuna = bracket insertYellowfinTuna deleteFish

Ошибки компиляции следующие:

tests/FishMother.hs:18:21:
    Couldn't match type `FishId
             -> Control.Monad.Trans.State.Lazy.StateT
                  (Yesod.Test.OneSpecData Connection) IO ()'
          with `Key SqlPersist Fish'
    Expected type: FishId -> OneSpec Connection ()
      Actual type: (FishId
            -> Control.Monad.Trans.State.Lazy.StateT
             (Yesod.Test.OneSpecData Connection) IO ())
           -> Control.Monad.Trans.State.Lazy.StateT
            (Yesod.Test.OneSpecData Connection) IO ()
    In the return type of a call of `bracket'
    In the expression: bracket insertYellowfinTuna deleteFish
    In an equation for `withYellowfinTuna':
    withYellowfinTuna = bracket insertYellowfinTuna deleteFish

Что я делаю неправильно?


person arussell84    schedule 18.01.2013    source источник
comment
На время полностью забудьте о Йесод. Если бы это был обычный IO, у вас были бы функции setup :: IO FishId, tearDown :: FishId -> IO (), а затем withYellowfinTuna = bracket setup tearDown. Поэкспериментируйте с пониманием того, каким будет тип этой функции, а затем вернитесь в мир Йесод и попробуйте заменить IO монадой OneSpec Connection.   -  person Michael Snoyman    schedule 19.01.2013
comment
Спасибо @michael-snoyman. Иногда вам просто нужно сделать шаг назад или даже немного поспать, чтобы решить проблему. Также помогло узнать, что я на правильном пути с OneSpec Connection и типами других функций. Вот исправленная подпись типа, которую я искал: withYellowfinTuna :: (FishId -> OneSpec Connection ()) -> OneSpec Connection ()   -  person arussell84    schedule 19.01.2013


Ответы (1)


Перечитав вопрос, я думаю, что простой ответ — использовать приподнятая скобка, которая будет обрабатывать все проблемы преобразования. Я также оставлю свой первоначальный ответ, так как он может дать немного больше понимания того, что происходит.


Проблема в этом случае заключается в использовании liftIO. Давайте посмотрим на сигнатуру типа:

liftIO :: MonadIO m => IO a -> m a

Это означает, что вы можете выполнить произвольное действие ввода-вывода (например, чтение из файла) и запустить его в любой монаде, которая позволяет выполнять ввод-вывод, например в монаде базы данных. Однако то, что вы пытаетесь сделать, на самом деле противоположно: запустить действие монады базы данных как обычное действие ввода-вывода. Это невозможно сделать напрямую, так как действия с базой данных зависят от дополнительного контекста, а именно от соединения с базой данных, которое монада IO не предоставляет.

Так как же превратить действие базы данных в действие ввода-вывода? Нам нужно развернуть. Развертка — обычное действие для преобразователей монад, которые можно рассматривать как слои, наложенные друг на друга. Поднятие (как в liftIO) позволяет вам взять действие с нижнего уровня и переместить его на более высокий уровень. Развертка снимает слой. Поскольку то, как вы разворачиваете, зависит от конкретного преобразователя, каждый преобразователь имеет свои собственные функции развертывания.

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

person Michael Snoyman    schedule 18.01.2013
comment
Спасибо за подсказки. Я удалил вызовы liftIO и использую поднятые bracket, но проблемы все еще возникают. Я думаю, что одна из подсказок, которые мне нужны, — это подпись типа withYellowfinTuna. Я знаю, что это не должно быть FishId -> PersistMonadBackend (), но я не могу понять, что это должно быть! Я, вероятно, также должен сказать, что я предпочел бы избегать использования специфичных для базы данных вызовов, таких как withSqliteConn, когда это возможно. К сожалению, отношения между runDB, withSqliteConn и runSqlConn мне пока не совсем ясны. - person arussell84; 19.01.2013
comment
Я обновил свой исходный пост, указав, где я оказался после прочтения вашего ответа, но я все еще застрял. Думаю, мне трудно это понять, потому что я не знаю сигнатур типов insert и delete в контексте моей модели Fish. Кроме того, уместно ли мне называть runDB в этих местах и ​​почему? Я не знаю, почему мой возвращаемый тип OneSpec Connection () после вызова runDB, но это то, что мне сказали использовать ошибки компиляции! - person arussell84; 19.01.2013