ebb
ebb

Reputation: 9397

Function composition - when?

Given the following two approaches, what would the cons and pros of both, when it comes to function composition?

Approach 1

let isNameTaken source name =
   source |> Query.Exists(fun z -> z.Name = name)

let usage : Customer = isNameTaken source "Test"

Approach 2

let isNameTaken f name = 
   f(fun z -> z.Name = name)

let usage : Customer = isNameTaken (source |> Query.Exists) "Test"

Is it just silly to pass (source |> Query.Exists) in Approach 2 - is it too extreme?

Upvotes: 1

Views: 130

Answers (2)

plinth
plinth

Reputation: 49209

I think the answer to your question has to do with two main criteria. Which is more important, the readability of the code or the decoupling of the query from isNameTaken. In this particular case, I'm not sure that you get much at all from decoupling the query and it also seems like your decoupling is partial.

The thing I don't like about this is that in both cases, you've got z.Name tightly coupled into isNameTaken, which means that isNameTaken needs to know about the type of z. If that's OK with you then fine.

Upvotes: 0

Tomas Petricek
Tomas Petricek

Reputation: 243096

It depends on the wider context. I would generally prefer the first approach, unless you have some really good reason for using the second style (e.g. there is a number of functions similar to Query.Exists that you need to apply in a similar style).

Aside - I think your second example has a couple of issues (e.g. the piping in source |> Query.Exists would have to be replaced with (fun pred -> source |> Query.Exists pred) which makes it uglier.

Even then, the second approach does not really give you much benefit - your isNameTaken is simply a function that tests whether a customer name equals a given name and then it passes that as an argument to some f - you could just define a function that tests name equality and write something like this:

let nameEquals name (customer:Customer) = 
  customer.Name = name

let usage = source |> Query.Exists (nameEquals "Test")

More generally, I think it is always preferable to write code so that the caller can compose the pieces that are available to them (like Query.Exists, nameEquals etc.) rather than In a way that requires the caller to fill some holes of a particular required shape (e.g. implement a function with specified signature).

Upvotes: 3

Related Questions