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.