Functional Programming – How to Organize Chain of Functions Sharing Parameters

design-patternsfunctional programmingprogramming practices

When trying to follow a functional programming paradigm, I often find myself in a situation where I have a chain of functions that I would want to combine/compose somehow, but they all also take in a similar set of parameters. I've been trying to research best practices surrounding this kind of set up, because it's not immediately obvious how to combine them, and I don't want to end up with some esoteric solution. I'm also open to the possibility that there may be ways to avoid this sort of problem entirely.

To be more concrete, I noticed the trend working on two unrelated projects: interacting with a peculiar API and an agent based simulation.

  • For the API, I needed to make multiple calls to get the information I needed. Each call needing some information from the last, and each call needed the same general information (like the url, various object ids, and some option parameters).
  • For the agent based simulation, in each iteration I needed to make a series of updates to the population of agents as they interacted and evolved through various stages. Each stage needed the state of the previous stage's update, and each stage needed the same general information (like parameters dictating how the agents interact and change, and how to create new agents, etc)

These aren't the only two examples, just the most recent, and usually in the past I end up giving up and going back to OOP. It sort of makes sense, because if I have a chain of functions all with the same first parameter, that's sort of like how all object methods in Python take self as the first argument. But I'm sure the functional paradigm has another way of tackling this.

Hopefully that makes the problem clear. In sum, what are standard ways to handle these "ambient" parameters in a functional context?


Some paths I've tried that may further clarify the issue

One potential solution I thought about was a sort of composition "lift". For example, if I have two function f :: X -> (A -> B) and g :: X -> (B -> C), I could compose them into a function of type X -> (A -> C) with an implementation like lift_compose f g x = (f x) . (g x). However, I don't have the aesthetic knowledge to know if this is considered "clean", or the vocabulary to know if this is already called something standard.

It also becomes a problem for longer chains of functions functions, like if h :: X -> (C -> D) actually also needs a little information from what type A was in f. In that case the composition isn't as straightforward. Something I tried was having a series of data types that inherited fields from each other, and each function essentially added a field to the data and passed it on. For example:

type A:
    ambient_info : str
    more_info : str

type B (inherits from A)
   new_info : str

type C (inherits from B)
   newer_info : str

Then I could make the functions just f :: A -> B and g :: B -> C and so on for however many steps, and compose normally, keeping all the information throughout. However, this started to feel strange and non standard. Again, this is pretty hard to search because I just don't have the right words. Thanks for any help.


Also, I wrote these examples in a weird pseudo Haskell, but I'm looking for something as language agnostic as practical, since I don't actually get to work in Haskell very much. Python is my main fluency, but I see this as generally affecting me whatever language I'm writing in.

Best Answer

One alternative is, in case these "ambient" parameters are some sort of context, is to use Monads.

It really depends on your use-case whether that makes sense or not, so let's use an example of Transaction. Instead of passing in some database connection to each function, you could instead return something that needs a database connection. For example:

f :: a -> Transaction b

If you're specifically need to modify some things in each function, a State monad may be the thing you're looking for. Same concept, function will return something that will still need an initial state to "really" run.

Whether that is language agnostic and compose? Well, Haskell has the do notation, Scala has for-comprehensions. If all else fails, you can just use flatMap directly. I've been doing the latter in Java. Works up to a point, but admittedly gets pretty unreadable real fast.

Related Topic