9

Long running switch cases or if-else-if constructs are avoided in OOP using polymorphism wherever it is applicable.

instead of branching by matching a value, branching is done at class-level itself.

How can similar approach be applied in Functional Programming paradigm, Clojure specifically ?

3
  • 1
    It depends on the language. Scala, for instance, is a functional language that also has object-oriented capabilities, so you just do the same. Cloure has multimethods. In Haskell you may have a function that are defined on a certain typeclass and different datatype may give different implementations of the function.
    – Andrea
    Commented Jun 28, 2013 at 7:10
  • I don't have experience in functional languages to really say, but from the little I know I think that its more oriented around lambdas, monads, and set-like operations, particularly utilizing sequences and the seq quick ref and whatever is the equivalent to matchers.
    – JustinC
    Commented Jun 28, 2013 at 7:19
  • @Andrea:Thanks. Checking out multi-methods. Commented Jun 28, 2013 at 9:03

3 Answers 3

13

They don't avoid them, they embrace them using the pattern match syntax.

But functional programming is largely orthogonal to object oriented programming, so absolute majority of "functional" languages are also object oriented¹, including clojure. In fact clojure's multi-methods are even better than plain virtual methods of Java, because they can dynamically dispatch on types of multiple arguments, not just the first one.

There is one purely functional language that does not have dynamic polymorphism, Haskell. In Haskell you can define multi-methods via type classes, but the types are resolved at compile time. To have varying types at runtime, you have to create a union type and you either have to write the function with pattern match (which is like the if chain, but with more convenient syntax) or ask the compiler to derive the method by composing the methods of the constituent types. Or use the GHC forall extension.


¹ By object-oriented I mean that the language has some form of dynamic polymorphism with dispatch based on actual runtime type. Many new languages only have “trait-based” polymorphism where only interfaces can be inherited; I count that as object-oriented and for purpose of this answer it is sufficient.

7
  • 1) Clojure is not object oriented by any sense of the word that I know. If you believe otherwise, you should state your reasoning in the answer. 2) Clojure does not support inheritance in the OO sense of the word. You can ape it like so: gist.github.com/david-mcneil/661983, but I would not call the ability to merge dispatch maps an inheritance scheme. 3) It's a rather bold statement to say that Haskell is the only language that does not have inheritance. I also believe it to be incorrect. If you believe otherwise, you should state your reasoning in your answer.
    – Tim Pote
    Commented Jul 30, 2015 at 2:23
  • @Tim, as long as language has ability to define traits/interfaces for objects and can have trait-typed variable (including fully dynamic) with dispatch based on the actual value at runtime, I call it object-oriented. That's all of object orientation you'll get in many new languages (e.g. Go, Rust) and class inheritance is being discouraged in the languages that have it anyway. The inheritance scheme you linked is a trait-based inheritance, so I count it as object oriented.
    – Jan Hudec
    Commented Jul 30, 2015 at 5:13
  • @Tim, ad point 3, the answer does not state that Haskell was only such language. Only that it is an important example of such language. Where did you see the word “only” in it?
    – Jan Hudec
    Commented Jul 30, 2015 at 5:15
  • RE: point 3: I read "there is one" as "only one". So fair enough. RE: OO: Java does not dispatch on runtime values. So by your definition it's not OO. Haskell has type dispatch, so by your definition it is OO. Besides dispatch is only one facet of OO. State management is another major factor. Either way, I'm not sure a discussion of what is or isn't OO is relevant to the question. So you could certainly just remove any judgement calls about it.
    – Tim Pote
    Commented Jul 31, 2015 at 15:40
  • @TimPote: Java definitely dispatches on runtime value of the invocant (though not the other arguments). Haskell has a type-dispatch, but unless you use the ghc forall extension, it is completely compile-time.
    – Jan Hudec
    Commented Aug 3, 2015 at 4:57
4

This is a very old question, but I feel like the answers were lacking.

As you mentioned, in OO branching is frequently moved to the class level. Let's think about what that means:

  1. Factory method handles branching, returning a derived class.
  2. The derived class is a collection of streamlined methods.
  3. So the factory method is a method that returns streamlined methods.

That is exactly how you would handle it: a higher order function. Your higher order function handles branching, returning streamlined functions for consumption.

In the context of your question, polymorphism is a bit of an abstraction for that--with added type safety.

-3

In functional programming language, we can use functions and key params to get rid of conditional branches. That mean use functions with condition param instead of "if esle". See example 3. As computeSphereArea({radius:25.55})

Example 1: OOP // in OOP(use java for example(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());
    }
}

Example 2: functional like oop. //in functional programming(use javascript for example):

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

// Although, this is not pure functional program example, but with functional interface, like initCircle() initSphere().You can create more functions like computeCircleArea() computeSphereArea() makes it more functional. // PS:typeOf() is here:https://github.com/will-v-king/javascript-showMe

Example3: Ok, let's make it more funcitonal:

/** 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()");
1
  • 3
    Your answer would be stronger if you explained why you made the decisions you did with the two examples you provided.
    – user53019
    Commented Jun 28, 2013 at 10:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.