2012-02-29 10 views
7

Estoy tratando de convertir un tipo de datos Haskell simple y una función a OO. Pero estoy confundido ..Haskell datatype to Java (OO)

tener el siguiente tipo de Haskell para el cálculo aritmético:

data Expr = Lit Int | 
     Add Expr Expr | 
    deriving Show 

--Turn the expr to a nice string 
showExpr :: Expr -> String 
showExpr (Lit n) = show n 
showExpr (Add e1 e2) = "(" ++ showExpr e1 ++ "+" ++ showExpr e2 ++ ")" 

Ahora estoy tratando de convertir ..

public interface Expr { 
    String showExpr(String n); 
} 

// Base case 
public class Lit implements Expr { 
    String n; 

    public Lit(String n) { 
    this.n = n; 
    } 

    @Override 
    public String ShowExpr() { 
    return n; 
    } 
} 

public class Add implements Expr { 
    Expr a; 
    Expr b; 

    public Add(Expr aExpr, Expr bExpr) { 
    this.a = aExpr; 
    this.b = bExpr; 
    } 

    public String ShowExpr() { 
    return "(" + a.ShowExpr() + "+" + b.ShowExpr() + ")"; 
    } 

    public static void main(String[] args) { 
    Lit firstLit = new Lit("2"); 
    Lit secLit = new Lit("3"); 
    Add add = new Add(firstLit,secLit); 
    System.out.println(add.ShowExpr()); 
    } 
} 

Esto dará como resultado "(2 + 3) ", es la respuesta correcta.

Pero ... No estoy seguro ... ¿es esta la manera correcta de pensarlo y modelarlo en OO?

¿No es una buena representación del tipo de datos Haskell?

+3

Creo que su conversión es realmente buena. Lo único que puedo comentar es que Expr debería ser una clase abstracta en lugar de una interfaz. Y no use Java para este tipo de problemas porque Haskell es mucho mejor en esto. – nist

Respuesta

5

Copiemos el código tan de cerca como podamos.

Estas son algunas de las propiedades que la estructura de datos Haskell tiene:

  1. Tiene el tipo Expr y dos constructores Lit y Add
  2. No se puede agregar o quitar los constructores del "exterior"

Por lo tanto, si queremos que estas propiedades se mantengan ciertas en la versión de Java, debe hacerlo así:

public abstract class Expr { 
    // So that you can't add more subclasses outside this block 
    private Expr() {} 

    // Simulate pattern matching: 
    // (This CAN be done with instanceof, but that's ugly and not OO) 
    public boolean isLit() { 
     return false; 
    } 
    public boolean isAdd() { 
     return false; 
    } 
    public Lit asLit() { 
     throw new UnsupportedOperationException("This is not a Lit"); 
    } 
    public Add asAdd() { 
     throw new UnsupportedOperationException("This is not an Add"); 
    } 

    public static class Lit extends Expr { 
     public final int n; 
     public Lit(int n) { 
      this.n = n; 
     } 
     @Override 
     public boolean isLit() { 
      return true; 
     } 
     @Override 
     public Lit asLit() { 
      return this; 
     } 
    } 

    public static class Add extends Expr { 
     public final Expr a, b; 
     public Lit(Expr a, Expr b) { 
      this.a = a; 
      this.b = b; 
     } 
     @Override 
     public boolean isAdd() { 
      return true; 
     } 
     @Override 
     public Add asAdd() { 
      return this; 
     } 
    } 
} 

Ahora, para convertir showExpr:

public static String showExpr(final Expr expr) { 
    if(expr.isLit()) { 
     return Integer.toString(expr.asLit().n); 
    } else if(expr.isAdd()) { 
     return "(" + expr.asAdd().a + "+" + expr.asAdd().b + ")"; 
    } 
} 

Usted puede poner showExpr como un método estático de la clase Expr. Me gustaría no hacerlo un método de instancia, porque eso se aleja más de la versión de Haskell.

+5

No veo una diferencia real entre 'if (e.isLit()) {Lit l = e.asLit(); } 'y' if (e instancia de Lit) {Lit l = (Lit) e; } ' La coincidencia de patrones al estilo Haskell no es inherentemente OO, por lo que tratar de ocultar ese hecho con métodos que simulan 'instanceof' y moldes no aporta nada al código. – yshavit

+0

@yshavit, tienes razón, por supuesto, pero creo que el estilo que presento conduce a un código más fácil de leer, y menos errores como 'if (foo instanceof Foo) {((Bar) foo) .bla() ; } 'debido a la refactorización (' Foo! = Bar'). También podría, por ejemplo, querer hacer 'Add' y' Lit' en clases privadas y usar métodos de fábrica para ellos, y en ese caso, 'instanceof' no funcionará. – dflemstr

