2012-02-29 15 views
7

Sto provando a convertire un semplice tipo di dati Haskell e una funzione in OO. Ma io sono confuso ..Tipo di dati Haskell su Java (OO)

Avere il seguente tipo di Haskell per il calcolo aritmetico:

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 ++ ")" 

Ora sto cercando di convertire ..

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()); 
    } 
} 

Questo si tradurrà in "(2 + 3) ", è la risposta corretta.

Ma .. Io non sono sicuro .. è questo il modo giusto per pensarci e modellarla in OO?

is't una buona rappresentazione del tipo di dati Haskell?

+3

Penso che la tua conversione sia davvero buona. L'unica cosa che posso dire è che Expr dovrebbe essere una classe astratta invece di un'interfaccia. E non usare java per questo tipo di problemi perché haskell è molto più bravo in questo. – nist

risposta

5

Copiamo il codice il più vicino possibile.

Qui ci sono alcune proprietà che la struttura dei dati Haskell ha:

  1. Ha il tipo Expr e due costruttori Lit e Add
  2. Non è possibile aggiungere o rimuovere i costruttori dal "fuori"

Quindi, se vogliamo che queste proprietà rimangano vere nella versione Java, dovresti farlo in questo modo:

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; 
     } 
    } 
} 

Ora, per convertire 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 + ")"; 
    } 
} 

Si può mettere showExpr come un metodo statico nella classe Expr. Vorrei non renderlo un metodo di istanza, perché si allontana ulteriormente dalla versione Haskell.

+5

Non vedo alcuna differenza reale tra 'if (e.isLit()) {Lit l = e.asLit(); } 'e' if (e instanceof Lit) {Lit l = (Lit) e; } '. La corrispondenza del modello in stile Haskell non è intrinsecamente OO, quindi provare a nasconderlo con metodi che simulano 'instanceof' e cast non porta nulla al codice. – yshavit

+0

@yshavit, hai ragione, certo, ma penso che lo stile che presento porti a un codice più facile da leggere e meno bug come 'if (foo instanceof Foo) {((Bar) foo) .bla() ; } 'a causa del refactoring (' Foo! = Bar'). Potrei anche, ad esempio, voler rendere 'Add' e' Lit' in classi private e usare i metodi factory per loro, e in tal caso 'instanceof' non funzionerà. – dflemstr

+0

Se 'e instanceof Lit' è più o meno leggibile di' e.isLit() 'Lascerò per una questione di opinione. :) Ma si noti che se sono classi private, 'e.asLit(). N' non funzionerà, neanche. – yshavit

2

Vorrei girarlo leggermente: ogni classe Haskell diventa un'interfaccia Java e instance ...where (o derives) diventa implements.

Quindi, class Show a diventa

interface Showable { 
    String show(); 
} 

Ora poi, che cosa fa il tipo di dati Expr fare? Combina vari tipi di costruttori Haskell, ovvero classi Java, in un singolo tipo, e dice anche che sono tutti coperti dalle classi Haskell (cioè implementano interfacce Java). Quindi, vorrei quindi dire che il tipo di dati Expr diventa

interface Expr extends Showable {} 

e il costruttore Lit, per esempio, diventa:

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

Ora potete avere collezioni di Java che comprendono sia Lit e Add casi (es. List<Expr>), e per qualsiasi Expr, lo sai implements Showable.

noti che tirando Showable come la propria interfaccia (piuttosto che mettere il suo metodo direttamente sul Expr come avete fatto), mi permetto di altri, le classi Java completamente estranei a implementare anche. Questo è simile a come chiunque può definire uno instance ... where di una classe Haskell definita dall'utente.

Infine, ci sono un paio di disallineamenti tra le politiche open-world e closed-world qui. Haskell's instance ... where è open-world, mentre Java implements è chiuso. Ma i costruttori-per-tipo di Haskell sono chiusi, mentre lo extends di Java è aperto per lo più .

Non c'è molto che si possa fare su implements chiuso, ma è possibile chiudere lo extends con una classe astratta e un costruttore privato, quindi è quello che farei (se si desidera tale comportamento). Modificando un po 'quanto sopra, otteniamo:

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

Sarebbe ok se il codice originale usasse una classe di tipo, ma penso che questo sia eccessivo per modellare un semplice ADT. – Landei

0

Il tuo codice sembra buono, ma potrebbe essere un po' più idiomatico. È possibile utilizzare final per rendere immutabili i campi, ad esempio. Si potrebbe anche sostituire showExp con toString (che è dichiarato in Object), a meno che non si desideri che toString esegua qualcosa di diverso. Usare String.format è un po 'più pulito di concatenare più stringhe, a mio parere. Infine, avrei Lit memorizzare un int, poiché è più compatto e digita sicuro.

Ecco come vorrei tradurlo:

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

vorrei tradurlo in questo modo:

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()); 
    } 
} 

in Haskell Lit e Add sono sottotipi (non v'è nulla di simile in Haskell), ma solo Expr essions. Non è necessario esporre le sottoclassi in Java, motivo per cui potrei usare classi anonime nascoste in alcuni metodi. Funziona perfettamente, a patto che non sia necessario il matching di pattern (che è comunque difficile da modellare in Java, dal momento che il semplice test instanceof diventa presto complicato con esempi più complicati).