Mecanismos Internos de Lenses em Haskell: Uma Análise Técnica

Para compreender profundamente o funcionamento da biblioteca lens em Haskell, é essencial observar como as funções fundamentais se expandem durante a avaliação. Abaixo, exploramos as definições básicas e realizamos a expansão manual de operações comuns.

Definições Base e Operações Fundamentais

As funções view, over e set são construídas sobre os conceitos de Const e Identity. Aqui estão suas implementações simplificadas:

-- Recupera um valor através de uma lente
view lensFn = getConst . lensFn Const

-- Aplica uma função sobre o alvo da lente
over lensFn f = runIdentity . lensFn (Identity . f)

-- Define um valor estático
set lensFn val = runIdentity . lensFn (\_ -> Identity val)

-- Construtor para getters customizados
to k = dimap k (contramap k)

-- Exemplo de implementação do getter para o primeiro elemento de uma tupla
instance Field1 (x, y) (x', y) x x' where
  _1 optic ~(val1, val2) = optic val1 <$> \newVal -> (newVal, val2)

Expansão de Operações com Tuplas

Vamos rastrear como o Haskell processa a operação view _1 (10, 20):

view _1 (10, 20)
= getConst . _1 Const $ (10, 20)
= getConst $ _1 Const (10, 20)
= getConst $ Const 10 <$> \newVal -> (newVal, 20)
= getConst $ Const 10
= 10

Agora, o processo para over alterando o estado:

over _1 (+5) (10, 20)
= runIdentity . _1 (Identity . (+5)) $ (10, 20)
= runIdentity $ _1 (Identity . (+5)) (10, 20)
= runIdentity $ (Identity . (+5)) 10 <$> \newVal -> (newVal, 20)
= runIdentity $ Identity 15 <$> \newVal -> (newVal, 20)
= runIdentity $ Identity (15, 20)
= (15, 20)

Composição e Transformação com 'to'

A composição de lentes permite transformações complexas. Veja a expansão de view (_1 . to abs) (-5, 10):

view (_1 . to abs) (-5, 10)
= getConst $ (_1 . to abs) Const (-5, 10)
= getConst $ _1 (to abs Const) (-5, 10)
= getConst $ (to abs Const) (-5) <$> \newVal -> (newVal, 10)
= getConst $ (dimap abs (contramap abs) (\x -> Const x)) (-5) <$> \newVal -> (newVal, 10)
= getConst $ ((contramap abs) . (\x -> Const x) . abs $ (-5)) <$> \newVal -> (newVal, 10)
= getConst $ ((contramap abs) . (\x -> Const x) $ 5) <$> \newVal -> (newVal, 10)
= getConst $ (contramap abs $ Const 5) <$> \newVal -> (newVal, 10)
= getConst $ Const 5 <$> \newVal -> (newVal, 10)
= 5

Estruturas de Suporte: Const, Profunctor e Identity

O funcionamento das lenses depende de instânccias específicas de Functors e Profunctors:

-- Const armazena um valor sem transformá-lo via fmap
newtype Const a b = Const { getConst :: a }
instance Functor (Const m) where
    fmap _ (Const v) = Const v

-- Identity permite a reconstrução da estrutura
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
    fmap f m = Identity (f (runIdentity m))

-- Funções nativas como Profunctors
instance Profunctor (->) where
  dimap ab cd bc = cd . bc . ab

Análise de Prisms: preview _Left

Prisms lidam com estruturas que podem ou não conter um valor. Veja como preview funciona internamente:

_Left = prism Left $ either Right (Left . Right)
prism bt seta = dimap seta (either pure (fmap bt)) . right'

preview l = getFirst . foldMapOf l (First . Just)
foldMapOf l f = getConst . l (Const . f)

Cálculo manual simplificado de preview _Left (Left 100):

preview _Left (Left 100)
= getFirst $ getConst $ _Left (Const . First . Just) (Left 100)
= getFirst $ getConst $ (dimap (either Right (Left . Right)) (either pure (fmap Left)) . right') (Const . First . Just) (Left 100)
= getFirst $ getConst $ (either pure (fmap Left)) $ fmap (Const . First . Just) (Right 100)
= getFirst $ getConst $ Right (Const . First $ Just 100)
= Just 100

Manipulação de Coleções com 'mapped'

O uso de mapped permite aplicar alterações em todos os elementos de um container de forma genérica através da classe Settable:

set mapped 9 [1, 2, 3]
= runIdentity $ (sets fmap) (\_ -> Identity 9) [1, 2, 3]
= runIdentity $ Identity . (fmap (\_ -> 9)) $ [1, 2, 3]
= runIdentity $ Identity [9, 9, 9]
= [9, 9, 9]

Extração com 'toListOf' e 'both'

Para estruturas que suportam Bitraversable, como tuplas, o combinader both permite extrair múltiplos valores simultaneamente:

toListOf both ("A", "B")
= flip appEndo [] . getConst $ bitraverse (Const . Endo . (:)) (Const . Endo . (:)) ("A", "B")
= flip appEndo [] . getConst $ (,) <$> (Const (Endo ("A":))) <*> (Const (Endo ("B":)))
= flip appEndo [] . getConst $ (Const (Endo (("A":) . ("B":))))
= appEndo (Endo (("A":) . ("B":))) []
= ["A", "B"]

Tags: Haskell Optics Lenses FunctionalProgramming CategoryTheory

Publicado em 7-21 06:44