Как определить экземпляр MonadUnliftIO для нового типа с фантомной переменной типа?

Связанный вопрос - Безопасно ли наследовать MonadThrow, MonadCatch, MonadBaseControl, MonadUnliftIO и т. Д.? - где я включил оба - DeriveAnyClass и GeneralizedNewtypeDeriving для компиляции кода, но не стал смотреть на зловещие предупреждения. Теперь, когда я запускаю свой отредактированный код, он выдает ошибку времени выполнения:

No instance nor default method for class operation >>=

Итак, я удалил DeriveAnyClass и оставил ТОЛЬКО GeneralizedNewtypeDeriving и получил следующую ошибку компиляции:

{-# LANGUAGE DataKinds, GADTs, ScopedTypeVariables, TypeFamilies, AllowAmbiguousTypes, RankNTypes, StandaloneDeriving, UndecidableInstances #-}

newtype AuthM (fs :: [FeatureFlag]) auth m a =
  AuthM (ReaderT (Auth auth) m a)
  deriving (Functor, Applicative, Monad, MonadReader (Auth auth), MonadIO, MonadThrow, MonadCatch, MonadMask, MonadUnliftIO)


--     • Couldn't match representation of type ‘m (Control.Monad.IO.Unlift.UnliftIO
--                                                   (AuthM fs auth m))’
--                                with that of ‘m (Control.Monad.IO.Unlift.UnliftIO
--                                                   (ReaderT (Auth auth) m))’
--         arising from the coercion of the method ‘Control.Monad.IO.Unlift.askUnliftIO’
--           from type ‘ReaderT
--                        (Auth auth)
--                        m
--                        (Control.Monad.IO.Unlift.UnliftIO (ReaderT (Auth auth) m))’
--             to type ‘AuthM
--                        fs auth m (Control.Monad.IO.Unlift.UnliftIO (AuthM fs auth m))’
--       NB: We cannot know what roles the parameters to ‘m’ have;
--         we must assume that the role is nominal
--     • When deriving the instance for (MonadUnliftIO (AuthM fs auth m))
--    |
-- 82 |   deriving (Functor, Applicative, Monad, MonadReader (Auth auth), MonadIO, MonadThrow, MonadCatch, MonadMask, MonadUnliftIO)
--    |                                                                                                               ^^^^^^^^^^^^^

Примечание. Я понимаю, что первая ошибка о >>= не имеет ничего общего с ошибкой о MonadUnliftIO. Я подтвердил, что предупреждений об отсутствии >>= при выключенном DeriveAnyClass нет.

Думаю, мне нужно написать экземпляр для MonadUnliftIO самому, потому что компилятор, вероятно, не сможет понять это при наличии newtype И фантомной переменной типа. Однако я просто не могу понять, как определить _ 13_ для моего типа, указанного выше.

Попытка 1 при минимальном фрагменте кода

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Try13 where

import Control.Monad.Reader
import UnliftIO
import Control.Monad.Catch

data Auth = Auth

newtype AuhM m a = AuthM (ReaderT Auth m a)
  deriving(Functor, Applicative, Monad, MonadReader Auth, MonadIO, MonadThrow, MonadCatch, MonadMask, MonadUnliftIO)

--     • Couldn't match representation of type ‘m (UnliftIO (AuhM m))’
--                                with that of ‘m (UnliftIO (ReaderT Auth m))’
--         arising from the coercion of the method ‘askUnliftIO’
--           from type ‘ReaderT Auth m (UnliftIO (ReaderT Auth m))’
--             to type ‘AuhM m (UnliftIO (AuhM m))’
--       NB: We cannot know what roles the parameters to ‘m’ have;
--         we must assume that the role is nominal
--     • When deriving the instance for (MonadUnliftIO (AuhM m))
--    |
-- 12 |   deriving(Functor, Applicative, Monad, MonadReader Auth, MonadIO, MonadThrow, MonadCatch, MonadMask, MonadUnliftIO)
--    |                                                                                                       ^^^^^^^^^^^^^
-- 

