3

What patterns are available for adhering to the Open/Close principle when programming in F#?

For example, I'm sure Pattern Matching within F# is great in some scenarios. But what guidance exists when you don't want to modify the Pattern Matching construct each time a new business case is made to operate on?

Example:

let getFullName nickName = 
    match nickName with
    | "Bizmonger" | "Rico" -> "Scott Nimrod"
    | _ -> "not found"

getFullName "Bizmonger";;

What happens when new cases are introduced?

Hence, I don't want to keep modifying this function.

Scott Nimrod
  • 11,206
  • 11
  • 54
  • 118
  • If you add new case to discriminated union you have to have the code matching this case hence you have to modify matching function. (You can use reflection to avoid it but it's ugly and non-performant) – Petr Nov 13 '15 at 15:42
  • 1
    Some lecture [here](http://stackoverflow.com/a/5578381/4925216) or [there](http://fsharpforfunandprofit.com/fppatterns/) – Sehnsucht Nov 13 '15 at 15:47

2 Answers2

5

I think the crux of the problem you are encountering is called the expression problem.

Functional code using algebraic data types sits on one side, where it is very to define an initial problem domain and to create and add new functions that operate on that domain. On the other hand, it is rather difficult, especially once you have developed a portfolio of functions to add types to the initial domain.

Object oriented code sits on the other side of the problem. Once you have defined your initial problem domain, it becomes extremely difficult to add new functionality because all of the types must be extended to implement it but it is relatively easy to extend the domain to new problems.

Consider

type Shape =
    |Circle of float
    |Square of float * float

module Shapes =
    let area = function
        |Circle r -> Math.PI * r ** 2.0
        |Square (a, b) -> a*b

    let perimeter = function
        |Circle r -> 2.0 * Math.PI * r
        |Square (a, b) -> 2.0 * (a + b)

Here, it's easy to add functions because I can just define behaviour for each type of shape but it's hard to add new shapes because I have to edit every function.

[<AbstractClass>]
type Shape() =
    abstract member Area : double
    abstract member Perimeter : double

type Circle(r) =
    inherit Shape()

    override this.Area = Math.PI * r **2.0
    override this.Perimeter = 2.0 * Math.PI * r 

type Square(a, b) =
    inherit Shape()

    override this.Area = a*b
    override this.Perimeter = 2.0 * (a+b)

Using OO, it's easy to add new shapes but it's hard to new properties or methods to Shape because I have to edit every type.

I don't know if you've ever used the visitor pattern in object oriented programming but the need for that pattern arises from the difficulty of expressing structures that can be readily expressed by algebraic data types in functional programming.

One of the great things about F# as a general purpose programming language is that you can decide which method of expressing a particular problem you want to use on a problem by problem basis. That gives you a huge amount of flexibility to pick the optimal approach to your software design.

TheInnerLight
  • 12,034
  • 1
  • 29
  • 52
4

well as you just found out in this case it's easy to add new functions (you could say it's open in this regard) but hard to add new cases

But of course you can just use classes/objects/interfaces in F# just as you do in C# and so you got your well-known OOP patterns as well


btw: in any case String is not a good choice to implement business cases ... you won't get OOP O/C and you won't get FP union-type goodies this way

Random Dev
  • 51,810
  • 9
  • 92
  • 119
  • Hmmm... I would expect that it would be common for cases to be mapped to ids such as integers and strings based on reads from a database. I'm completely new to this so please forgive me. – Scott Nimrod Nov 13 '15 at 15:50
  • 2
    sure but you use `Strings` here as the *label for cases* (just as you woud in C#) - it's no big dealt but usually you would go something like `type Nicknames = Bizmonger | Rico | ...` of course the example is a bit poor because you would just use an enum anyway but I hope you get the idea – Random Dev Nov 13 '15 at 15:52