Wie werden bedingte Verzweigungen in der funktionalen Programmierung beseitigt?

9

Lang laufende Switch-Fälle oder If-else-If-Konstrukte werden in OOP mithilfe von Polymorphismus vermieden, wo immer dies anwendbar ist.

Anstatt durch Abgleichen eines Werts zu verzweigen, erfolgt die Verzweigung auf Klassenebene.

Wie kann ein ähnlicher Ansatz im Paradigma der funktionalen Programmierung, speziell Clojure, angewendet werden?

Amogh Talpallikar
quelle
1
Das hängt von der Sprache ab. Scala zum Beispiel ist eine funktionale Sprache, die auch objektorientierte Funktionen hat. Sie tun also genau das Gleiche. Cloure hat mehrere Methoden. In Haskell haben Sie möglicherweise eine Funktion, die für eine bestimmte Typklasse definiert ist, und unterschiedliche Datentypen können unterschiedliche Implementierungen der Funktion ergeben.
Andrea
Ich habe keine Erfahrung in funktionalen Sprachen, um es wirklich zu sagen, aber von dem Wenigen, das ich weiß, denke ich, dass es mehr auf Lambdas, Monaden und satzähnliche Operationen ausgerichtet ist, insbesondere unter Verwendung von Sequenzen und der seq quick ref und was auch immer das Äquivalent zu ist Matcher.
JustinC
@Andrea: Danke. Multi-Methoden auschecken.
Amogh Talpallikar

Antworten:

13

Sie meiden sie nicht, sie umarmen sie mit der Pattern-Match-Syntax.

Die funktionale Programmierung ist jedoch weitgehend orthogonal zur objektorientierten Programmierung, so dass die absolute Mehrheit der "funktionalen" Sprachen auch objektorientiert ist¹, einschließlich Clojure. Tatsächlich sind die Multi-Methoden von clojure sogar noch besser als einfache virtuelle Java-Methoden, da sie nicht nur die ersten, sondern mehrere Arten von Argumenten dynamisch auslösen können.

Es gibt eine rein funktionale Sprache ohne dynamischen Polymorphismus, Haskell. In Haskell können Sie mehrere Methoden über Typklassen definieren, die Typen werden jedoch zur Kompilierungszeit aufgelöst. Um zur Laufzeit unterschiedliche Typen zu haben, müssen Sie einen Union-Typ erstellen und entweder die Funktion mit Musterübereinstimmung schreiben (ähnlich wie die if-Kette, jedoch mit einer bequemeren Syntax) oder den Compiler bitten, die Methode durch Zusammenstellen des abzuleiten Methoden der Bestandteilstypen. Oder verwenden Sie die GHC- forallErweiterung.


¹ Mit objektorientiert meine ich, dass die Sprache eine Form von dynamischem Polymorphismus aufweist, wobei der Versand auf dem tatsächlichen Laufzeittyp basiert. Viele neue Sprachen haben nur einen auf Merkmalen basierenden Polymorphismus, bei dem nur Schnittstellen vererbt werden können. Ich halte das für objektorientiert und für den Zweck dieser Antwort ist es ausreichend.

Jan Hudec
quelle
1) Clojure ist nicht objektorientiert nach dem Sinn des Wortes, den ich kenne. Wenn Sie etwas anderes glauben, sollten Sie Ihre Argumentation in der Antwort angeben. 2) Clojure unterstützt keine Vererbung im Sinne von OO. Sie können es so nachahmen : gist.github.com/david-mcneil/661983 , aber ich würde die Möglichkeit, Versandkarten zusammenzuführen , nicht als Vererbungsschema bezeichnen. 3) Es ist eine ziemlich kühne Aussage zu sagen, dass Haskell die einzige Sprache ist, die keine Vererbung hat. Ich glaube auch, dass es falsch ist. Wenn Sie anders glauben, sollten Sie Ihre Argumentation in Ihrer Antwort angeben.
Tim Pote
@Tim, solange die Sprache in der Lage ist, Merkmale / Schnittstellen für Objekte zu definieren und eine vom Merkmal typisierte Variable (einschließlich vollständig dynamisch) mit Versand basierend auf dem tatsächlichen Wert zur Laufzeit haben kann, nenne ich sie objektorientiert. Das ist alles, was Sie an Objektorientierung in vielen neuen Sprachen (z. B. Go, Rust) erhalten, und von der Klassenvererbung in den Sprachen, in denen sie sowieso vorhanden ist, wird abgeraten. Das von Ihnen verknüpfte Vererbungsschema ist eine auf Merkmalen basierende Vererbung, daher zähle ich es als objektorientiert.
Jan Hudec
@ Tim, Anzeige Punkt 3, die Antwort besagt nicht, dass Haskell nur eine solche Sprache war. Nur dass es ein wichtiges Beispiel für eine solche Sprache ist. Wo hast du das Wort "nur" darin gesehen?
Jan Hudec
RE: Punkt 3: Ich habe "es gibt einen" als "nur einen" gelesen. So fair genug. RE: OO: Java sendet keine Laufzeitwerte. Nach Ihrer Definition ist es also nicht OO. Haskell hat einen Typversand, also ist es Ihrer Definition nach OO. Neben dem Versand ist nur eine Facette von OO. Das staatliche Management ist ein weiterer wichtiger Faktor. In beiden Fällen bin ich mir nicht sicher, ob eine Diskussion darüber, was OO ist oder nicht, für die Frage relevant ist. Sie könnten also sicherlich alle Urteilsforderungen darüber entfernen.
Tim Pote
@ TimPote: Java sendet definitiv den Laufzeitwert des Invocants aus (jedoch nicht die anderen Argumente). Haskell hat einen Typversand, aber wenn Sie nicht die ghc- forallErweiterung verwenden, ist die Kompilierungszeit vollständig.
Jan Hudec
4

