Tentando compor os esquemas de encaixe não composíveis:

1. Introdução


Em Haskell, é habitual trabalhar com efeitos como functores cujos objetos são algumas expressões pelas quais estamos interessados ​​no momento.

Quando vemos o tipo de expressão Talvez a , abstraímos da existência real de alguns a , concentrando toda a nossa atenção nesse a . A mesma história da Lista a - valores plurais de a ; Estado sa - a , dependendo de algum estado atual; Ou ea - a , que pode retornar algum erro e .

Antes de continuar, o artigo usará várias definições:

type (:=) ta = ta -- |   type (:.) tua = t (ua) -- |   type (~>) tu = forall a . ta -> ua -- |   

Por exemplo: Lista : . Talvez: = a - é fácil imaginar essa expressão, é uma lista de valores cuja existência está em questão.

Além disso, como exemplo, usaremos quatro tipos comuns: Leitor , Estado , Qualquer um , Talvez .

Composições e Transformadores


A maneira mais óbvia de aplicar mais de um efeito a uma expressão é simplesmente incorporar um ao outro, essa é a composição usual dos functores. Nas composições, os efeitos não têm efeito um sobre o outro (a menos que métodos Traversable sejam usados ​​sobre eles). E, para mesclar muitos efeitos em um, transformadores são usados. Cada método tem suas vantagens e desvantagens:

Composições:

  • Não são necessários outros tipos para construí-los
  • Não existe um método geral para mesclar efeitos com as classes Functor / Applicative / Monad
  • Tudo compõe maravilhosamente até chegar às mônadas

Transformadores:

  • Permite mesclar vários efeitos em um
  • Mas você precisa de um tipo separado (geralmente algum tipo novo )
  • Usando a elevação, você pode executar cálculos em qualquer camada da pilha de transformação.
  • Mas você não pode levar em conta os efeitos separadamente, embora existam funções especializadas

Os transformadores são diferentes das composições da embreagem (não sei como chamar de maneira diferente). Tendo alguma composição, você pode transformá-lo em um transformador e vice-versa. Os esquemas de ancoragem nos ajudarão nisso.

Esquemas de ancoragem


Se examinarmos mais de perto os tipos de transformadores de mônada, podemos identificar alguns padrões:

 newtype ReaderT rma = ReaderT { runReaderT :: r -> ma } newtype MaybeT ma = MaybeT { runMaybeT :: m (Maybe a) } newtype ExceptT ema = ExceptT { runExceptT :: m (Either ea)) } newtype StateT sma = StateT { runStateT :: s -> m (a,s) } 

Os transformadores descrevem um caso especial de como o efeito definido e indefinido atual deve ser mesclado.

Seja definido e indefinido, tente:

 Reader: r -> ua ===> (->) r :. u := a ===> t :. u := a -- t ~ (->) r Maybe: u (Maybe a) ===> u :. Maybe := a ===> u :. t := a -- t ~ Maybe Either: u (Either ea) ===> u :. Either e := a ===> u :. t := a -- t ~ Either e 

Alguns efeitos são bastante complexos e podem ser definidos através da composição de outros efeitos mais simples:

 State: s -> u (a, s) ===> (->) s :. (,) s := a ==> t :. u :. t' := a -- t ~ (->) s, t' ~ (,) s newtype State sa = State ((->) s :. (,) s := a) 

Se dermos uma olhada nos três primeiros exemplos, podemos observar padrões gerais: se no Reader , um certo efeito envolve um indefinido (leva entre parênteses, torna-se um objeto de um functor), então com Either e Talvez seja o contrário - um efeito indefinido envolve um específico. No caso do Estado, colocamos o functor entre dois efeitos definidos mais simples.

Vamos tentar expressar esses padrões nos tipos:

 newtype TU tua = TU (t :. u := a) newtype UT tua = UT (u :. t := a) newtype TUT tut' a = TUT (t :. u :. t' := a) 

Acabamos de definir esquemas de acoplamento - essa é uma composição de functores em um invólucro que indica a posição de um efeito específico e indefinido.

De fato, os métodos para transformadores cujos nomes começam com run simplesmente removem o invólucro do transformador, retornando a composição dos functores. Nós descrevemos essa classe de tipos:

 class Composition t where type Primary ta :: * run :: ta -> Primary ta 

