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.

11
  • 1
    This is very broad. Do you have a specific concern you'd like to address? Oct 16, 2018 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. Oct 16, 2018 at 18:07
  • You might be interested in this article: cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf Oct 16, 2018 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? Oct 16, 2018 at 21:57
  • 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. Oct 16, 2018 at 22:13

1 Answer 1

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.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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