Evitare l'istanza di tipi di dati ricorsivi

5

Ho scritto una semplice gerarchia di classi per rappresentare i termini in Scala. I termini sono tipi di dati ricorsivi, ad esempio un Sum e un Multiplication sono costituiti dal lato sinistro ( lhs ), che è un Termine e il lato destro ( rhs ), che è anche un termine.

La corrispondenza dei modelli di Scala semplifica la verifica della presenza di un termine in una determinata struttura. Ad esempio, ecco un metodo che restituisce t3 dal Term se è strutturato come t1 * (t2 + t3) :

def fetchT3(t: Term): Option[Term] = Some(t).collect {
  case Multiplication(t1, Sum(t2, t3)) => t3
}

Ora voglio scrivere lo stesso in Java, ma la mancanza di pattern matching lo rende difficile. Utilizzo dell'operatore instanceof se disapprovato, ma non ho trovato una vera alternativa. Ecco cosa ho scoperto finora:

public static <T> Optional<T> maybeAs(final Object obj, final Class<T> clazz) {
    try {
        return Optional.of(clazz.cast(obj));
    } catch (ClassCastException exc) {
        return Optional.empty();
    }
}

Il metodo fetchT3 dall'alto può quindi essere scritto come:

public Optional<Term> fetchT3(final Term t) {
    return maybeAs(t, Multiplication.class).flatMap(mult ->
            maybeAs(mult.rhs, Sum.class).map(sum ->
                    sum.rhs
            )
    );
}

Quello che non mi piace di questo approccio è che, per prima cosa, sta usando le eccezioni per situazioni che non sono veramente eccezionali, e in secondo luogo, diventerà difficile da leggere quando i termini sono profondamente annidati. L'utilizzo delle eccezioni può essere evitato scrivendo un metodo per ogni classe ( maybeAsSum , maybeAsMultiplication , ...) e quindi usando instanceof , che invece introdurrebbe la ridondanza del codice. In entrambi i casi, le lezioni di casting sono generalmente viste come un sintomo di una progettazione scadente in Java, ma non conosco un modo per aggirarlo.

Quindi la mia domanda è, lo considereresti come un esempio di utilizzo accettabile del lancio di classe, o vedrai un metodo migliore per controllare la struttura dei termini?

    
posta helios35 25.08.2017 - 13:00
fonte

2 risposte

2

È passato un po 'di tempo da quando guardavo Scala, quindi mi scuso se mi manca il punto qui, ma una cosa che puoi fare (non è sexy) per fare un instanceof è creare un tipo enum che enumeri tutto il tipi di termini come questi:

public enum Kind
{
    MULTIPLICATION, SUM; // etc.
}

E poi sulla tua classe Term / interfaccia:

Kind kind();

È quindi possibile utilizzare un'istruzione switch o == per determinare il tipo di termine. Direi che questa istanza sembra appropriata in questo caso, specialmente se si dispone di Termini che sono specializzazioni di altri termini in quanto instanceof può funzionare come si desidera per questo.

Le prestazioni di instanceof non rappresentano un problema. È una delle operazioni più veloci della JVM, IIRC. Dovrebbe essere altrettanto veloce, se non più veloce, che recuperare la classe da un oggetto e confrontarla. Il motivo per cui è scoraggiato è che è stato spesso usato al posto del polimorfismo da coloro che non l'hanno capito e anche in implementazioni uguali quando avrebbe interrotto la transitività. Ciò ha provocato alcune cose brutte.

    
risposta data 25.08.2017 - 23:36
fonte
1

Potresti implementare un metodo per confrontare la struttura dei Termini. Questo metodo paragonerebbe i sottotrammi anche in modo ricorsivo.

static bool BothNull(Term a, Term b) { return a==null && b==null;}
static bool BothNotNull(Term a, Term b) { return a!=null && b!=null;}

class Term {

    Term rhs;
    Term lhs;
    Object value;

    Term(Object value){ this.value = value }

    bool HasEqualStructure(Term otherTerm) {
        bool sameType = this.GetType() == otherTerm.GetType();

        bool sameRhsType = BothNull(rhs, otherTerm.rhs)
                           || BothNotNull(rhs, otherTerm.rhs)
                              && rhs.HasEqualStructure(otherTerm.rhs);

        bool sameLhsType = BothNull(lhs, otherTerm.lhs)
                           || BothNotNull(lhs, otherTerm.lhs)
                              && lhs.HasEqualStructure(otherTerm.lhs);

        return sameType && sameRhsType && sameLhsType;
    }

    bool CopyValues(Term otherTerm) {
        if (HasEqualStructure(this, otherTerm)) {
            this.value = otherTerm.value;
            CopyValues(rhs, otherTerm.rhs);
            CopyValues(lhs, otherTerm.lhs);
            return true;
        }
        else
        {
            return false;
        }
    }
}

Quindi, per verificare la presenza di una struttura molto specifica, puoi creare una nuova istanza di Term con la struttura che desideri e passare a questo metodo, che ignora i valori e confronta solo i tipi di termini.

Funziona per il tuo caso?

EDIT: modificato sopra il codice per poter anche recuperare elementi dalle strutture.

La query potrebbe essere implementata in questo modo:

//t1 * (t2 + t3)
Sum s = new Sum(new Term(23), new Term(89));
Multiplication m = new Multiplication(new Term(123), s);

Term queryTerm = m;
Object t3Pointer = s.rhs;

Term actualRuntimeTerm = SomeActualTermOfYourApp();

if (queryTerm.CopyValues(actualRuntimeTerm))
    Console.WriteLine("T3 = {0}", t3Pointer);
}
    
risposta data 25.08.2017 - 20:40
fonte

Leggi altre domande sui tag