Agora, temos uma maneira universal de executar esses circuitos:

 instance Composition (TU tu) where type Primary (TU tu) a = t :. u := a run (TU x) = x instance Composition (UT tu) where type Primary (UT tu) a = u :. t := a run (UT x) = x instance Composition (TUT tu t') where type Primary (TUT tu t') a = t :. u :. t' := a run (TUT x) = x 

E os transformadores? Aqui, você também precisará de uma classe de tipo na qual um esquema de encaixe é prescrito para um tipo específico; o método embed é declarado para elevar o efeito indefinido ao nível do transformador e construído para construir um efeito específico no transformador:

 class Composition t => Transformer t where type Schema (t :: * -> *) (u :: * -> *) = (r :: * -> *) | r -> tu embed :: Functor u => u ~> Schema tu build :: Applicative u => t ~> Schema tu type (:>) tua = Transformer t => Schema tua 

Agora resta declarar as instâncias, comece com Maybe e Either :

 instance Transformer Maybe where type Schema Maybe u = UT Maybe u embed x = UT $ Just <$> x build x = UT . pure $ x instance Transformer (Either e) where type Schema (Either e) u = UT (Either e) u embed x = UT $ Right <$> x build x = UT . pure $ x 

Criaremos nosso próprio tipo para o Reader , pois ele não está na base . E ele também precisa de uma instância da classe Composition , pois é um wrapper para o functor de seta:

 newtype Reader ea = Reader (e -> a) instance Composition (Reader e) where type Primary (Reader e) a = (->) ea run (Reader x) = x instance Transformer (Reader e) where type Schema (Reader e) u = TU ((->) e) u embed x = TU . const $ x build x = TU $ pure <$> run x 

Faça algo semelhante com State :

 newtype State sa = State ((->) s :. (,) s := a) instance Composition (State s) where type Primary (State s) a = (->) s :. (,) s := a run (State x) = x instance Transformer (State s) where type Schema (State s) u = TUT ((->) s) u ((,) s) embed x = TUT $ \s -> (s,) <$> x build x = TUT $ pure <$> run x 

Como exemplo


Resta testar isso nos problemas do mundo real - como exemplo, escreveremos um programa que calcula o posicionamento correto de vários tipos de colchetes.

Definir tipos para parênteses: eles podem ser abertos e fechados; e também tem estilos diferentes:

 data Shape = Opened | Closed data Style = Round | Square | Angle | Curly 

Outros símbolos do nosso programa não são interessantes:

 data Symbol = Nevermind | Bracket Style Shape 

Também definimos uma lista de erros que nosso programa pode encontrar:

 data Stumble = Deadend (Int, Style) --     | Logjam (Int, Style) --     | Mismatch (Int, Style) (Int, Style) --       

Quais efeitos nosso programa precisa? Precisamos manter uma lista de colchetes que aguardam verificação e precisamos parar no primeiro erro encontrado. Nós fazemos um transformador:

 State [(Int, Style)] :> Either Stumble := () 

O algoritmo é simples: passamos pela estrutura com colchetes indexados, se após a passagem não encontramos um erro e ainda temos os colchetes no estado, o colchete aberto não tem um colchete fechado:

 checking :: Traversable t => t (Int, Symbol) -> Either Stumble () checking struct = run (traverse proceed struct) [] >>= \case (s : _, _) -> Left . Logjam $ s where ([], _) -> Right () 

Lembramos de qualquer colchete aberto, comparamos qualquer fechado com o último aberto lembrado:

 proceed :: (Int, Symbol) -> State [(Int, Style)] :> Either Stumble := () proceed (_, Nevermind) = pure () proceed (n, Bracket style Opened) = build . modify . (:) $ (n, style) procceed (n, Bracket closed Closed) = build get >>= \case []-> embed $ Left . Deadend $ (n, closed) ((m, opened) : ss) -> if closed /= opened then embed . Left $ Mismatch (m, opened) (n, closed) else build $ put ss where 

Conclusão


Usando esquemas de encaixe, com alguma composição de functores, podemos transformá-los em transfômeros e vice-versa. Infelizmente, esse truque não funcionará com a mãe das mônadas - sequelas. E tudo porque eles não podem ser imaginados como uma composição de functores, mas é possível como uma composição de profunctors ... E, no entanto, essa é uma história completamente diferente.

Código da biblioteca no Github | Documentação de Hackage | Exemplo de parênteses

Source: https://habr.com/ru/post/pt467683/


All Articles