関数型プログラミングで条件付きブランチを取り除くにはどうすればよいですか?


9

実行時間の長いswitchケースまたはif-else-if構文は、適用可能な場合はどこでも、ポリモーフィズムを使用してOOPで回避されます。

値を突き合わせて分岐する代わりに、分岐はクラスレベル自体で行われます。

関数型プログラミングのパラダイム、特にClojureで同様のアプローチをどのように適用できますか?


1
言語によって異なります。たとえば、Scalaはオブジェクト指向の機能も備えた関数型言語なので、同じことを行うだけです。Cloureにはマルチメソッドがあります。Haskellでは、特定の型クラスで定義された関数があり、データ型が異なると、関数の実装が異なる場合があります。
Andrea

私は関数型言語で実際に言う経験はありませんが、ラムダ、モナド、およびセットのような操作、特にシーケンスseqクイック参照を利用すること、および同等の操作を中心に考えていることを少し知っています。マッチャー。
JustinC 2013年

@Andrea:ありがとう。マルチメソッドをチェックアウトします。
Amogh Talpallikar 2013年

回答:


13

彼らはそれらを避けません、彼らはパターンマッチ構文を使用してそれらを受け入れます。

ただし、関数型プログラミングはオブジェクト指向プログラミングとほぼ直交しているため、「関数型」言語の絶対的な大多数も、clojureを含むオブジェクト指向です。実際、clojureのマルチメソッドは、最初の引数だけでなく、複数の引数の型に動的にディスパッチできるため、Javaの単純な仮想メソッドよりも優れています。

動的なポリモーフィズムを持たない純粋に関数型の言語であるHaskellがあります。Haskellでは、型クラスを介してマルチメソッドを定義できますが、型はコンパイル時に解決されます。実行時にさまざまな型を使用するには、共用体型を作成する必要があり、パターンマッチを使用して関数を作成する(ifチェーンに似ていますが、より便利な構文を使用する)か、または構成要素タイプのメソッド。または、GHC forall拡張を使用します。


objectオブジェクト指向とは、言語に、実際の実行時の型に基づいてディスパッチする動的ポリモーフィズムの形式があることを意味します。多くの新しい言語は、「トレイトベース」のポリモーフィズムしか持たないため、インターフェースのみを継承できます。私はそれをオブジェクト指向であるとみなし、この答えの目的にはそれで十分です。


1)Clojureは、私が知っている言葉の感覚によるオブジェクト指向ではありません。信じられない場合は、答えに自分の推論を記述してください。2)Clojureは、OOの意味での継承をサポートしていません。gist.github.com/david-mcneil/661983のように類推できますが、ディスパッチマップをマージする機能を継承スキームと呼ぶことはしません。3)Haskellは継承を持たない唯一の言語であると言うのはかなり大胆な発言です。私はまたそれが正しくないと信じています。他に信じるなら、あなたの答えにあなたの推論を述べるべきです。
Tim Pote、2015

@Tim、言語がオブジェクトの特性/インターフェイスを定義する機能を持ち、実行時に実際の値に基づいてディスパッチを行う特性型変数(完全に動的を含む)を持つことができる限り、私はそれをオブジェクト指向と呼びます。これは、多くの新しい言語(Go、Rustなど)で得られるオブジェクト指向のすべてであり、クラスの継承は、それを備えている言語では推奨されていません。リンクした継承スキームは特性ベースの継承なので、オブジェクト指向と見なします。
Jan Hudec 2015

@Tim、広告ポイント3、答えはHaskellがそのような言語だけであったことを述べていません。それだけがそのような言語の重要な例です。「唯一」という言葉はどこにありましたか?
Jan Hudec 2015

RE:ポイント3:「1つあります」を「1つだけ」と読みました。とても公正です。RE:OO:Javaはランタイム値をディスパッチしません。したがって、あなたの定義では、それはオブジェクト指向ではありません。Haskellにはタイプdispatchがあるので、あなたの定義ではそれはOOです。発送に加えて、OOの1つの面のみです。状態管理ももう1つの主要な要素です。どちらにしても、OOが何であるか、またはOOでないかについての議論が質問に関連しているかどうかはわかりません。したがって、それについての判断の呼びかけを削除するだけで済みます。
Tim Pote、2015

@TimPote:Javaは必ず(他の引数ではなく)呼び出し元のランタイム値をディスパッチします。Haskellにはtype-dispatchがありますが、ghc forall拡張を使用しない限り、完全にコンパイル時です。
Jan Hudec 2015

4

これは非常に古い質問ですが、答えが足りないような気がします。

あなたが述べたように、オブジェクト指向ではブランチはクラスレベルに頻繁に移されます。それが何を意味するのか考えてみましょう:

  1. ファクトリメソッドは分岐を処理し、派生クラスを返します。
  2. 派生クラスは、効率化されたメソッドのコレクションです。
  3. したがって、ファクトリメソッドは、効率化されたメソッドを返すメソッドです。

それがまさにあなたがそれを処理する方法です:高次関数。より高次の関数は分岐を処理し、効率化された関数を使用して返します。

あなたの質問の文脈では、ポリモーフィズムはそのためのちょっとした抽象化です-型安全性が追加されています。


-3

関数型プログラミング言語では、関数とキーパラメータを使用して、条件付きブランチを取り除くことができます。つまり、「if esle」の代わりに、条件パラメーターを持つ関数を使用します。例3を参照してください。computeSphereArea({radius:25.55})として

例1:OOPで// OOPを使用(たとえば、javaを使用(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());
    }
}

例2:oopのような機能。//関数型プログラミング(たとえば、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());

//ただし、これは純粋な関数型プログラムの例ではありませんが、initCircle()initSphere()のような関数型インターフェースを使用しています。// PS:typeOf()はこちら:https : //github.com/will-v-king/javascript-showMe

例3:OK、もっと機能的にしましょう:

/** 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()");

3
あなたが提供した2つの例を使用して決定を下した理由を説明すると、答えはより強力になります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.