Existem pelo menos quatro bibliotecas que sei fornecer lentes.
A noção de uma lente é que ela fornece algo isomórfico para
data Lens a b = Lens (a -> b) (b -> a -> a)
fornecendo duas funções: um getter e um setter
get (Lens g _) = g
put (Lens _ s) = s
sujeito a três leis:
Primeiro, se você colocar algo, poderá recuperá-lo
get l (put l b a) = b
Segundo, obter e definir não altera a resposta
put l (get l a) a = a
E terceiro, colocar duas vezes é o mesmo que colocar uma vez, ou melhor, que o segundo put vence.
put l b1 (put l b2 a) = put l b1 a
Observe que o sistema de tipos não é suficiente para verificar essas leis para você; portanto, você mesmo deve garantir essas leis, independentemente da implementação da lente usada.
Muitas dessas bibliotecas também fornecem vários combinadores extras na parte superior, e geralmente alguma forma de maquinaria haskell de modelo para gerar automaticamente lentes para os campos de tipos simples de registros.
Com isso em mente, podemos recorrer às diferentes implementações:
Implementações
fclabels
Talvez o fclabels seja o mais facilmente fundamentado nas bibliotecas de lentes, porque a :-> b
pode ser traduzido diretamente para o tipo acima. Ele fornece uma instância de categoria para a (:->)
qual é útil, pois permite compor lentes. Também fornece uma leiPoint
tipo que generaliza a noção de lente usada aqui, e algumas canalizações para lidar com isomorfismos.
Um obstáculo à adoção de fclabels
é que o pacote principal inclui o encanamento template-haskell, portanto o pacote não é o Haskell 98 e também requer a TypeOperators
extensão (bastante não controversa) .
acessador de dados
[Edit: data-accessor
não está mais usando essa representação, mas foi movido para um formulário semelhante ao de data-lens
. Eu estou mantendo este comentário, no entanto.]
O acessador de dados é um pouco mais popular do que fclabels
, em parte porque é Haskell 98. No entanto, sua escolha de representação interna me faz vomitar um pouco na boca.
O tipo T
usado para representar uma lente é definido internamente como
newtype T r a = Cons { decons :: a -> r -> (a, r) }
Consequentemente, para get
o valor de uma lente, você deve enviar um valor indefinido para o argumento 'a'! Isso me parece uma implementação incrivelmente feia e ad hoc.
Dito isto, Henning incluiu o encanamento template-haskell para gerar automaticamente os acessadores para você em um pacote separado ' data-accessorable-template '.
Ele tem o benefício de um conjunto decentemente grande de pacotes que já o emprega, sendo o Haskell 98, e fornecendo a Category
instância mais importante ; portanto, se você não prestar atenção em como a salsicha é feita, esse pacote é realmente uma escolha bastante razoável .
lentes
Em seguida, há o pacote de lentes , que observa que uma lente pode fornecer um homomorfismo de mônada de estado entre duas mônadas de estado, definindo lentes diretamente como tais homomorfismos de mônada.
Se realmente se desse ao trabalho de fornecer um tipo para suas lentes, elas teriam um tipo de classificação 2 como:
newtype Lens s t = Lens (forall a. State t a -> State s a)
Como resultado, prefiro não gostar dessa abordagem, pois ela desnecessariamente tira o Haskell 98 (se você deseja que um tipo forneça suas lentes em abstrato) e priva você da Category
instância de lentes, o que permitiria que você componha-os com .
. A implementação também requer classes do tipo multiparâmetros.
Observe que todas as outras bibliotecas de lentes mencionadas aqui fornecem algum combinador ou podem ser usadas para fornecer esse mesmo efeito de focalização de estado, de modo que nada é ganho ao codificar sua lente diretamente dessa maneira.
Além disso, as condições colaterais declaradas no início realmente não têm uma expressão agradável nesta forma. Assim como 'fclabels', isso fornece o método template-haskell para gerar automaticamente lentes para um tipo de registro diretamente no pacote principal.
Por causa da falta de Category
instância, da codificação barroca e da exigência de template-haskell no pacote principal, essa é a minha implementação menos favorita.
lente de dados
[Edit: A partir da 1.8.0, estes passaram do pacote comonad-transformers para a lente de dados]
Meu data-lens
pacote fornece lentes em termos de loja comum.
newtype Lens a b = Lens (a -> Store b a)
Onde
data Store b a = Store (b -> a) b
Expandido, isso é equivalente a
newtype Lens a b = Lens (a -> (b, b -> a))
Você pode ver isso como fatorar o argumento comum do getter e do setter para retornar um par que consiste no resultado da recuperação do elemento e um setter para inserir um novo valor novamente. Isso oferece o benefício computacional que o 'setter' aqui é possível reciclar parte do trabalho usado para obter o valor, criando uma operação de 'modificação' mais eficiente do que na fclabels
definição, especialmente quando os acessadores são encadeados.
Há também uma boa justificativa teórica para essa representação, porque o subconjunto de valores de 'Lente' que satisfazem as três leis declaradas no início desta resposta são precisamente aquelas lentes para as quais a função empacotada é uma 'comonad coalgebra' para a loja comonad . Isso transforma três leis cabeludas para uma lente l
em até 2 equivalentes sem pontos:
extract . l = id
duplicate . l = fmap l . l
Esta abordagem foi observado pela primeira vez e descrito em Russell O'Connor Functor
é Lens
como Applicative
é Biplate
: Apresentando Multiplate e foi colocado no blog sobre baseada em uma pré-publicação por Jeremy Gibbons.
Também inclui vários combinadores para trabalhar estritamente com lentes e algumas lentes de estoque para contêineres, como Data.Map
.
Portanto, as lentes na data-lens
forma a Category
(ao contrário do lenses
pacote), são Haskell 98 (ao contrário de fclabels
/ lenses
), são sãs (ao contrário do back-end data-accessor
) e fornecem uma implementação um pouco mais eficiente, data-lens-fd
fornecem a funcionalidade para trabalhar com o MonadState para aqueles que desejam sair do Haskell 98, e o maquinário template-haskell está agora disponível via data-lens-template
.
Atualização 6/28/2012: Outras estratégias de implementação de lentes
Lentes de isomorfismo
Vale a pena considerar outras duas codificações de lente. O primeiro fornece uma boa maneira teórica de ver uma lente como uma maneira de quebrar uma estrutura no valor do campo e 'todo o resto'.
Dado um tipo de isomorfismos
data Iso a b = Iso { hither :: a -> b, yon :: b -> a }
de modo que membros válidos satisfaçam hither . yon = id
eyon . hither = id
Podemos representar uma lente com:
data Lens a b = forall c. Lens (Iso a (b,c))
Elas são úteis principalmente como uma maneira de pensar sobre o significado das lentes, e podemos usá-las como uma ferramenta de raciocínio para explicar outras lentes.
Lentes van Laarhoven
Podemos modelar lentes de modo que elas possam ser compostas com (.)
e id
, mesmo sem uma Category
instância, usando
type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a
como o tipo para nossas lentes.
Em seguida, definir uma lente é tão fácil quanto:
_2 f (a,b) = (,) a <$> f b
e você pode validar por si mesmo que a composição da função é a composição da lente.
Eu escrevi recentemente sobre como você pode generalizar ainda mais as lentes de van Laarhoven para obter famílias de lentes que podem alterar os tipos de campos, apenas generalizando essa assinatura para
type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b
Isso tem a conseqüência infeliz de que a melhor maneira de falar sobre lentes é usar o polimorfismo de grau 2, mas você não precisa usar essa assinatura diretamente ao definir lentes.
O que Lens
eu defini acima _2
é na verdade a LensFamily
.
_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)
Eu escrevi uma biblioteca que inclui lentes, famílias de lentes e outras generalizações, incluindo getters, setters, dobras e travessias. Está disponível no hackage como o lens
pacote.
Novamente, uma grande vantagem dessa abordagem é que os mantenedores de bibliotecas podem realmente criar lentes nesse estilo em suas bibliotecas sem incorrer em nenhuma dependência de bibliotecas de lentes, fornecendo apenas funções com o tipo Functor f => (b -> f b) -> a -> f a
, para seus tipos específicos 'a' e 'b'. Isso reduz muito o custo de adoção.
Como você não precisa realmente usar o pacote para definir novas lentes, isso tira muita pressão de minhas preocupações anteriores sobre manter a biblioteca Haskell 98.
lens
pacote tem a funcionalidade e a documentação mais valiosas; portanto, se você não se importa com a complexidade e as dependências, é o caminho a seguir.