Como as pessoas se livram de ramificações condicionais na Programação Funcional?

9

Casos de comutação de longa execução ou construções if-else-if são evitados no OOP usando polimorfismo sempre que aplicável.

em vez de ramificar pela correspondência de um valor, a ramificação é feita no próprio nível de classe.

Como uma abordagem semelhante pode ser aplicada no paradigma de Programação Funcional, especificamente no Clojure?

Amogh Talpallikar
fonte
1
Depende do idioma. Scala, por exemplo, é uma linguagem funcional que também possui recursos orientados a objetos, para que você faça o mesmo. Cloure tem vários métodos. No Haskell, você pode ter uma função definida em uma determinada classe de tipo e tipos de dados diferentes podem fornecer implementações diferentes da função.
Andrea
Eu não tenho experiência em linguagens funcionais para dizer realmente, mas pelo pouco que sei, acho que é mais orientado para operações lambdas, mônadas e operações de conjunto, particularmente utilizando sequências e o seq quick ref e o que é equivalente a matchers.
28813 JustinC
@ Andrea: Obrigado. Verificando vários métodos.
Amogh Talpallikar

Respostas:

13

Eles não os evitam, eles os abraçam usando a sintaxe de correspondência de padrões.

Porém, a programação funcional é amplamente ortogonal à programação orientada a objetos; portanto, a maioria absoluta das linguagens "funcionais" também é orientada a objetos¹, incluindo clojure. De fato, os multi-métodos do clojure são ainda melhores que os métodos virtuais simples de Java, porque eles podem despachar dinamicamente tipos de múltiplos argumentos, não apenas o primeiro.

Existe uma linguagem puramente funcional que não possui polimorfismo dinâmico, Haskell. No Haskell, você pode definir vários métodos por meio de classes de tipos, mas os tipos são resolvidos em tempo de compilação. Para ter tipos variados em tempo de execução, é necessário criar um tipo de união e você deve escrever a função com correspondência de padrão (que é como a cadeia if, mas com sintaxe mais conveniente) ou pedir ao compilador para derivar o método compondo o método métodos dos tipos constituintes. Ou use a forallextensão GHC .


Por orientação a objeto, quero dizer que a linguagem possui alguma forma de polimorfismo dinâmico com despacho baseado no tipo de tempo de execução real. Muitas novas linguagens possuem apenas polimorfismos "baseados em características", onde apenas as interfaces podem ser herdadas; Eu considero que, como orientado a objetos, e para o propósito desta resposta, é suficiente.

Jan Hudec
fonte
1) Clojure não é orientado a objetos por nenhum sentido da palavra que eu conheço. Se você acredita de outra forma, deve declarar seu raciocínio na resposta. 2) Clojure não suporta herança no sentido OO da palavra. Você pode usar o seguinte : gist.github.com/david-mcneil/661983 , mas eu não chamaria a capacidade de mesclar mapas de expedição como um esquema de herança. 3) É uma afirmação bastante ousada dizer que Haskell é a única linguagem que não tem herança. Eu também acredito que está incorreto. Se você acredita de outra forma, deve declarar seu raciocínio em sua resposta.
Tim Pote
@ Tim, desde que a linguagem tenha capacidade de definir características / interfaces para objetos e possa ter uma variável do tipo característica (incluindo totalmente dinâmica) com despacho com base no valor real em tempo de execução, eu chamo de orientação a objeto. É toda a orientação a objetos que você obtém em muitos idiomas novos (por exemplo, Go, Rust) e a herança de classe está sendo desencorajada nos idiomas que o possuem de qualquer maneira. O esquema de herança que você vinculou é uma herança baseada em características, então eu a conto como orientada a objetos.
Jan Hudec
@ Tim, ponto de anúncio 3, a resposta não indica que Haskell era apenas esse idioma. Só que é um exemplo importante dessa linguagem. Onde você viu a palavra "somente" nela?
Jan Hudec
RE: ponto 3: Li "existe um" como "apenas um". Tão justo o suficiente. RE: OO: Java não despacha em valores de tempo de execução. Então, pela sua definição, não é OO. Haskell tem o tipo dispatch, portanto, por sua definição, é OO. Além da expedição, há apenas uma faceta do OO. A gestão do estado é outro fator importante. De qualquer forma, não tenho certeza de que uma discussão sobre o que é ou não OO seja relevante para a questão. Portanto, você pode simplesmente remover qualquer julgamento sobre isso.
Tim Pote
@ TimPote: Java definitivamente despacha em valor de tempo de execução do invocante (embora não os outros argumentos). Haskell tem um envio de tipo, mas, a menos que você use a forallextensão ghc , ela é completamente em tempo de compilação.
Jan Hudec
4

Esta é uma pergunta muito antiga, mas sinto que as respostas estavam faltando.

Como você mencionou, na ramificação OO é freqüentemente movida para o nível de classe. Vamos pensar no que isso significa:

  1. O método Factory trata da ramificação, retornando uma classe derivada.
  2. A classe derivada é uma coleção de métodos simplificados.
  3. Portanto, o método de fábrica é um método que retorna métodos simplificados.

É exatamente assim que você lidaria com isso: uma função de ordem superior. Sua função de ordem superior lida com ramificação, retornando funções simplificadas para consumo.

No contexto da sua pergunta, o polimorfismo é uma abstração para isso - com segurança adicional do tipo.

Daniel
fonte
-3

Na linguagem de programação funcional, podemos usar funções e parâmetros principais para se livrar de ramificações condicionais. Isso significa usar funções com a condição param em vez de "if esle". Veja o exemplo 3. Como computeSphereArea ({radius: 25.55})

Exemplo 1: OOP // no OOP (use java por exemplo (sourceCode from: 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());
    }
}

Exemplo 2: funcional como oop. // na programação funcional (use javascript, por exemplo):

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

// Embora este não seja um exemplo de programa funcional puro, mas com uma interface funcional, como initCircle () initSphere (). Você pode criar mais funções como computeCircleArea () computeSphereArea () para torná-lo mais funcional. // PS: typeOf () está aqui: https://github.com/will-v-king/javascript-showMe

Exemplo3: Ok, vamos torná-lo mais 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()");
vai
fonte
3
Sua resposta seria mais forte se você explicasse por que tomou as decisões que tomou com os dois exemplos que forneceu.