Dies ist eine sehr alte Frage, aber ich habe das Gefühl, dass die Antworten fehlten.

Wie Sie bereits erwähnt haben, wird die Verzweigung in OO häufig auf die Klassenebene verschoben. Lassen Sie uns darüber nachdenken, was das bedeutet:

  1. Die Factory-Methode behandelt die Verzweigung und gibt eine abgeleitete Klasse zurück.
  2. Die abgeleitete Klasse ist eine Sammlung optimierter Methoden.
  3. Die Factory-Methode ist also eine Methode, die optimierte Methoden zurückgibt.

Genau so würden Sie damit umgehen: eine Funktion höherer Ordnung. Ihre Funktion höherer Ordnung übernimmt die Verzweigung und gibt optimierte Funktionen für den Verbrauch zurück.

Im Kontext Ihrer Frage ist Polymorphismus eine Art Abstraktion dafür - mit zusätzlicher Typensicherheit.

Daniel
quelle
-3

In der funktionalen Programmiersprache können wir Funktionen und Schlüsselparameter verwenden, um bedingte Verzweigungen zu entfernen. Das heißt, verwenden Sie Funktionen mit Bedingungsparameter anstelle von "if esle". Siehe Beispiel 3. Als computeSphereArea ({Radius: 25,55})

Beispiel 1: OOP // in OOP (verwenden Sie beispielsweise Java (Quellcode von: http: //developer.51cto.com/art/200907/136506.htm)):

public abstract class Shape {
    //    ...

    public abstract void computeArea();
    public abstract void computeVolume();
    public abstract double getArea();
    public abstract double getVolume();

}
public class Circle extends CircleShape2 {
    //    ...
    double volume = 0.0; //
    public void computeArea() { //
        area = Math.PI * radius * radius;
    }
    public double getArea() {
        return area;
    }
    public void computeVolume() {} //
    public double getVolume() {
        return volume;
    }

}
public class Sphere extends Circle {
    //    ...
    public void computeArea() { //
        super.computeArea(); //
        area = 4 * area;
    }
    public void computeVolume() { //
        super.computeArea(); //
        volume = 4.0 / 3 * radius * area;
    }
}
public class CircleShapeApp {
    public static void main(String[] args) {
        Circle circle = new Circle(12.98);
        Sphere sphere = new Sphere(25.55);

        Shape shape = circle; //
        //
        shape.computeArea();
        shape.computeVolume();
        System.out.println("circle area: " + shape.getArea());
        System.out.println("circle volume: " + shape.getVolume());
        //
        shape = sphere;
        shape.computeArea();
        shape.computeVolume();
        System.out.println("Sphere area: " + shape.getArea());
        System.out.println("Sphere volume: " + shape.getVolume());
    }
}

Beispiel 2: funktional wie oop. // in der funktionalen Programmierung (verwenden Sie zum Beispiel Javascript):

function initShape(v) {
    var shape = {};
    v = v || {};
    if (typeOf(v, 'object') === true) {
        shape.volumne = v.volumne || 0.0;
        shape.computeArea = v.computeArea || function() {};
        shape.computeVolume = v.computeVolume || function() {};
        shape.getArea = v.getArea || function() {};
        shape.getVolume = v.getVolume || function() {};
    }

    return shape;
}

