gui create java haskell typeclass

create - set border java



¿Puedo definir la interfaz Negatable en Java? (4)

Actualmente, si. No directamente, pero puedes hacerlo. Simplemente incluya un parámetro genérico y luego derive del tipo genérico.

public interface Negatable<T> { T negate(); } public static <T extends Negatable<T>> T normalize(T a) { return a.negate().negate(); }

Usted implementaría esta interfaz como tal

public static class MyBoolean implements Negatable<MyBoolean> { public boolean a; public MyBoolean(boolean a) { this.a = a; } @Override public MyBoolean negate() { return new MyBoolean(!this.a); } }

De hecho, la biblioteca estándar de Java utiliza este truco exacto para implementar Comparable .

public interface Comparable<T> { int compareTo(T o); }

Al hacer esta pregunta para aclarar mi comprensión de las clases de tipo y los tipos de tipo más alto, no estoy buscando soluciones en Java.

En Haskell, podría escribir algo como

class Negatable t where negate :: t -> t normalize :: (Negatable t) => t -> t normalize x = negate (negate x)

Luego, asumiendo que Bool tiene una instancia de Negatable ,

v :: Bool v = normalize True

Y todo funciona bien.

En Java, no parece posible declarar una interfaz Negatable adecuada. Podríamos escribir:

interface Negatable { Negatable negate(); } Negatable normalize(Negatable a) { a.negate().negate(); }

Pero entonces, a diferencia de Haskell, lo siguiente no se compilaría sin una MyBoolean (suponga que MyBoolean implementa Negatable ):

MyBoolean val = normalize(new MyBoolean()); // does not compile; val is a Negatable, not a MyBoolean

¿Hay alguna manera de referirse al tipo de implementación en una interfaz Java, o es esta una limitación fundamental del sistema de tipo Java? Si es una limitación, ¿está relacionada con el soporte de tipo de tipo superior? Creo que no: parece que este es otro tipo de limitación. Si es así, ¿tiene un nombre?

Gracias, y hágamelo saber si la pregunta no está clara.


En general, no.

Puede usar trucos (como se sugiere en las otras respuestas) que harán que esto funcione, pero no ofrecen todas las mismas garantías que la clase de tipos de Haskell. Específicamente, en Haskell, podría definir una función como esta:

doublyNegate :: Negatable t => t -> t doublyNegate v = negate (negate v)

Ahora se sabe que el argumento y el valor de retorno de doublyNegate son t . Pero el equivalente de Java:

public <T extends Negatable<T>> T doublyNegate (Negatable<T> v) { return v.negate().negate(); }

no, porque Negatable<T> podría ser implementado por otro tipo:

