Un hecho poco conocido es que si activa suficientes extensiones de idioma (ghc), ¡Haskell se convierte en un lenguaje interpretado de tipo dinámico! Por ejemplo, el siguiente programa implementa la suma.
{-# Language MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, UndecidableInstances #-}
data Zero
data Succ a
class Add a b c | a b -> c
instance Add Zero a a
instance (Add a b c) => Add (Succ a) b (Succ c)
Esto ya no se parece a Haskell. Para uno en lugar de operar sobre objetos, operamos sobre tipos. Cada número es su propio tipo. En lugar de funciones tenemos clases de tipo. Las dependencias funcionales nos permiten usarlas como funciones entre tipos.
Entonces, ¿cómo invocamos nuestro código? Usamos otra clase
class Test a | -> a
where test :: a
instance (Add (Succ (Succ (Succ (Succ Zero)))) (Succ (Succ (Succ Zero))) a)
=> Test a
Esto establece el tipo de test
al tipo 4 + 3. Si abrimos esto en ghci, encontraremos que de test
hecho es del tipo 7:
Ok, one module loaded.
*Main> :t test
test :: Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero))))))
Tarea
Quiero que implementes una clase que multiplique dos números Peano (enteros no negativos). Los números de Peano se construirán utilizando los mismos tipos de datos en el ejemplo anterior:
data Zero
data Succ a
Y su clase será evaluada de la misma manera que arriba también. Puedes nombrar tu clase como quieras.
Puede usar cualquier extensión de idioma ghc que desee sin costo para los bytes.
Casos de prueba
Estos casos de prueba asumen que su clase se llama M
, puede nombrarla de otra manera si lo desea.
class Test1 a| ->a where test1::a
instance (M (Succ (Succ (Succ (Succ Zero)))) (Succ (Succ (Succ Zero))) a)=>Test1 a
class Test2 a| ->a where test2::a
instance (M Zero (Succ (Succ Zero)) a)=>Test2 a
class Test3 a| ->a where test3::a
instance (M (Succ (Succ (Succ (Succ Zero)))) (Succ Zero) a)=>Test3 a
class Test4 a| ->a where test4::a
instance (M (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))) (Succ (Succ (Succ Zero))) a)=>Test4 a
Resultados
*Main> :t test1
test1
:: Succ
(Succ
(Succ
(Succ
(Succ (Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))))))))
*Main> :t test2
test2 :: Zero
*Main> :t test3
test3 :: Succ (Succ (Succ (Succ Zero)))
*Main> :t test4
test4
:: Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ
(Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))))))))))))))
Se inspira en escribir la entrevista técnica
fuente
Respuestas:
130121 bytes-9 bytes gracias a Ørjan Johansen
Pruébalo en línea!
Esto define familias de tipo cerrado para suma
(+)
y multiplicación(*)
. Luego,(#)
se define una clase de tipo que usa la(*)
familia de tipos junto con una restricción de igualdad para convertir del mundo de las familias de tipos al mundo del prólogo de la clase de tipos.fuente
Zero
porz
.+
?139 bytes
Pruébalo en línea!
Define un operador de tipo
*
. Equivalente al programa Prolog:Potato44 y Hat Wizard guardaron 9 bytes cada uno. ¡Gracias!
fuente
f
lugar deSucc
.Versión familiar, 115 bytes
Pruébalo en línea!
Esto utiliza una familia de tipo cerrado como potato44's . Excepto a diferencia de la otra respuesta, uso solo 1 familia tipo.
Esto define un operador en tres tipos. Básicamente se implementa
(a*b)+c
. Siempre que queramos agregar nuestro argumento de la mano derecha al total, lo colocamos en el acumulador.Esto nos impide tener que definir
(+)
en absoluto. Técnicamente, puede usar esta familia para implementar la suma haciendoVersión de clase, 137 bytes
Pruébalo en línea!
Esta versión de clase pierde algo de terreno con la versión familiar, sin embargo, todavía es más corta que la versión de clase más corta aquí. Utiliza el mismo enfoque que mi versión familiar.
fuente
Constraint
. Por lo tanto, debe actualizar la especificación o volver al formulario que utiliza una clase en lugar de un sinónimo de tipo. Si