function initCircle(v) {
    var circle = {};
    v = v || {};
    if (typeOf(v, 'object') === true) {
        circle.volume = 0.0;
        circle.radius = v.radius || 0.0;
        circle.computeArea = v.computeArea || function() {
            this.area = Math.PI * this.radius * this.radius;
        };
        circle.computeVolume = function() {};
        circle.getArea = v.getArea || function() {
            return this.area
        };
        circle.getVolume = v.getVolume || function() {
            return this.volume
        };
    }
    return initShape(circle);
}

function initSphere(v) {
    var sphere = {}
    v = v || {};
    if (typeOf(v, 'object') === true) {
        var circle = initCircle(v);
        sphere = circle;
        sphere.volume = v.volume;
        sphere.computeArea = function() {
            circle.computeArea();
            this.area = 4 * circle.area;
        }
        sphere.computeVolume = function() {
            circle.computeArea();
            this.volume = 4.0 / 3 * this.radius * circle.area;
        }
    }
    return initShape(sphere);
}
var circle = initCircle(12.98);
circle.computeArea();
circle.computeVolume();
console.log("circle area: " + circle.getArea());
console.log("circle volume: " + circle.getVolume());

var sphere = initShpere(25.55);
sphere.computeArea();
sphere.computeVolume();
console.log("sphere area: " + sphere.getArea());
console.log("sphere volume: " + sphere.getVolume());

// Dies ist zwar kein reines Funktionsprogrammbeispiel, sondern eine Funktionsschnittstelle wie initCircle () initSphere (). Sie können weitere Funktionen erstellen, wie computeCircleArea () computeSphereArea () es funktionaler macht. // PS: typeOf () ist hier: https://github.com/will-v-king/javascript-showMe

Beispiel 3: Ok, machen wir es funktionsfähiger:

/** in functional code shape became meaningless. 
function initShape(v) {
    var shape = {};
    v = v || {};
    if (typeOf(v, 'object') === true) {
        shape = v.object || v.shape || shape;
        shape.volumne = v.volumne || 0.0;
    }
    return shape;
}

function computeShapeArea(v){
}
function computeShapeVolume(v){
}
*/

function initCircle(v) {
    var circle = {};
    v = v || {};
    if (typeOf(v, 'object') === true) {
        circle = v.object || v.circle || circle;
        circle.volume = 0.0;
        circle.radius = v.radius || 0.0;
    }
    return initShape(circle);
}

function computeCircleArea(v){
  var area;
  v = v || {};
  if(typeOf(v) === 'Object'){
    var radius = v.radius || v.object.radius || v.circle.radius;
    if(!typeOf(v,'undefined')){
       area = Math.PI * radius * radius;
    }
  }
  return area;
}
function computeCircleVolume(v){
  return 0.0;
}
/**function initCircle and initSphere are not necessary. why? see the last line.*/
function initSphere(v) {
    var sphere = {}
    v = v || {};
    if (typeOf(v, 'object') === true) {
        var circle = initCircle(v);
        sphere = circle;
        sphere.volume = v.volume;
    }
    return initShape(sphere);
}

function computeSphereArea(v){
  var area;
  v = v || {};
  if(typeOf(v) === 'Object'){
    var radius = v.radius || v.object.radius || v.sphere.radius;
    if(!typeOf(v,'undefined')){
       area = 4 * computeCircleArea({radius:radius});  // **POINT** the same as :circle.computeArea();  this.area = 4 * circle.area;
    }
  }
  return area;
}
function computeSphereVolume(v){
  var volume;
  v = v || {};
  if(typeOf(v,'object') === ture){
    radius = v.radius || typeOf(v.object, 'object') === true ? v.object.radius : typeOf(v.sphere, 'Object') === true ? v.sphere.radius : 0.0;
    var circleArea = computeCircleArea({radius:radius});
    if(typeOf(circleArea,'number')=== true){
      volume = 4.0 / 3 * radius * computeCircleArea({radius:radius}); // **POINT** the same as:    circle.computeArea();  this.volume = 4.0 / 3 * this.radius * circle.area;
    }
  }
  return volume;
}


var circle = initCircle({radius:12.98});
console.log("circle area: " + computeCircleArea(circle) );
console.log("circle volume: " + computeCircleVolume(circle) );

var sphere = initShpere(25.55);
console.log("sphere area: " + computeSphereArea({radius:25.55}) );
console.log("sphere volume: " + computeSphereVolume({radius:25.55}) );
console.log("sphere object is unused.That means initSphere is also not necessary as initShape()");
werden
quelle
3
Ihre Antwort wäre stärker, wenn Sie erklären würden, warum Sie die Entscheidungen getroffen haben, die Sie anhand der beiden von Ihnen angegebenen Beispiele getroffen haben.