person Saurabh Nanda    schedule 25.07.2019    source источник
comment
Не могли бы вы предоставить определения Auth и FeatureFlag или упростить этот код до минимальной части, которая воспроизводит эту проблему? Сейчас невозможно легко воспроизвести ошибку, которую вы получаете, и я уверен, что вы могли бы вырезать несколько нерелевантных частей.   -  person Bartek Banachewicz    schedule 25.07.2019
comment
@BartekBanachewicz Я добавил к вопросу минимальный фрагмент кода. В качестве первого шага я смог получить ошибку только с новым типом (т.е. без фантомного типа).   -  person Saurabh Nanda    schedule 25.07.2019
comment
Не связано с вашим вопросом, но я думаю, что ваш newtype может быть неправильно написан AuhM vs AuthM?   -  person lehins    schedule 25.07.2019
comment
@lehins да, хороший улов :)   -  person Saurabh Nanda    schedule 25.07.2019


Ответы (2)


План:

  • Как реализовать MonadUnliftIO вручную.
  • Как newtype-derive MonadUnliftIO.

Реализовать явно

newtype AuthM m a = AuthM { unAuthM :: ReaderT Auth m a }
  deriving ...

instance MonadUnliftIO m => MonadUnliftIO (AuthM m) where
  askUnliftIO = AuthM (fmap (\(UnliftIO run) -> UnliftIO (run . unAuthM)) askUnliftIO)
  withRunInIO go = AuthM (withRunInIO (\k -> go (k . unAuthM)))

В этом нет ничего волшебного; вот как вы можете получить определение askUnliftIO. Мы хотим обернуть существующий экземпляр MonadUnliftIO на ReaderT Auth m. Используя этот экземпляр, мы имеем:

askUnliftIO :: ReaderT Auth m (UnliftIO (ReaderT Auth m))

И мы ищем

_ :: AuthM m (UnliftIO (AuthM m))

Другими словами, мы хотим заменить два вхождения ReaderT Auth на AuthM. Внешний - это просто:

AuthM askUnliftIO :: AuthM m (UnliftIO (ReaderT Auth m))

Чтобы добраться до внутреннего, мы можем использовать fmap, и тогда проблема заключается в том, чтобы найти правильную функцию UnliftIO (ReaderT Auth m) -> UnliftIO (AuthM m).

fmap _ (AuthM askUnliftIO) :: AuthM m (UnliftIO (AuthM m))

-- provided --

_ :: UnliftIO (ReaderT Auth m) -> UnliftIO (AuthM m)

Теперь мы ищем функцию, а библиотека не предоставляет никаких функций для UnliftIO, поэтому единственный способ начать - это лямбда с сопоставлением с образцом, а поскольку результат функции - UnliftIO, мы также можем начать с конструктора :

(\(UnliftIO run) -> UnliftIO (_ :: forall a. AuthM m a -> IO a) :: UnliftIO (AuthM m))
  :: UnliftIO (ReaderT Auth m) -> UnliftIO (AuthM m)

-- where (run :: forall a. ReaderT Auth m a -> IO a)

Здесь мы видим, что run и дыра различаются только своими аргументами. Мы можем преобразовать аргумент функции с помощью композиции функции, мы заполняем дыру run . _, содержащую новую дыру:

(\(UnliftIO run) -> UnliftIO (run . (_ :: AuthM m a -> ReaderT Auth m a)
                                :: forall a. AuthM m a -> IO a
                             )
) :: UnliftIO (ReaderT Auth m) -> UnliftIO (AuthM m)

Эта дыра, наконец, заполняется деструктором \(AuthM u) -> u, иначе. unAuthM. Сложите все вместе:

fmap (\(UnliftIO run) -> UnliftIO (run . unAuthM)) (AuthM askUnliftIO)

Обратите внимание, что fmap f (AuthM u) = AuthM (fmap f u) (по определению fmap для AuthM), именно так вы получаете версию наверху. Переписывать или нет - дело вкуса.

