¿Cómo se eliminan las ramas condicionales en la programación funcional?

9

Los casos de interruptores de larga duración o las construcciones if-else-if se evitan en OOP utilizando polimorfismo donde sea aplicable.

en lugar de bifurcar haciendo coincidir un valor, la bifurcación se realiza a nivel de clase.

¿Cómo se puede aplicar un enfoque similar en el paradigma de la programación funcional, específicamente Clojure?

Amogh Talpallikar
fuente
1
Depende del idioma. Scala, por ejemplo, es un lenguaje funcional que también tiene capacidades orientadas a objetos, por lo que solo debes hacer lo mismo. Cloure tiene múltiples métodos. En Haskell, puede tener una función que se define en una determinada clase de tipo y diferentes tipos de datos pueden dar diferentes implementaciones de la función.
Andrea
No tengo experiencia en lenguajes funcionales para decir realmente, pero por lo poco que sé, creo que está más orientado a lambdas, mónadas y operaciones tipo set, particularmente utilizando secuencias y la secuencia rápida de referencia y lo que sea equivalente a Matchers.
JustinC
@ Andrea: Gracias. Verificación de múltiples métodos.
Amogh Talpallikar

Respuestas:

13

No los evitan, los adoptan utilizando la sintaxis de coincidencia de patrones.

Pero la programación funcional es en gran parte ortogonal a la programación orientada a objetos, por lo que la mayoría absoluta de los lenguajes "funcionales" también están orientados a objetos¹, incluido clojure. De hecho, los métodos múltiples de clojure son incluso mejores que los métodos virtuales simples de Java, ya que pueden distribuir dinámicamente tipos de argumentos múltiples, no solo el primero.

Hay un lenguaje puramente funcional que no tiene polimorfismo dinámico, Haskell. En Haskell puede definir métodos múltiples a través de clases de tipos, pero los tipos se resuelven en tiempo de compilación. Para tener diferentes tipos en tiempo de ejecución, debe crear un tipo de unión y escribir la función con coincidencia de patrones (que es como la cadena if, pero con una sintaxis más conveniente) o pedirle al compilador que derive el método componiendo el métodos de los tipos constituyentes. O use la forallextensión GHC .


¹ Por orientado a objetos quiero decir que el lenguaje tiene alguna forma de polimorfismo dinámico con despacho basado en el tipo de tiempo de ejecución real. Muchos lenguajes nuevos solo tienen polimorfismo "basado en rasgos" donde solo se pueden heredar interfaces; Cuento que como orientado a objetos y para el propósito de esta respuesta es suficiente.

Jan Hudec
fuente
1) Clojure no está orientado a objetos por ningún sentido de la palabra que conozco. Si cree lo contrario, debe indicar su razonamiento en la respuesta. 2) Clojure no admite la herencia en el sentido OO de la palabra. Puede simularlo así: gist.github.com/david-mcneil/661983 , pero no llamaría a la capacidad de fusionar mapas de despacho un esquema de herencia. 3) Es una afirmación bastante audaz decir que Haskell es el único idioma que no tiene herencia. También creo que es incorrecto. Si cree lo contrario, debe indicar su razonamiento en su respuesta.
Tim Pote
@Tim, siempre que el lenguaje tenga la capacidad de definir rasgos / interfaces para objetos y pueda tener una variable de tipo de rasgo (incluso completamente dinámica) con despacho basado en el valor real en tiempo de ejecución, lo llamo orientado a objetos. Esa es toda la orientación a objetos que obtendrás en muchos idiomas nuevos (por ejemplo, Go, Rust) y la herencia de clase se desaconseja en los idiomas que la tienen de todos modos. El esquema de herencia que vinculó es una herencia basada en rasgos, por lo que lo considero orientado a objetos.
Jan Hudec
@Tim, punto 3 del anuncio, la respuesta no indica que Haskell era solo ese lenguaje. Solo que es un ejemplo importante de tal lenguaje. ¿Dónde viste la palabra "solo" en ella?
Jan Hudec
RE: punto 3: leí "hay uno" como "solo uno". Tan justo lo suficiente. RE: OO: Java no se distribuye en valores de tiempo de ejecución. Entonces, según su definición, no es OO. Haskell tiene envío de tipo, por lo que, según su definición, es OO. Además el despacho es solo una faceta de OO. La gestión estatal es otro factor importante. De cualquier manera, no estoy seguro de que una discusión sobre qué es o no es relevante para la pregunta. Entonces, ciertamente podría eliminar cualquier llamada de juicio al respecto.
Tim Pote
@TimPote: Java definitivamente distribuye el valor de tiempo de ejecución del invocante (aunque no los otros argumentos). Haskell tiene un envío de tipo, pero a menos que use la forallextensión ghc , es completamente en tiempo de compilación.
Jan Hudec
4

Esta es una pregunta muy antigua, pero siento que faltaban las respuestas.

Como mencionó, en OO la ramificación se mueve frecuentemente al nivel de clase. Pensemos en lo que eso significa:

  1. El método de fábrica maneja la ramificación, devolviendo una clase derivada.
  2. La clase derivada es una colección de métodos simplificados.
  3. Entonces, el método de fábrica es un método que devuelve métodos simplificados.

Así es exactamente como lo manejarías: una función de orden superior. Su función de orden superior maneja la ramificación, devolviendo funciones optimizadas para el consumo.

En el contexto de su pregunta, el polimorfismo es una especie de abstracción para eso, con mayor seguridad de tipo.

Daniel
fuente
-3

En el lenguaje de programación funcional, podemos usar funciones y parámetros clave para deshacernos de las ramas condicionales. Eso significa usar funciones con la condición param en lugar de "if esle". Ver ejemplo 3. Como computeSphereArea ({radio: 25.55})

Ejemplo 1: OOP // en OOP (use java por ejemplo (código fuente de: 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());
    }
}

Ejemplo 2: funcional como oop. // en programación funcional (use javascript por ejemplo):

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

// Aunque este no es un ejemplo de programa funcional puro, sino con una interfaz funcional, como initCircle () initSphere (). Puedes crear más funciones como computeCircleArea () computeSphereArea () lo hace más funcional. // PS: typeOf () está aquí: https://github.com/will-v-king/javascript-showMe

Ejemplo 3: Ok, hagámoslo más funcional:

/** 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()");
será
fuente
3
Su respuesta sería más sólida si explicara por qué tomó las decisiones que tomó con los dos ejemplos que proporcionó.