eternity
eternity

Reputation: 1728

Repeatable pattern matching

Consider the following simple example.

type PaymentInstrument =
    | Check of string
    | CreditCard of string * DateTime

let printInstrumentName instrument = 
    match instrument with 
    | Check number-> printfn "check"
    | CreditCard (number, expirationDate) -> printfn "card"

let printRequisites instrument =
    match instrument with 
    | Check number -> printfn "check %s" number
    | CreditCard (number, expirationDate) -> printfn "card %s %A" number expirationDate

As you can see the same pattern matching logic is repeated in two functions. If I would use OOP I would create interface IPaymentInstrument, define two operations:

PrintInstrumentName and PrintRequisites

and then implement classes - one per payment instrument. To instantiate instrument depending on some external conditions I would use (for example) the factory pattern (PaymentInstrumentFactory).

If I would need to add a new payment instrument, I just need to add a new class which implements IPaymentInstrument interface and update factory instantiating logic. Other code that uses these classes remains as is.

But if I use the functional approach I should update each function where pattern matching on this type exists.

If there will be a lot of functions using PaymentInstrument type that will be a problem.

How to eliminate this problem using functional approach?

Upvotes: 8

Views: 600

Answers (2)

eternity
eternity

Reputation: 1728

Using Mark Seemann's answer I came to such design decision.

type PaymentInstrument =
    | Check of string
    | CreditCard of string * DateTime

type Operations =
    { 
      PrintInstrumentName : unit -> unit
      PrintRequisites : unit -> unit
    }

let getTypeOperations instrument =
    match instrument with 
    | Check number-> 
        let printCheckNumber () = printfn "check"
        let printCheckRequisites () = printfn "check %s" number
        { PrintInstrumentName = printCheckNumber; PrintRequisites = printCheckRequisites }
    | CreditCard (number, expirationDate) -> 
        let printCardNumber () = printfn "card"
        let printCardRequisites () = printfn "card %s %A" number expirationDate
        { PrintInstrumentName = printCardNumber; PrintRequisites = printCardRequisites }

And usage

let card = CreditCard("124", DateTime.Now)
let operations = getTypeOperations card
operations.PrintInstrumentName()
operations.PrintRequisites()

As you can see the getTypeOperations function executes the role of factory pattern. To aggregate functions in a one bunch I use simple record type (however, according to F# design guidelines http://fsharp.org/specs/component-design-guidelines/ interfaces are preferred to such decision but I am interested to do it in functional approach for now to understand it better).

I got what I wanted - pattern matching is in only one place for now.

Upvotes: 1

Mark Seemann
Mark Seemann

Reputation: 233135

As Patryk Ćwiek points out in the comment above, you're encountering the Expression Problem, so you'll have to choose one or the other.

If the ability to add more data types is more important to you than the ability to easily add more behaviour, then an interface-based approach may be more appropriate.

In F#, you can still define object-oriented interfaces:

type IPaymentInstrument =
    abstract member PrintInstrumentName : unit -> unit
    abstract member PrintRequisites : unit -> unit

You can also create classes that implement this interface. Here's Check, and I'll leave CreditCard as an exercise to the reader:

type Check(number : string) =
    interface IPaymentInstrument with
        member this.PrintInstrumentName () = printfn "check"
        member this.PrintRequisites () = printfn "check %s" number

Yet, if you want to go the object-oriented way, you should begin to consider the SOLID principles, one of which is the Interface Segregation Principle (ISP). Once you start applying the ISP aggressively, you'll ultimately end up with interfaces with a single member, like this:

type IPaymentInstrumentNamePrinter =
    abstract member PrintInstrumentName : unit -> unit

type IPaymentInstrumentRequisitePrinter =
    abstract member PrintRequisites : unit -> unit

You can still implement this in classes:

type Check2(number : string) =
    interface IPaymentInstrumentNamePrinter with
        member this.PrintInstrumentName () = printfn "check"
    interface IPaymentInstrumentRequisitePrinter with
        member this.PrintRequisites () = printfn "check %s" number

This is beginning to seem slightly ridiculous now. If you're using F#, then why go to all the trouble of defining an interface with a single member?

Why not, instead, use functions?

Both desired interface members have the type unit -> unit (not a particularly 'functionally' looking type, though), so why not pass such functions around, and dispense with the interface overhead?

With the printInstrumentName and printRequisites functions from the OP, you already have the desired behaviour. If you want to turn them into polymorphic 'objects' that 'implement' the desired interface, you can close over them:

let myCheck = Check "1234"
let myNamePrinter () = printInstrumentName myCheck

In Functional Programming, we don't call these things objects, but rather closures. Instead of being data with behaviour, they're behaviour with data.

Upvotes: 17

Related Questions