Dejemos que Monad sea más violenta_Haskell notes 11

Escribí que entré
en contacto por primera vez con IO Monad, y más tarde me enteré de Maybe Monad y List Monad. De hecho, hay muchas Mónadas (como Writer Monad, Reader Monad, State Monad, etc.) ubicadas en el paquete mtl, que se pueden ver a través del comando ghc-pkg :


$ ghc-pkg list | grep mtl
mtl-2.2.1

La plataforma PSHaskell incluye el paquete mtl de forma predeterminada, no es necesario instalarlo manualmente

1.
Proceso de ejecución de seguimiento de Writer Monad
Al comprender el algoritmo recursivo, existe una fuerte demanda, es decir, registrar el proceso intermedio. Así era como se hacía en ese momento:


import Debug.Trace
d f = trace ("{" ++ show f ++ "}") f

Agregue el registro a través de trace :: String -> a -> a:


When called, trace outputs the string in its first argument, before returning the second argument as its result.

Acepte una cadena y un valor, imprima la cadena y devuelva el valor de entrada tal como está, por ejemplo:


> x `add` y = trace (show x ++ " + " ++ show y) (x + y)
> add 3 $ add 1 2
1 + 2
3 + 3
6

El proceso de ejecución se ha rastreado con éxito, pero el código fuente necesita ser modificado. Es demasiado problemático cambiar cada función a una versión con registros, así que use la función de herramienta d para hacerlo (lo que quiera saber):


> d (1 + 2) + 3
{3}
6

Tome el clásico ordenamiento rápido de Haskell como ejemplo:


quickSort [] = []
quickSort (x:xs) = quickSort ltX ++ [x] ++ quickSort gtX
  where ltX = [a | a <- xs, a <= x]
        gtX = [a | a <- xs, a > x]

Agregue un registro, vea el proceso de procesamiento a la izquierda (d ltX):


quickSortWithLog [] = []
quickSortWithLog (x:xs) = quickSortWithLog (d ltX) ++ [x] ++ quickSortWithLog gtX
  where ltX = [a | a <- xs, a <= x]
        gtX = [a | a <- xs, a > x]

Pruébalo:


> quickSortWithLog [9, 0, 8, 10, -5, 2, 13, 7]
{[0,8,-5,2,7]}
{[-5]}
{[]}
[-5,0{[2,7]}
{[]}
,2{[]}
,7,8,9{[]}
,10{[]}
,13]

Según el registro, el lado izquierdo del primer viaje es [0,8, -5,2,7] (el pivote es 9) y el siguiente es [-5] (el pivote es 0), y luego a la izquierda está [] (el pivote es -5), el primer viaje al otro lado de (0) es [2,7] a la izquierda, y el izquierdo es [] cuando continúa. El lado izquierdo de la matriz original se procesa y el lado derecho es similar, por lo que no lo repetiré.

Apenas se puede resolver el problema, pero hay varias deficiencias:

La salida del registro se mezcla en los resultados, el registro no parece muy intuitivo

El registro afectará la salida del resultado original, falta de aislamiento

Solo se puede imprimir, no hay forma de recolectarlo para su posterior procesamiento, no es lo suficientemente flexible

Entonces, si desea realizar un seguimiento del proceso de ejecución, ¿existe una forma más elegante?


¿Puede Writer mantener automáticamente un registro de operaciones durante la computación?

Si considera la información de registro adicional como contexto, parece que tiene algo que ver con Monad. Por ejemplo, puede recopilar registros automáticamente (mantener este contexto) mientras el valor está involucrado en el cálculo.

Este es el origen de Writer:

Writer agrega un contexto de valor agregado, al igual que log

Writer nos permite recopilar todos los registros mientras calculamos, integrar un registro y adjuntarlo al resultado.

El escritor se ve así:


type Writer w = WriterT w Identity
newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }

Desde la perspectiva de la declaración de tipo, Writer es un contenedor para tuplas ((a, w)), y m se designa como Identidad:


