lentes, fclabels, acessador de dados - qual biblioteca para acesso e mutação de estrutura é melhor

173

Existem pelo menos três bibliotecas populares para acessar e manipular campos de registros. Os que eu conheço são: acessador de dados, fclabels e lentes.

Pessoalmente, comecei com o acessador de dados e estou usando agora. No entanto, recentemente, no haskell-cafe, havia uma opinião de que os fclabels eram superiores.

Portanto, estou interessado em comparar essas três (e talvez mais) bibliotecas.

Tener
fonte
3
Atualmente, o lenspacote 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.
modular

Respostas:

200

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 :-> bpode 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 TypeOperatorsextensão (bastante não controversa) .

acessador de dados

[Edit: data-accessornã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 Tusado para representar uma lente é definido internamente como

newtype T r a = Cons { decons :: a -> r -> (a, r) }

Consequentemente, para geto 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 Categoryinstâ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 Categoryinstâ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 Categoryinstâ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-lenspacote 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 fclabelsdefiniçã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 lem 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é Lenscomo 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-lensforma a Category(ao contrário do lensespacote), 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-fdfornecem 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 = ideyon . 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 Categoryinstâ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 Lenseu 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.

Edward KMETT
fonte
28
Eu gosto das fclabels para a sua abordagem otimista:->
Tener
3
Os artigos Guia não essenciais a data-assessor e guia não essenciais para fclabels poderia ser digno de nota
HVR
10
Ser compatível com Haskell 1998 é importante? Porque facilita o desenvolvimento do compilador? E não deveríamos começar a falar sobre o Haskell 2010?
Yairchu
55
Ah não! Eu era o autor original data-accessor, e depois passei para Henning e parei de prestar atenção. A a -> r -> (a,r)representação também me deixa desconfortável, e minha implementação original foi exatamente como o seu Lenstipo. Heeennnninngg !!
Luqui
5
Yairchu: é principalmente para que sua biblioteca possa ter uma chance de trabalhar com um compilador que não seja o ghc. Ninguém mais tem modelo Haskell. 2010 não adiciona nada relevante aqui.
Edward KMETT