0

Functional programming strongly suggests to separate data from behaviours (functions). However, I can't see the benefit of this for an algorithm's implementation intrinsically tied with particular settings data.

For example, suppose there's a trait LagrangeAlgorithmOOP with immutable data being the algorithmic settings, problem specification and dependencies on helpers. The trait's methods all use this data to find a problem solution. Their implementation is specific to the algorithm's type. Almost none of would make sense as a stand-alone function.

Specifically, suppose we refactor

 trait LagrangeAlgorithOOP {

       val settings: SettingsLagrange
       val problem: ConstrainedProblem
       val innerMinimiser: Minimiser
       val penaltiesFunction: ConstraintPenaltiesFunction

       def iteration( s: StateLagrange):  Either[String, StateLagrange]
       def lagrangeFunction( lams: Lambdas, pens: Penalties): AugmentedLagrangianFunction
       def estimateLambdas( pens: Penalties, las: Lambdas, cons: ConstraintValues): Option[Lambdas]
       def updatePenaltiesHistory( h: HistoryLagrange): HistoryLagrange
     }   

into this

case class LagrangeData(settings: SettingsLagrange,
                        problem: ConstrainedProblem,
                        innerMinimiser: Minimiser,
                        penaltiesFunction: ConstraintPenaltiesFunction)


trait LagrangeAlgorithmFUN {

  def iteration(d: LagrangeData, s: StateLagrange):  Either[String, StateLagrange]
  def lagrangeFunction(d: LagrangeData, lams: Lambdas, pens: Penalties): Lagrangian
  def estimateLambdas(d: LagrangeData, pens: Penalties, old: Lambdas, cons: ConstraintValues): Option[Lambdas]
  def updatePenaltiesHistory(d: LagrangeData,  h: HistoryLagrange): HistoryLagrange
}

The latter case introduces a data class and an extra parameter into each method. (Instead, I could use a Reader monad, which would, however, also require monad transformers.)

Questions:

  1. What is the best refactoring of this algorithm to an FP style?
  2. Could some half-way approach work better: eg, to leave some of the data fields in the trait?
  3. Is there much difference between the original and refactored versions?
  4. What is the benefit of refactoring to FP-style in this case?

Note: I agree with many points in the related post Why is "tight coupling between functions and data" bad?. Still I'm not sure how this applies to immutable settings data that is intrinsic to the functions implementing the algorithm.

schrödingcöder
  • 823
  • 8
  • 15
  • 1
    This is very broad. Do you have a *specific* concern you'd like to address? – Robert Harvey Oct 16 '18 at 17:33
  • @RobertHarvey I'm happy to modify the question to make it more specific, and welcome any advice on that. Originally I programmed a technical algorithm as an OOP class. Generally, I'm trying to switch to FP-style programming. However, for the specific algorithm, I'm stuck deciding whether to refactor it to FP, and even whether there's any real difference between the two approaches. – schrödingcöder Oct 16 '18 at 18:07
  • You might be interested in this article: https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf – Robert Harvey Oct 16 '18 at 20:31
  • @RobertHarvey It's a very interesting article indeed! To use an example from its section 4.1, what I can't understand is the essential difference between the "OOP" - style function `repeat (o.iteration(s))` and the "FP" - style function `repeat (iteration(d, s))`, where `o: LagrangeAlgorithm`, `s: State` and `d: LagrangeData`. If there is no essential difference, then both code snippets in the post are essentially equivalent, aren’t they? – schrödingcöder Oct 16 '18 at 21:57
  • Well, I haven't looked at that part of the article, but the OOP approach would typically be iterative (i.e. a loop). The functional approach (declarative) would most likely be a `map` function with a first-class function as a parameter. – Robert Harvey Oct 16 '18 at 22:03
  • Is there much difference between `map`ing the function `o.iteration(s)` and the function `iteration(d, s)`, especially if we consider currying? – schrödingcöder Oct 16 '18 at 22:10
  • Also, as noted by Uncle Bob, “Every functional program ever written is composed of a set of functions that operate on data. Every OO program ever written is composed of a set of functions that operate on data.” And the little “… difference between `f(o)`, `o.f()`, and `(f o)` … is just about the syntax of a function call.” https://blog.cleancoder.com/uncle-bob/2014/11/24/FPvsOO.html. So I am a bit confused. – schrödingcöder Oct 16 '18 at 22:11
  • 2
    I consider things like immutability, declarative programming style, recursion, lambda expressions, and functional composition more important than the underlying nuances of loops and data structures. Ultimately, every program resolves to a series of machine instructions and a collection of mutable data, regardless of the overarching programming paradigms in use. – Robert Harvey Oct 16 '18 at 22:13
  • Probably I'm too confused, but aren't things like immutability, recursion and functional composition equally apply to either version in the OP? – schrödingcöder Oct 16 '18 at 22:16
  • Instinctively, I feel like you're looking too closely at the trees, when you should be looking at the whole forest. Look closely enough, and this is all the same soup anyway. It is only when you get high-level that the material differences emerge. – Robert Harvey Oct 16 '18 at 22:20
  • https://stackoverflow.com/a/1784702 – Robert Harvey Oct 16 '18 at 22:42

1 Answers1

3

If you specify the data of the type as immutable, then there is little difference between your two examples. You say, "The latter case introduces a data class and an extra parameter into each method." But in actuality it doesn't. An OO method implicitly passes the parameter you call an "extra parameter" so it isn't truly extra, just moved from before the function name to after.

Keep in mind that without inheritance the difference between foo.bar() and bar(foo) is purely a syntactic one. When you add the fact that modern OOP practice strongly favors using inheritance only for interfaces (which corresponds closely to FP type classes) you will see that there is a lot of convergence between best practices in both OOP and FP. Given this, we now see that in the "best practices" cases, foo.bar() and bar(foo) (or in an ML language (bar foo)) is merely a syntactic difference even taking inheritance/type classes into account.

Once you allow mutability, then an OO class effectively becomes a specialized IO monad and the OO programmer ends up having to work with lots of these specialized IO monads to get anything done. Here too the current trend is to reduce the number of classes that are explicitly mutable which has lead to an explosion of languages that are "largely" but not purely functional, and with syntax more in the OO style which is familiar to the typical programmer.

So in answer to your questions, the difference is largely syntax but IMO the OO style of obj.func() allows for a more natural chaining of function calls. Instead of c(b(a)), I can do a.b().c() which makes the order of operations more clear because it reads in a left to right order.

The counter argument is that the OO call style necessarily gives precedence to one particular parameter of the function over the others which is true and a good reason to avoid the style if there is no natural preference inferred by the algorithm. For example when comparing two objects for equality, a.equals(b) just feels silly compared to equals(a, b) because there is no reason to preference a over b in that context. All OO languages that I know of, with the exception of Java, allow for "free" functions that follow the more functional style of not giving precedence to one particular parameter. It's even doable in Java through the use of so-called "static" functions that exist inside a class, but don't actually have a implicit this or self parameter.

Daniel T.
  • 3,013
  • 19
  • 24