newtype Identity a = Identity { runIdentity :: a }
instance Applicative Identity where
  pure     = Identity
instance Monad Identity where
  m >>= k  = k (runIdentity m)

Parece inútil, mire más de cerca: se declara un tipo de empaquetado llamado Identity, y también se implementa Monad. El comportamiento de return es envolver el valor dado, y el comportamiento de >> = es aplicar el lado derecho al valor envuelto a la izquierda. función. Todavía no he encontrado ningún uso ... De hecho, es equivalente a id :: a -> a en el mundo Monad, que puede envolver un valor en un Monad para participar en el cálculo. Además, no hace nada. En términos de escenarios de aplicación, es como id Lo mismo, a veces necesitas una Monad que no haga nada (al igual que a veces necesitas una función que no haga nada)


Identity allows us to define just monad transformers and then define their corresponding monads just as SomeT Identity.

PD Para obtener más información sobre la identidad, consulte ¿Por qué es útil la mónada de identidad?

A continuación, observe la implementación de Monad de WriterT:


instance (Monoid w, Monad m) => Monad (WriterT w m) where
    return a = writer (a, mempty)
    m >>= k  = WriterT $ do
        ~(a, w)  <- runWriterT m
        ~(b, w') <- runWriterT (k a)
        return (b, w `mappend` w')
    fail msg = WriterT $ fail msg

writer :: (Monad m) => (a, w) -> WriterT w m a
writer = WriterT . return

Donde a es el tipo del valor yw es el tipo del Monoide adicional. Desde la perspectiva de la implementación de Monad, saque el valor ay la información adicional w de la izquierda, aplique la función de la derecha a a, y saque el valor by la información adicional w 'del resultado, el valor del resultado es b, y la información adicional es w mappendw', Finalmente, envuelva el resultado con retorno para devolver el valor de tipo m como parámetro del constructor de valor WriterT

Tenga en cuenta que el punto clave es mappendbuscar información adicional mientras se calcula el valor , a fin de preservar el contexto del registro y lograr el mantenimiento automático del registro de operaciones.

Si m = Identity (Writer así se define), el proceso específico es equivalente a:


(WriterT (Identity (a, w))) >>= f = let (WriterT (Identity (b, w'))) = f a in WriterT (Identity (b, w `mappend` w'))

El ~ en PS ~ (a, w) significa coincidencia de patrones perezosos (consulte Haskell / Laziness | Coincidencia de patrones perezosos para obtener más detalles):


prepending a pattern with a tilde sign delays the evaluation of the value until the component parts are actually used. But you run the risk that the value might not match the pattern — you’re telling the compiler ‘Trust me, I know it’ll work out’. (If it turns out it doesn’t match the pattern, you get a runtime error.)

Pruébalo:


> runWriterT $ (return 1 :: WriterT String Identity Int)
Identity (1,"")
> let (WriterT (Identity (a, w))) = WriterT (Identity (1,"")) in (a, w)
(1,"")
> (WriterT (Identity (1,"abc")) :: WriterT String Identity Int) >>= \a -> return (a + 1)
WriterT (Identity (2,"abc"))

Writer no expone directamente el constructor de valor, pero Writer se puede construir a través de la función de escritor, por ejemplo:


> writer (1,"abc") :: Writer String Int
WriterT (Identity (1,"abc"))

Además, se puede describir con una notación do más clara:


do {
  a <- writer (1, "one")
  b <- writer (2, "two")
  return (a + b)
} :: Writer String Int

-- 得到的结果
WriterT (Identity (3,"onetwo"))

Los registros están pegados, use una matriz en su lugar:


do {
  a <- writer (1, ["one"])
  b <- writer (2, ["two"])
  return (a + b)
} :: Writer [String] Int

-- 得到的结果
WriterT (Identity (3,["one","two"]))

Los resultados del cálculo y los registros se pueden separar de él:


> fst . runWriter $ WriterT (Identity (3,["one","two"]))
3
> snd . runWriter $ WriterT (Identity (3,["one","two"]))
["one","two"]
> mapM_ putStrLn $ snd . runWriter $ WriterT (Identity (3,["one","two"]))
one
two

Los defectos mencionados anteriormente parecen estar perfectamente resueltos, hay otra pregunta, ¿y si solo quieres insertar un log irrelevante?

por supuesto que puede. tell se puede usar para insertar información adicional sin un valor:


tell :: MonadWriter w m => w -> m ()

Similar a imprimir en el escenario de E / S:


print :: Show a => a -> IO ()

El efecto también es
el mismo, sin valor, solo registra un dato, como Writer:


logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, [show x])

multWithLog :: Writer [String] Int
multWithLog = do
  a <- logNumber 3
  b <- logNumber 5
  tell ["*"]
  return (a*b)

Los operandos y operadores se pueden registrar en forma de expresiones de sufijo similares:


> multWithLog
WriterT (Identity (15,["3","5","*"]))

El proceso específico es equivalente a:


> writer (3, ["3"]) >>= \a -> (writer (5, ["5"])) >>= \b -> (writer ((), ["*"])) >> return (a * b) :: Writer [String] Int
WriterT (Identity (15,["3","5","*"]))

Mirando hacia atrás en el seguimiento del proceso de ejecución, la solución de Writer es agregar un contexto de registro a los valores involucrados en el cálculo y continuar manteniendo este registro durante el proceso de cálculo (a través del mappend interno), de modo que el contexto del resultado del cálculo sea completo. Registro de operaciones:

Simplemente reescribimos el valor ordinario en valor monádico, y confiamos en >> = y Writer para ayudarnos a manejar todo.

Entonces, si desea convertir una función normal en una versión con registros, simplemente empaque los parámetros (y las constantes en la operación) en Writer. En la

lista de Diferencias,

usamos List para almacenar registros, lo cual es vagamente incómodo.

Debido a que la operación ++ de List está asociada a la derecha de forma predeterminada (es decir, insertada en el encabezado de List), la eficiencia es mayor. Si inserta con frecuencia al final de la Lista, debe recorrer y construir la Lista a la izquierda cada vez, lo cual es muy ineficiente. Entonces, ¿hay una lista más eficiente?

Sí, se llama Lista de diferencias, que puede realizar una operación de adición eficiente. La idea de realización es muy inteligente:

Primero, convierta cada Lista en una función:


-- [1,2,3]
\xs -> [1,2,3] ++ xs
-- []
\xs -> [] ++ xs

Nota: El punto clave es que solo se antepone List en el cuerpo de la función, lo que garantiza la eficiencia de la operación ++ (la eficiencia de inserción del cabezal es muy alta)

Naturalmente, la operación de adición de List se convierte en una combinación de funciones:


f = \xs -> "dog" ++ xs
g = \xs -> "meat" ++ xs
f `append` g = \xs -> f (g xs)
-- 展开f `append` g,得到
\xs -> "dog" ++ ("meat" ++ xs)

Pase una lista vacía a la lista de diferencias (una función que acepta un parámetro de lista) para obtener la lista de resultados:

> f `append` g $ []
"dogmeat"

Entonces, defina DiffList así:


newtype DiffList a = DiffList { getDiffList :: [a] -> [a] }

toDiffList xs = DiffList (xs++)
fromDiffList (DiffList f)
]

Entonces date cuenta de Monoid:


instance Monoid (DiffList a) where
  mempty = DiffList (\xs -> [] ++ xs)
  (DiffList f) `mappend` (DiffList g) = DiffList (\xs -> f (g xs))

Pruébalo:


> fromDiffList $ (toDiffList [1, 2, 3]) `mappend` (toDiffList [3, 2, 1])
[1,2,3,3,2,1]

Entonces, ¿cuál es la diferencia de rendimiento? Simplemente prueba:


countdown n = if (n == 0) then do {tell ["0"]} else do {countdown (n - 1); tell [show n]} :: Writer [String] ()
countdown' n = if (n == 0) then do {tell (toDiffList ["0"])} else do {countdown' (n - 1); tell (toDiffList [show n])} :: Writer (DiffList String) ()

