-
QualifiedDo -
- Since:
-
9.0.1
Разрешает использование квалифицированной
doнотации.
QualifiedDo позволяет квалифицировать do блок с именем модуля, чтобы контролировать, какие операции использовать для монадических комбинаторов, к которым do нотация выполняет разбор. Когда -XQualifiedDo включено, вы можете *квалифицировать* do нотацию, написав modid.do, где modid — это имя модуля в области видимости:
{-# LANGUAGE QualifiedDo #-}
import qualified Some.Module.Monad as M
action :: M.SomeType a
action = M.do x <- u
res
M.return x
Дополнительное имя модуля (здесь M) называется квалификатором выражения do.
Неквалифицированная do синтаксическая конструкция удобна для написания монадического кода, но она работает только для типов данных, которые предоставляют экземпляр типа Monad класса. Существуют и другие типы, которые являются «подобными монадам», но не могут предоставить экземпляр Monad (например, индексированные монады, градуированные монады или относительные монады), но они все еще могут использовать синтаксис do, если бы он не был жёстко связан с методами Monad класса. -XQualifiedDo предназначено для настройки синтаксиса do в этом отношении. Оно позволяет комбинировать do блоки разных типов с подходящими операциями для каждого случая:
{-# LANGUAGE QualifiedDo #-}
import qualified Control.Monad.Linear as L
import MAC (label, box, runMAC)
import qualified MAC as MAC
f :: IO ()
f = do
x <- runMAC $ -- (Prelude.>>=)
-- (runMAC $
MAC.do --
d <- label "y" -- label "y" MAC.>>= \d ->
box $ --
-- (box $
L.do --
r <- L.f d -- L.f d L.>>= \r ->
L.g r -- L.g r L.>>
L.return r -- L.return r
-- ) MAC.>>
MAC.return d -- (MAC.return d)
-- )
print x -- (\x -> print x)
Семантика do нотации с -XQualifiedDo такова:
-
Выражение
x <- uиспользует(M.>>=)M.do { x <- u; stmts } = u M.>>= \x -> M.do { stmts } -
Выражение
uиспользует(M.>>)M.do { u; stmts } = u M.>> M.do { stmts } -
Выражение
pat <- uиспользуетM.failдля случая ошибки, если такой случай нуженM.do { pat <- u; stmts } = u M.>>= \case { pat -> M.do { stmts } ; _ -> M.fail "…" }Если шаблон не может завершиться ошибкой, то нам не нужно использовать
M.fail.M.do { pat <- u; stmts } = u M.>>= \case pat -> M.do { stmts } -
Разбор
-XApplicativeDoиспользуетM.fmap,(M.<*>), иM.join(после группировки applicative-do)M.do { (x1 <- u1 | … | xn <- un); M.return e } = (\x1 … xn -> e) `M.fmap` u1 M.<*> … M.<*> un M.do { (x1 <- u1 | … | xn <- un); stmts } = M.join ((\x1 … xn -> M.do { stmts }) `M.fmap` u1 M.<*> … M.<*> un)
Обратите внимание, что M.join необходимо только в том случае, если конечное выражение не идентифицируется как return. При включенном -XQualifiedDo, -XApplicativeDo ищет только квалифицированные return/pure в квалифицированном блоке do.
-
При включенном
-XRecursiveDo,recиmdoблоки используютM.mfixиM.return:M.do { rec { x1 <- u1; … ; xn <- un }; stmts } = M.do { (x1, …, xn) <- M.mfix (\~(x1, …, xn) -> M.do { x1 <- u1; …; xn <- un; M.return (x1, …, xn)}) ; stmts }
Если имя M.op требуется процессом разбора (и только если оно требуется!), но имя не находится в области видимости, это сообщается как ошибка.
Типы операций, выбранных для разбора, должны создавать выражение, которое принимается типовой проверкой. Но помимо этого, к типам не предъявляются конкретные требования.
Если с включенным -XQualifiedDo, квалификатор не указан, он по умолчанию использует операции, определенные в Prelude, или, если включен -XRebindableSyntax, любые операции, находящиеся в области видимости.
Обратите внимание, что операции, которые нужно квалифицировать, должны находиться в области видимости для работы QualifiedDo. Например, import MAC (label) в примере выше приведет к ошибке, так как MAC.>>= и MAC.>> не будут находиться в области видимости.
6.2.5.1. Примеры
-XQualifiedDo не влияет на return в монадической do нотации.
import qualified Some.Monad.M as M
boolM :: (a -> M.M Bool) -> b -> b -> a -> M.M b
boolM p a b x = M.do
px <- p x -- M.>>=
if px then
return b -- Prelude.return
else
M.return a -- M.return
-XQualifiedDo не влияет на явное (>>=) в монадической do нотации.
import qualified Some.Monad.M as M
import Data.Bool (bool)
boolMM :: (a -> M.M Bool) -> M b -> M b -> a -> M.M b
boolMM p ma mb x = M.do
p x >>= bool ma mb -- Prelude.>>=
Вложенные do блоки не влияют друг на друга.
import qualified Some.Monad.M as M
f :: M.M SomeType
f = M.do
x <- f1 -- M.>>=
f2 (do y <- g1 -- Prelude.>>=
g2 x y)
where
f1 = ...
f2 m = ...
g1 = ...
g2 x y = ...
Тип (>>=) также может быть изменён, как показано здесь для градуированной монады:
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeFamilies #-}
module Control.Monad.Graded (GradedMonad(..)) where
import Data.Kind (Constraint)
class GradedMonad (m :: k -> * -> *) where
type Unit m :: k
type Plus m (i :: k) (j :: k) :: k
type Inv m (i :: k) (j :: k) :: Constraint
(>>=) :: Inv m i j => m i a -> (a -> m j b) -> m (Plus m i j) b
return :: a -> m (Unit m) a
-----------------
module M where
import Control.Monad.Graded as Graded
g :: GradedMonad m => a -> m SomeTypeIndex b
g a = Graded.do
b <- someGradedFunction a Graded.>>= someOtherGradedFunction
c <- anotherGradedFunction b
Graded.return c