Не удалось сопоставить типы глубоко в стеке монад - PullRequest
0 голосов
/ 10 июня 2018

Я пишу программу OpenGL для учебных целей, используя библиотеку GPipe.Библиотека выполняет магию черного типа, и это (с добавлением newtype от меня для хорошей меры) делает меня неспособным правильно анализировать сообщения об ошибках.Следующий код не компилируется:

{-# LANGUAGE PackageImports #-}
module Main where

import Control.Monad.State
import Control.Monad.Except

import qualified "GPipe" Graphics.GPipe as GP
import qualified "GPipe-GLFW" Graphics.GPipe.Context.GLFW as GLFW

---- types ----

newtype Processor ctx os a = Processor {
    runProcessor :: GP.ContextT ctx os (StateT (FullState os) IO) a
}

data Transition os = ToMainMenu (FullState os)
                   | Quit

type CType = GP.RGBFloat
type UnitWindow os = GP.Window os CType ()

data ArtState os = ArtState {
    _asWindow :: UnitWindow os
}

data ProgState = ProgState

data FullState os = FullState {
    _fsArtState :: ArtState os
  , _fsProgState :: ProgState
}

---- constructors ----

mkFullState :: UnitWindow os -> FilePath -> ExceptT String IO (FullState os)
mkFullState window directory = do
    art <- mkArtState window directory
    prog <- mkProgState directory
    return FullState {
        _fsArtState = art
      , _fsProgState = prog
    }

mkArtState :: UnitWindow os -> FilePath -> ExceptT String IO (ArtState os)
mkArtState window _ = return ArtState {
    _asWindow = window
}

mkProgState :: FilePath -> ExceptT String IO ProgState
mkProgState _ = return ProgState

---- processors ----

start :: Processor ctx os (Transition os)
start = Processor $ GP.runContextT GLFW.defaultHandleConfig $ do
    win <- GP.newWindow (GP.WindowFormatColor GP.RGB8) (GLFW.defaultWindowConfig "Foobar")
    possiblyState <- liftIO $ runExceptT $ mkFullState win "./"
    case possiblyState of
         Left err -> liftIO $ putStrLn err >> return Quit
         Right state -> return $ ToMainMenu state

---- Main ----

main :: IO ()
main = do
    transition <- runProcessor start
    case transition of 
         Quit -> return ()
         ToMainMenu _ -> return ()

Идея состоит в том, чтобы Processor s возвращал Transition для использования основным циклом для выбора подходящего пути выполнения.Ошибка компиляции выглядит следующим образом:

/tmp/testing/app/Main.hs:60:25: error:
    • Couldn't match type ‘os1’ with ‘os’
      ‘os1’ is a rigid type variable bound by
        a type expected by the context:
          forall os1.
          GP.ContextT
            GLFW.Handle
            os1
            (GP.ContextT ctx os (StateT (FullState os) IO))
            (Transition os)
        at app/Main.hs:(55,21)-(60,49)
      ‘os’ is a rigid type variable bound by
        the type signature for:
          start :: forall ctx os. Processor ctx os (Transition os)
        at app/Main.hs:54:1-41
      Expected type: GP.ContextT
                       GLFW.Handle
                       os1
                       (GP.ContextT ctx os (StateT (FullState os) IO))
                       (Transition os)
        Actual type: GP.ContextT
                       GLFW.Handle
                       os1
                       (GP.ContextT ctx os (StateT (FullState os) IO))
                       (Transition os1)
    • In the expression: return $ ToMainMenu state
      In a case alternative: Right state -> return $ ToMainMenu state
      In a stmt of a 'do' block:
        case possiblyState of
          Left err -> liftIO $ putStrLn err >> return Quit
          Right state -> return $ ToMainMenu state
    • Relevant bindings include
        state :: FullState os1 (bound at app/Main.hs:60:16)
        possiblyState :: Either String (FullState os1)
          (bound at app/Main.hs:57:5)
        win :: GP.Window os1 GP.RGBFloat () (bound at app/Main.hs:56:5)
        start :: Processor ctx os (Transition os)
          (bound at app/Main.hs:55:1)
   |
60 |          Right state -> return $ ToMainMenu state
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^

Мое понимание Хаскелла и монад не позволяет мне это исправить, я могу видеть, что os1 и os создаются различными уравнениями ипоэтому GHC не может просто пометить их как одинаковые, но я не знаю, как их починить.Если я удаляю параметр os из перечисления Transition, ошибка исчезает, но мне нужно , чтобы передать состояние вместо повторной инициализации в каждом процессоре.

Может кто-нибудь объяснитьчто не так и как это исправить?

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

1 Ответ

0 голосов
/ 10 июня 2018

Функция, которая возвращает значение ContextT (здесь заключено в Processor), например start, не должна вызывать GP.runContextT.

GP.runContextT.инициализировать и предоставлять контекст для выполнения процессоров, что вы хотите сделать только один раз в начале всей программы.Таким образом, он, вероятно, должен быть в main, вместе с newWindow, defaultWindowConfig и mkFullState.

A Processor подобно start можно получить текущее состояние с помощью преобразователя StateT,Но сначала мы должны исправить тип Processor.Обратите внимание на тип runContextT, в частности forall:

runContextT
    :: (MonadIO m, MonadAsyncException m, ContextHandler ctx)
    => ContextHandlerParameters ctx -> (forall os. ContextT ctx os m a) -> m a

. Это forall означает, что переменная типа os не может встречаться в m или в a, предотвращаяопределенные ресурсы от утечки.Это несовместимо с текущим определением Processor, поскольку StateT (FullState os) IO содержит os.Возможно, вы можете поменять местами трансформаторы.

newtype Processor ctx os a = Processor {
    runProcessor :: StateT (FullState os) (GP.ContextT ctx os IO) a
}

Теперь start может использовать get для доступа к текущему состоянию, и, поскольку он не должен обрабатывать инициализацию, он не имеет Quitветвление больше (вы можете больше не хотеть делать start a Processor на данный момент, но, надеюсь, это достаточно близко к тому, что вы на самом деле хотели бы сделать с другими процессорами):

start :: Processor ctx os (Transition os)
start = Processor $ do
  s <- get
  return $ ToMainMenu s

И main может выглядеть так:

main :: IO ()
main =
    -- Initialize and provide context, i.e, convert the wrapped
    -- do-block of type `ContextT _ _ IO` to `IO`
    GP.runContextT GLFW.defaultHandleConfig $ do

        -- Create a GLFW window
        -- You can probably create more than one
        win <- GP.newWindow (GP.WindowFormatColor GP.RGB8) (GLFW.defaultWindowConfig "Foobar")

        -- Create the initial processor state, handling initialization failures
        s_ <- liftIO $ runExceptT $ mkFullState win "./"
        s0 <- case s_ of
            Left e -> fail e
            Right s0 -> return s0

        -- Run a processor
        (transition, s1) <- (`runStateT` s0) $ runProcessor start

        case transition of
            Quit -> return ()
            ToMainMenu _ -> return ()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...