En el escenario de la cuenta regresiva, Writer se usa para registrar cada número en el proceso de cuenta regresiva. La diferencia es que la cuenta regresiva usa List para mantener registros, mientras que la cuenta regresiva usa DiffList

Principalmente por un tiempo, como 500.000 recuentos:


> mapM_ putStrLn . snd . runWriter $ countdown 500000
> mapM_ putStrLn . fromDiffList . snd . runWriter $ countdown' 500000

En términos de eficiencia visible a simple vista, la cuenta regresiva se ejecuta cada vez más lentamente, la cuenta regresiva siempre se produce sin problemas

PS Para obtener más métodos de prueba científicos, consulte Rendimiento | 5 Bibliotecas de evaluación comparativa

Para la implementación completa de PSDiffList, consulte spl / dlist

PD Además, la plataforma Haskell no viene con un paquete dlist de forma predeterminada (por lo que no hay una DiffList incorporada de forma predeterminada), debe instalarla manualmente, consulte el comienzo de este artículo.

2.
Reader Monad Reader Monad es en realidad Function Monad, la función también es Monad, ¿cómo entender esto?

También se puede pensar que una función contiene un contexto. Este contexto significa que estamos esperando un cierto valor, que aún no ha aparecido, pero sabemos que lo usaremos como parámetro de función y llamaremos a la función para obtener el resultado.

