-
QualifiedDo -
- С версии:
-
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