2011-08-29 7 views
9

Me interesaría un pequeño ejemplo de van Laarhoven's isomorphism lenses, aplicado a un tipo de datos como data BValue = BValue { π :: Float, σ :: Float, α :: Float } deriving Show (específicamente, las funciones get/set/modify). Gracias de antemano.Isomorfismo lentes

Respuesta

6

Desde el puesto de van Laarhoven, el tipo Lens es

data Lens a b = forall r . Lens (Iso a (b, r)) 

Así que en nuestro caso es aBValue y queremos construir algunos leneses que captan a cabo uno o más de los elementos. Entonces, por ejemplo, construyamos una lente que elija π.

piLens :: Lens BValue Float 

así que va a ser una lente de una BValue a un Float

piLens = Lens (Iso {fw = piFwd, bw = piBwd}) 

Una lente selecciona dos cosas (es decir, la primera de ellas en el triple, con pi etiqueta.): Un residuo tipo r (se omite aquí porque no tenemos que especificar explícitamente un tipo existencial en haskell), y un isomorfismo. Un isomorfismo a su vez se compone de una función hacia delante y hacia atrás.

piFwd :: BValue -> (Float, (Float, Float)) 
piFwd (BValue {pi, sigma, alpha}) = (pi, (sigma, alpha)) 

La función de reenvío simplemente aísla el componente que queremos. Tenga en cuenta que mi tipo residual aquí es el "resto del valor", es decir, un par de flotadores sigma y alfa.

piBwd :: (Float, (Float, Float)) -> BValue 
piBwd (pi, (sigma, alpha)) = BValue { pi = pi, sigma = sigma, alpha = alpha } 

La función hacia atrás es análoga.

Así que ahora hemos definido una lente para manipular el componente pi de un BValue.

Las otras siete lentes son similares. (7 lentes: elija sigma y alfa, elija todos los pares posibles (sin tener en cuenta el orden), seleccione todos BValue y elija ()).

El único aspecto del que no estoy seguro es el rigor: me preocupa un poco que las funciones fw y bw que he escrito sean demasiado estrictas. No es seguro.

No hemos terminado aún:

todavía tenemos que comprobar que en realidad piLens respeta las leyes de la lente. Lo bueno de la definición de van Laarhoven de Lens es que solo debemos verificar las leyes de isomorfismo; las leyes de lentes siguen a través del cálculo en su publicación de blog.

Así que nuestras obligaciones de prueba son:

  1. fw piLens . bw piLens = id
  2. bw piLens . fw piLens = id

Ambas pruebas se siguen directamente de las definiciones de piFwd y piBwd y leyes sobre la composición.

+2

Una última cosa: no hay funciones get/set/modify para 'BValue'. Las funciones get/set/modify se definen una vez y siguen para cada tipo de 'Lens a b'. (Ver la publicación del blog). Lo único que queda por hacer es aplicar, por ejemplo, 'get' a un objetivo específico adaptado a su tipo de datos. Entonces: 'get piLens' es el getter para el campo pi de' BValue'. – Lambdageek

+0

¿Cómo se utilizarían estos accesadores en una variable específica del tipo dado, por ejemplo, 'bv :: BValue'? La lente se aplica a un tipo, por lo que debería ser posible 'obtener' o 'establecer' los campos de una variable que pertenece al tipo. –

+0

@mindbound solo aplica la función get/set/modify a la lente y al valor: 'get piLens bv' o' modify piLens (+ 1.0) bv' – Lambdageek

2

Consulte , que implementa lentes para tipos de registro.

Para ilustrar este paquete, tomemos los siguientes dos tipos de datos de ejemplo.

import Data.Label 
import Prelude hiding ((.), id) 

data Person = Person 
{ _name :: String 
, _age :: Int 
, _isMale :: Bool 
, _place :: Place 
} 

data Place = Place 
{ _city 
, _country 
, _continent :: String 
} 

Ambos tipos de datos son tipos de registro con todas las etiquetas con un guion bajo. Este guión bajo es una indicación para nuestro código Template Haskell para derivar lentes para estos campos. Las lentes Derivadas se pueden hacer con este simple revestimiento:

$(mkLabels [''Person, ''Place]) 

Para todas las etiquetas se creará una lente.

Ahora veamos este ejemplo. Este compañero de 71 años de edad, mi vecino llamó Ene, no le importaba usándolo como un ejemplo:

jan :: Person 
jan = Person "Jan" 71 True (Place "Utrecht" "The Netherlands" "Europe") 

Cuando queremos estar seguros de Jan es realmente tan viejo como él afirma que podemos utilizar la función get para obtener la edad como un número entero:

hisAge :: Int 
hisAge = get age jan 

Considere que ahora quiere mudarse a Amsterdam: qué mejor lugar para pasar sus viejos días. El uso de la composición que puede cambiar el valor de la ciudad en el interior de la estructura:

moveToAmsterdam :: Person -> Person 
moveToAmsterdam = set (city . place) "Amsterdam" 

Y ahora:

ghci> moveToAmsterdam jan 
Person "Jan" 71 True (Place "Amsterdam" "The Netherlands" "Europe") 

composición se realiza mediante el operador que es parte del módulo Control.Category (.). Asegúrese de importar este módulo y ocultar la función de identificación predeterminada (.) Del Preludio de Haskell.