+0

Si 'e instanceof Lit' es más o menos legible que' e.isLit() 'Lo dejo como una cuestión de opinión. :) Pero tenga en cuenta que si son clases privadas, 'e.asLit(). N' tampoco funcionará. – yshavit

2

Me gustaría poner esto en su lugar: cada clase Haskell se convierte en una interfaz Java, y instance ...where (o derives) se convierte en implements.

Por lo tanto, se convierte en class Show a

interface Showable { 
    String show(); 
} 

Ahora bien, ¿qué hace el tipo de datos Expr? Combina varios constructores de Haskell, también conocidos como clases de Java, en un solo tipo, y también dice que todos están cubiertos por las clases de Haskell (es decir, implementan las interfaces de Java). Por lo tanto, me gustaría decir entonces que el tipo de datos Expr convierte

interface Expr extends Showable {} 

y la Lit constructor, por ejemplo, se convierte en:

class Lit implements Expr { 
    @Override 
    String show() { 
     ... 
    } 
} 

Ahora usted puede tener colecciones de Java que incluyen tanto Lit y Add casos (por ejemplo, List<Expr>), y para cualquier Expr, lo sabes implements Showable.

Tenga en cuenta que tirando Showable como su propia interfaz (en lugar de poner su método directamente en Expr como lo hizo), permito que otras clases de Java completamente independientes también lo implementen. Esto es similar a cómo alguien puede definir un instance ... where de cualquier clase Haskell que defina.

Finalmente, hay un par de desajustes entre las políticas de mundo abierto y de mundo cerrado aquí. El instance ... where de Haskell es de mundo abierto, mientras que el implements de Java está cerrado. Pero los constructores por tipo de Haskell están cerrados, mientras que el extends de Java es en su mayoría abierto.

No hay mucho que pueda hacer para cerrar implements, pero puede cerrar el extends con una clase abstracta y un constructor privado, así que eso es lo que haría (si quiere ese comportamiento). Afinando un poco más arriba, obtenemos:

public abstract class Expr implements Showable { 
    private Expr() { 
     // hide the ctor from the outside world, so nobody else 
     // can extend this class 
    } 

    public static class Lit extends Expr { 
     ... 
    } 

    public static class Add extends Expr { 
     ... 
    } 
} 
+0

Eso estaría bien si el código original utilizara una clase de tipo, pero creo que esto es excesivo para modelar un ADT simple. – Landei

0

Su código se ve bien, pero podría ser un poco más idiomático. Puede usar final para hacer campos inmutables, por ejemplo. También puede reemplazar showExp con toString (que se declara en Object), a menos que desee toString para hacer algo diferente. Usar String.format es un poco más limpio que concatenar varias cadenas, en mi opinión. Finalmente, tendría Lit tienda un int, ya que es más compacto y tipo seguro.

Así es como me gustaría traducirlo:

abstract class Exp { 
    // Force subclasses to override Object.toString. 
    public abstract String toString(); 
} 

class Lit extends Exp { 
    final int value; 

    Lit(int value) { 
    this.value = value; 
    } 

    public String toString() { 
    return Integer.toString(value); 
    } 
} 

class Add extends Exp { 
    final Exp a, b; 

    Add(Exp a, Exp b) { 
    this.a = a; 
    this.b = b; 
    } 

    public String toString() { 
    return String.format("(%s + %s)", a, b); 
    } 
} 

class Main { 
    public static void main(String[] args) { 
    Lit firstLit = new Lit(2); 
    Lit secLit = new Lit(3); 
    Add add = new Add(firstLit, secLit); 
    System.out.println(add); 
    } 
} 
1

que se traduciría esa manera:

public abstract class Expr { 

    public static Expr Lit(final int n) { 
     return new Expr() { 
      public String showExpr() { 
       return "" + n; 
      } 
     }; 
    } 

    public static Expr Add(final Expr e1, final Expr e2) { 
     return new Expr() { 
      public String showExpr() { 
       return String.format("(%s+%s)", e1.showExpr(), e2.showExpr()); 
      } 
     }; 
    } 

    public abstract String showExpr(); 

    public static void main(String[] args) { 
     Expr firstLit = Lit(2); 
     Expr secLit = Lit(3); 
     Expr add = Add(firstLit, secLit); 
     System.out.println(add.showExpr()); 
    } 
} 

En Haskell Lit y Add hay subtipos (no hay tal cosa en Haskell), pero solo Expr esiones. No es necesario exponer las subclases en Java, por lo que podría usar clases anónimas ocultas en algunos métodos. Esto funciona perfectamente, siempre y cuando no necesite una coincidencia de patrones (que es difícil de modelar en Java de todos modos, ya que la simple prueba instanceof se torna pronto desordenada con ejemplos más complicados).