Большинство этих шагов можно выполнить с помощью типизированных дыр GHC. В начале, когда вы пытаетесь найти правильную форму для выражения, есть некоторые недостатки, но также может быть способ использовать типизированные отверстия, чтобы помочь с этой частью исследования.

Обратите внимание, что для этого не требуется никаких знаний о назначении askUnliftIO и AuthM. Это 100% бессмысленная упаковка / разворачивание между AuthM и ReaderT, то есть 100% шаблонный код, который можно автоматизировать, что является темой следующего раздела.

Производный

Техническое объяснение того, почему вывод не работает. Расширение GeneralizedNewtypeDeriving пытается принудить ReaderT Auth m (UnliftIO (ReaderT Auth m)) к AuthM m (UnliftIO (AuthM m)) (в случае askUnliftIO). Однако это невозможно, если m номинально зависит от аргумента.

Нам нужно ограничение «репрезентативной роли», которое мы можем закодировать следующим образом благодаря QuantifiedConstraints, появившемуся в GHC 8.6.

{-# LANGUAGE QuantifiedConstraints, RankNTypes, KindSignatures #-}
-- Note: GHC >= 8.6

import Data.Coerce
import Data.Kind (Constraint)

type Representational m
  = (forall a b. Coercible a b => Coercible (m a) (m b) :: Constraint)
  -- ^ QuantifiedConstraints + RankNTypes               ^ KindSignatures

Таким образом, аннотируйте производный экземпляр этим ограничением:

{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving #-}

deriving instance (MonadUnliftIO m, Representational m) => MonadUnliftIO (AuthM m)

Полный фрагмент:

{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving, QuantifiedConstraints, KindSignatures, RankNTypes #-}

module Try13 where

import Control.Monad.Reader
import UnliftIO
import Control.Monad.Catch
import Data.Coerce
import Data.Kind (Constraint)

data Auth = Auth

newtype AuthM m a = AuthM { unAuthM :: ReaderT Auth m a }
  deriving(Functor, Applicative, Monad, MonadReader Auth, MonadIO, MonadThrow, MonadCatch, MonadMask)

type Representational m = (forall a b. Coercible a b => Coercible (m a) (m b) :: Constraint)

deriving instance (MonadUnliftIO m, Representational m) => MonadUnliftIO (AuthM m)

-- instance MonadUnliftIO m => MonadUnliftIO (AuthM m) where
--   askUnliftIO = AuthM (fmap (\(UnliftIO run) -> UnliftIO (run . unAuthM)) askUnliftIO)
--   withRunInIO go = AuthM (withRunInIO (\k -> go (k . unAuthM)))
person Li-yao Xia    schedule 25.07.2019
comment
Ух ты! Спасибо за проницательный ответ. Вторая часть - это некий новый уровень тип-фу, которого я до сих пор не видел. Позвольте мне поиграть с моим оригиналом и вернуться с результатами! - person Saurabh Nanda; 25.07.2019
comment
В какой версии GHC была QuantifiedConstraints представлена? я получаю Unsupported extension ошибку. - person Saurabh Nanda; 25.07.2019
comment
Он появился в GHC 8.6 downloads.haskell. org / ~ ghc / latest / docs / html / users_guide / - person Li-yao Xia; 25.07.2019

Начиная с версии 0.2.0.0 из unliftio-core, функция askUnliftIO была перемещена из класса типов, что делает возможным newtype-наследование этого экземпляра снова!

data FeatureFlag
data Auth auth

newtype AuthM (fs :: [FeatureFlag]) auth m a = AuthM
  { unAuthM :: Auth auth -> m a
  }
  deriving newtype
    ( Functor
    , Applicative
    , Monad
    , MonadReader (Auth auth)
    , MonadIO
    , MonadThrow
    , MonadCatch
    , MonadMask
    , MonadUnliftIO
    )

cf https://github.com/fpco/unliftio/issues/55

person sara    schedule 15.06.2020