public class X implements Negatable<SomeNegatableClass> { public SomeNegatableClass negate () { return new SomeNegatableClass(); } public static void main (String[] args) { new X().negate().negate(); // results in a SomeNegatableClass, not an X }

Esto no es particularmente grave para esta aplicación, pero causa problemas para otras clases de tipos de Haskell, por ejemplo, Equatable . No hay forma de implementar una Equatable tipos Java Equatable sin usar un objeto adicional y enviar una instancia de ese objeto a cualquier lugar donde enviemos valores que deban compararse (por ejemplo:

public interface Equatable<T> { boolean equal (T a, T b); } public class MyClass { String str; public static class MyClassEquatable implements Equatable<MyClass> { public boolean equal (MyClass a, MyClass b) { return a.str.equals(b.str); } } } ... public <T> methodThatNeedsToEquateThings (T a, T b, Equatable<T> eq) { if (eq.equal (a, b)) { System.out.println ("they''re equal!"); } }

(De hecho, esta es exactamente la forma en que Haskell implementa las clases de tipo, pero oculta el parámetro que pasa de usted para que no tenga que averiguar qué implementación enviar a dónde)

Tratar de hacer esto solo con interfaces Java simples lleva a algunos resultados contraintuitivos:

public interface Equatable<T extends Equatable<T>> { boolean equalTo (T other); } public MyClass implements Equatable<MyClass> { String str; public boolean equalTo (MyClass other) { return str.equals(other.str); } } public Another implements Equatable<MyClass> { public boolean equalTo (MyClass other) { return true; } } .... MyClass a = ....; Another b = ....; if (b.equalTo(a)) assertTrue (a.equalTo(b)); ....

Usted esperaría, debido al hecho de que equalTo realmente debería definirse simétricamente, que si la declaración if se compila, la afirmación también se compilará, pero no lo hace, porque MyClass no es compatible con Another , aunque la otra todo es verdad Pero con una clase de tipo Haskell Equatable , sabemos que si areEqual ab funciona, areEqual ba también es válido. [1]

Otra limitación de las interfaces frente a las clases de tipo es que una clase de tipo puede proporcionar un medio para crear un valor que implementa la clase de tipo sin tener un valor existente (p. Ej., El operador de return para la Monad ), mientras que para una interfaz ya debe tener un objeto de El tipo para poder invocar sus métodos.

Usted pregunta si hay un nombre para esta limitación, pero no conozco uno. Es simplemente porque las clases de tipos son realmente diferentes a las interfaces orientadas a objetos, a pesar de sus similitudes, porque se implementan de esta manera fundamentalmente diferente: un objeto es un subtipo de su interfaz, por lo tanto, lleva una copia de los métodos de la interfaz directamente sin modificar su definición, mientras que una clase de tipo es una lista separada de funciones, cada una de las cuales se personaliza mediante la sustitución de variables de tipo. No existe una relación de subtipo entre un tipo y una clase de tipo que tiene una instancia para el tipo (un Integer Haskell no es un subtipo de Comparable , por ejemplo: simplemente existe una instancia Comparable que se puede pasar cuando una función necesita Ser capaz de comparar sus parámetros y esos parámetros resultan ser enteros).

[1]: El operador Haskell == realidad se implementa usando una clase de tipo, Eq ... No he usado esto porque la sobrecarga del operador en Haskell puede ser confusa para las personas que no están familiarizadas con la lectura del código de Haskell.


Estás buscando genéricos, además de auto escribir. La auto escritura es la noción de marcador de posición genérico que equivale a la clase de la instancia.

Sin embargo, la auto escritura no existe en java.

Esto se puede resolver con genéricos sin embargo.

public interface Negatable<T> { public T negate(); }

Entonces

public class MyBoolean implements Negatable<MyBoolean>{ @Override public MyBoolean negate() { //your impl } }

Algunas implicaciones para los implementadores:

  • Deben especificarse cuando implementen la interfaz, por ejemplo, los MyBoolean implements Negatable<MyBoolean>
  • Extender MyBoolean requeriría uno para anular de nuevo el método de negate .

Interpreto la pregunta como

¿Cómo podemos implementar el polimorfismo ad-hoc utilizando clases de tipos en Java?

Puede hacer algo muy similar en Java, pero sin las garantías de seguridad de tipo de Haskell: la solución que se presenta a continuación puede generar errores en el tiempo de ejecución.

Aquí sabrás como podrás hacerlo:

  1. Definir la interfaz que representa la clase de tipos.

    interface Negatable<T> { T negate(T t); }

  2. Implemente algún mecanismo que le permita registrar instancias de la clase de tipos para varios tipos. Aquí, un HashMap estático hará:

    static HashMap<Class<?>, Negatable<?>> instances = new HashMap<>(); static <T> void registerInstance(Class<T> clazz, Negatable<T> inst) { instances.put(clazz, inst); } @SuppressWarnings("unchecked") static <T> Negatable<T> getInstance(Class<?> clazz) { return (Negatable<T>)instances.get(clazz); }

  3. Defina el método de normalize que utiliza el mecanismo anterior para obtener la instancia adecuada en función de la clase de tiempo de ejecución del objeto pasado:

    public static <T> T normalize(T t) { Negatable<T> inst = Negatable.<T>getInstance(t.getClass()); return inst.negate(inst.negate(t)); }

  4. Registrar instancias reales para varias clases:

    Negatable.registerInstance(Boolean.class, new Negatable<Boolean>() { public Boolean negate(Boolean b) { return !b; } }); Negatable.registerInstance(Integer.class, new Negatable<Integer>() { public Integer negate(Integer i) { return -i; } });

  5. ¡Úsalo!

    System.out.println(normalize(false)); // Boolean `false` System.out.println(normalize(42)); // Integer `42`

El principal inconveniente es que, como ya se mencionó, la búsqueda de instancia de typeclass puede fallar en tiempo de ejecución, no en tiempo de compilación (como en Haskell). El uso de un mapa hash estático también es subóptimo, ya que trae todos los problemas de una variable global compartida, esto podría mitigarse con mecanismos de inyección de dependencia más sofisticados. La generación automática de instancias de clase de tipos a partir de otras instancias de clase de tipos, requeriría aún más infraestructura (podría hacerse en una biblioteca). Pero, en principio, implementa un polimorfismo ad-hoc que utiliza clases de tipos en Java.

Código completo:

import java.util.HashMap; class TypeclassInJava { static interface Negatable<T> { T negate(T t); static HashMap<Class<?>, Negatable<?>> instances = new HashMap<>(); static <T> void registerInstance(Class<T> clazz, Negatable<T> inst) { instances.put(clazz, inst); } @SuppressWarnings("unchecked") static <T> Negatable<T> getInstance(Class<?> clazz) { return (Negatable<T>)instances.get(clazz); } } public static <T> T normalize(T t) { Negatable<T> inst = Negatable.<T>getInstance(t.getClass()); return inst.negate(inst.negate(t)); } static { Negatable.registerInstance(Boolean.class, new Negatable<Boolean>() { public Boolean negate(Boolean b) { return !b; } }); Negatable.registerInstance(Integer.class, new Negatable<Integer>() { public Integer negate(Integer i) { return -i; } }); } public static void main(String[] args) { System.out.println(normalize(false)); System.out.println(normalize(42)); } }