Es decir (->) r, he sabido antes que es un Functor y un Aplicativo. Resultó ser Monad, y su implementación específica es la siguiente:


instance Monad ((->) r) where
  f >>= k = \ r -> k (f r) r

No hay implementación adicional de devolución, por lo que es puramente Aplicativo:


instance Applicative ((->) a) where
  pure = const

const                   :: a -> b -> a
const x _               =  x

Acepta un valor arbitrario (x) y devuelve una función (_ -> x) que acepta un parámetro, ignora y devuelve el valor arbitrario pasado previamente. Esto se hace para envolver un valor en el contexto de la función para que pueda participar en las operaciones de la función:

La única forma de que una función tenga un cierto valor fijo es hacer que ignore por completo sus parámetros.

= Desde el punto de vista de la implementación, se generará una nueva función (\ r -> k (fr) r), que acepta un parámetro (r), que será pasado al valor monádico de la izquierda (también una función, f), Luego, pase el valor de retorno (fr) a la función (k) de la derecha, devuelva un valor monádico (sigue siendo una función, k (fr)), acepte el parámetro (r) y finalmente devuelva un valor monádico

PS Parece extraño pasar r como parámetro a f. Esto se debe a que f es un valor monádico y tiene un contexto (una función) .Para sacar el valor del contexto de la función, se debe alimentar con parámetros. De manera similar para k (fr), ingrese el valor tomado de f a k, devuelva algo con un contexto de función y finalmente alimente el parámetro r para obtener el resultado final

Bien, la función ahora es Monad, ¿de qué sirve?


palyAGame x = do
  f <- (/0.5) . (subtract 3.9343) . (*5) . (+52.8)
  g <- (*10)
  return (f - g)

Piense en un número en su corazón, úselo para sumar 52.8, multiplique por 5, luego reste 3.9343, divida por 0.5 y finalmente reste diez veces el número que desea

Mirando el proceso de traducción, dividimos la fórmula de cálculo en dos partes relacionadas con x, y finalmente hacemos la resta. Pruébelo primero:


> palyAGame 201807 01
520.1314

Tenga en cuenta que además de x, también ingresamos un parámetro más, porque el resultado está envuelto en return _ -> x, por lo que debe completar cualquier parámetro para obtener el resultado

Recuerde el papel real de Function Monad:

Pegue todas las funciones juntas para hacer una función grande, y luego alimente los parámetros de esta función a todas las funciones, lo que significa sacar sus valores futuros

PD "Toma su valor futuro" se refiere a la descripción final f-g, traviesa

De hecho, una descripción más científica es esta:


The Reader monad (also called the Environment monad). Represents a computation, which can read values from a shared environment, pass values from function to function, and execute sub-computations in a modified environment.

Entre ellos, el entorno compartido se refiere a mantener vinculaciones de variables, es decir, cada valor monádico en el bloque do comparte los parámetros de esta gran función. El significado de pasar valores entre funciones es similar a "sacar sus valores futuros", como para manipular Los subcálculos en el entorno anterior pueden referirse a escenarios de aplicación como la inyección de dependencia (consulte ¿Cuál es el propósito de Reader Monad?)

PS puede leer valores del entorno compartido, por eso se llama Reader Monad

3.
Además del entorno de seguimiento y uso compartido de registros, State Monad también tiene el problema más común de mantenimiento del estado

Sin embargo, hay algunas áreas donde los problemas dependen fundamentalmente de los cambios de estado a lo largo del tiempo. Aunque también podemos escribir programas de este tipo en Haskell, a veces es bastante doloroso escribir. Es por eso que Haskell agregó la función State Monad. Esto nos permite manejar fácilmente problemas con estado en Haskell y mantener puras otras partes del programa.

Este es el significado de State Monad. Quiero facilitar el mantenimiento del estado sin afectar otras partes puras.

Desde una perspectiva de implementación, State Monad es una función que acepta un estado, devuelve un valor y un nuevo estado.


s -> (a,s)
-- 即
state -> (result, newState)

Similar a Writer Monad, el valor del resultado se separa de la información adicional adjunta al contexto (aquí, newState) y se organiza en dos tuplas.

La implementación específica es la siguiente:


type State s = StateT s Identity
newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }

instance (Monad m) => Monad (StateT s m) where
  return a = StateT $ \ s -> return (a, s)
  m >>= k  = StateT $ \ s -> do
      ~(a, s') <- runStateT m s
      runStateT (k a) s'
  fail str = StateT $ \ _ -> fail str

return pone el valor recibido en una función de operación de estado de s -> (a, s), y luego lo envuelve en StateT

= Saque la función de operación de estado de la izquierda, pase s para sacar el nuevo estado s 'y el resultado del cálculo a, luego aplique la función de la derecha al resultado de cálculo a, y obtenga un valor monádico, y luego saque la función de operación de estado dentro a través de runStateT , Aplicar al nuevo estado s ', obtener la (a, s) dos tuplas y regresar. De esta forma, el tipo de lambda es el estándar s -> (a, s), y finalmente, conecta StateT para construir un nuevo valor monádico

State Monad puede hacer que las operaciones de mantenimiento del estado se expresen de manera más sucinta, entonces, ¿hasta qué punto esto puede simplificar las operaciones de mantenimiento del estado? Veamos el ejemplo del número aleatorio

Números aleatorios y mónada de estado
En lo que respecta a los escenarios, los números aleatorios deben mantener el estado (semilla de número aleatorio), que es muy adecuado para procesar con State Monad

Específicamente, en el escenario de números aleatorios anterior, los números aleatorios se generaron cambiando diferentes semillas de números aleatorios a la función aleatoria:


random :: (Random a, RandomGen g) => g -> (a, g)

P.ej:


> random (mkStdGen 7) :: (Bool, StdGen)
(True,320112 40692)

Para generar 3 números aleatorios, el método más directo es:


random3'' = let (r1, g1) = random (mkStdGen 7); (r2, g2) = random g1; (r3, g3) = random g2 in [(r1, g1), (r2, g2), (r3, g3)] :: [(Bool, StdGen)]
> random3''
[(True,320112 40692),(False,2071543753 1655838864),(True,33684305 2103410263)]

Por supuesto, se puede encapsular de forma un poco más elegante:


random3 i = collectNext $ collectNext $ [random $ mkStdGen i]
  where collectNext xs@((i, g):_) = [random g] ++ xs

> reverse $ random3 7 :: [(Bool, StdGen)]
[(True,320112 40692),(False,2071543753 1655838864),(True,33684305 2103410263)]

Parece más cómodo, pero todavía se siente molesto. Cambiar a State Monad:


randomSt :: (RandomGen g, Random a) => State g a
randomSt = state random

threeCoins :: State StdGen (Bool,Bool,Bool)
threeCoins = do
  a <- randomSt
  b <- randomSt
  c <- randomSt
  return (a,b,c)

Parece bastante elegante, la función aleatoria solo satisface la forma de s -> (a, s), así que tírela a state :: MonadState sm => (s -> (a, s)) -> ma para construir el valor de State Monad. . Pruébalo:


> runState threeCoins (mkStdGen 7)
((True,False,True),33684305 2103410263)

El estado s en el resultado (a, s) es la cuarta semilla de número aleatorio (contando el mkStdGen 7 entrante), porque esta semilla es el último estado (el resto de los estados intermedios se pierden)

Sí, Moand simplifica un escenario general de mantenimiento del estado. State Monad nos ayuda a completar automáticamente el mantenimiento del estado intermedio, haciendo que todo sea lo más simple posible

4. Error Monad
Finalmente, el manejo de excepciones también es un escenario importante, que también se puede simplificar con la ayuda de Monad


Building computations from sequences of functions that may fail or using exception handling to structure error handling.

Ya sabemos que Maybe es una mónada, que se puede usar para expresar cálculos que pueden producir errores. ¿Está bien?

por supuesto. De hecho, Either es una instancia de Error Monad (también llamado Exception mónada):


class (Monad m) => MonadError e m | m -> e where
    throwError :: e -> m a
    catchError :: m a -> (e -> m a) -> m a

instance MonadError e (Either e) where
    throwError             = Left
    Left  l `catchError` h = h l
    Right r `catchError` _ = Right r

(De Control.Monad.Excepto)

PD Tenga en cuenta que Control.Monad.Error y Control.Monad.Trans.Error están desactualizados, se recomienda utilizar Control.Monad.Except, consulte Control.Monad.Error para obtener más detalles

No hay nada que decir sobre throwError. Se acuerda que Izquierda x representa un error (Derecha x representa un resultado normal), catchError puede usarse para detectar el error, y si no ocurre ningún error, simplemente no haga nada. Entonces, el patrón general es este:


do { action1; action2; action3 } `catchError` handler

P.ej:


do {
  x <- (Left "error occurred")
  return x
} `catchError` error

Detecte el error y luego tírelo directamente con el error, por lo que aparece un error:


*** Exception: error occurred
上面do block中的操作实际上依赖的是Either自身的Monad实现:

instance Monad (Either e) where
    Left  l >>= _ = Left l
    Right r >>= k = k r
等价于:

> ((Left "error occurred") >>= (\x -> return x) :: Either String Int) `catchError` error
*** Exception: error occurred
> ((throwError "error occurred") >>= (\x -> return x) :: Either String Int) `catchError` error
*** Exception: error occurred

En otras palabras, Error Monad solo implementa throwError y catchError adicionales para aquellos tipos que pueden expresar errores (como Either, Maybe), sin hacer modificaciones intrusivas, pero con estos dos comportamientos, podemos manejar los errores con elegancia. Esto es diferente de las mónadas presentadas anteriormente.

Además de Either, otra instancia importante de MonadError es ExceptT (por supuesto, hay más de estos 2):


instance Monad m => MonadError e (ExceptT e m) where
    throwError = ExceptT.throwE
    catchError = ExceptT.catchE

Después de terminar, puede usar el lanzamiento y la captura definidos en ExceptT, por lo que ExceptT puede agregar capacidades de manejo de errores a otras Mónadas. La implementación es la siguiente:


newtype ExceptT e m a = ExceptT (m (Either e a))

runExceptT :: ExceptT e m a -> m (Either e a)
runExceptT (ExceptT m) = m

throwE :: (Monad m) => e -> ExceptT e m a
throwE = ExceptT . return . Left

catchE :: (Monad m) =>
    ExceptT e m a               -- ^ the inner computation
    -> (e -> ExceptT e' m a)    -- ^ a handler for exceptions in the inner
                                -- computation
    -> ExceptT e' m a
m `catchE` h = ExceptT $ do
    a <- runExceptT m
    case a of
        Left  l -> runExceptT (h l)
        Right r -> return (Right r)

De hecho, se trata de envolver el valor de otra Mónada (a) en Either y agregar la información de excepción (e), mientras se asegura que el tipo de Mónada sea correcto (aún m)

ThrowE convierte el mensaje de error en Either with Left, luego lo envuelve en la Monad deseada con return, y finalmente lo conecta a ExceptT para construir el valor ExceptT

catchE saca el Either de la izquierda a través de runExceptT para ver si hay un error, y luego decide si lo lanza al manejador derecho

Todo resuelto, ahora intente agregar manejo de excepciones a las operaciones de E / S:


getString :: ExceptT String IO String
getString = do {
  line <- liftIO getLine;
  if (null line) then
    throwError "empty input"
  else
    return line
} `catchError` (\e -> return "Error occurred, use default string")

safeIO = do
  -- 放心用Right匹配,因为getString有错误处理
  (Right line) <- runExceptT getString
  putStrLn line

Tenga en cuenta que liftIO :: MonadIO m => IO a -> ma se usa para elevar IO al contexto de Monad requerido (ExceptT en el ejemplo anterior):

Levante un cálculo de la mónada IO.

Y runExceptT se usa para sacar el empaquetado en Except, por ejemplo:


> runExceptT (liftIO getLine :: ExceptT String IO String)
aaa
Right "aaa"

Pruébalo:

> safeIO

Error occurred, use default string
> safeIO
abc
abc

Como se esperaba, si la entrada es ilegal, use la cadena predeterminada

PS Además, Except también se define sobre la base de ExceptT:


type Except e = ExceptT e Identity

except :: Either e a -> Except e a
except m = ExceptT (Identity m)
runExcept :: Except e a -> Either e a
runExcept (ExceptT m) = runIdentity m

Ver Control.Monad.Trans.Excepto para detalles

5. El encanto de
Monad Monad puede proporcionar al cálculo algunas capacidades adicionales, tales como:

Writer Monad: puede convertir la función en una versión de registro, utilizada para rastrear el proceso de ejecución o agregar información adicional a la transformación de datos

Reader Monad: habilita una serie de funciones para trabajar juntas en un entorno compartido controlable, como leer parámetros de este entorno, leer los resultados de otras funciones, etc.

State Monad: puede mantener automáticamente el estado, adecuado para escenarios que necesitan mantener el estado, como generar una serie de números aleatorios

Error Monad: proporciona un mecanismo de manejo de errores, que puede hacer que los cálculos sean más seguros

La importancia de Monad es abstraer patrones comunes de estos escenarios comunes para simplificar las operaciones, como el mantenimiento del estado, la recopilación de registros, etc., que Monad puede completar automáticamente

Desde el punto de vista de uso, solo use el paquete Monad (sí, es así de simple), puede obtener capacidades adicionales, este es el encanto de Monad

Material de referencia
Control.Monad.Reader

Control.Monad.Error

Control.Monad.Excepto

Contacto ayqy

Supongo que te gusta

Origin blog.51cto.com/15080030/2591201
Recomendado
Clasificación