Extreme narrowing parameters vs parameters in common

Hi All.

Starter conversation from one that arose at work.

I have a tendency to want to narrow parameters as much as possible, having picked this concept up from talks by Richard Feldman such as I think his 2017 Elm Europe talk or this one Learn Narrowing Types – Advanced Elm . Basically if a function only takes in what it deals with it is easier to reason about and debug.

However, what I see in a real life large codebase is the desire to group functions by entity - a bit like in OO modules and in Domain Driven Design, and use of the key model of the module as a consistent parameter.

An example may be a person and a set of functions related to that person’s data (edit - and subdata) - taking person as the only or main parameter.

In my attempt to moderate my first instinct to just pass the minimum, I did propose a sub-entity of a person that I thought was also big enough to “pass around” too.

However that faced the argument that since the sub-entity only exists on the person then abstraction information is lost in breaking it out. Is this “abstraction connection” something of real worth though?

So …
Is it a question of balance and nuance identifying entities that make sense?
Is it best to have entity-based functions with parameters in common?
If so what are some rules of thumb about when and where?
Or is it a valid path of always go to the extreme and narrow functions to only the parameters they need regardless?

I’m not sure I follow your example, but in DDD style, you should use extensible records. Let’s say Person is a record with bunch of fields. Your setName function should accept any record with name field and return it with changed name.

That way you have minimal contract on your function but you keep it open to changes.

Of course it is a bit tedious in Elm, due to it’s type system, but it is really readable. And if you ever change Person record, compiler would catch any incompatible changes.

@solificati
Ok, yes I see your point. Let’s look at getters to better illustrate what I’m trying to get to:

Say for example you had a list of a person’s socks in Person.socks and you want to get a particular sock. So would you always come back to a function getSock person sockId because you have a whole lot of other such functions in your Person module that you want the same person parameter on, or would you tend to given the list of socks off a person use a function getSock sockId since that keeps the parameters narrower.

Because the opposing argument is something like if you say had six functions that get different things from a person you should pass the whole person (whether that is using extensible records or not) to each of them so people can see that the functions are all related. And after all, socks only exist on people here.

As always, it depends :slight_smile:

If getSock accepts sockId, that means that callers have some knowledge about socks, and SockMap could be named module and exported. Then caller would firstly get socks map and on this result use sockId. On the other hand I can imagine you would have getFavouriteSock on Person and it would accept Person object.

It all narrows to question “what aggregates are technical and what are in the domain”.
Is socksMap an implementation detail? Then expose getSock with (partial) Person argument.
Is socksMap something in domain language that users are interested in? Expose getSocks and whole module SocksMap.

Sometimes it can in between. In my application I have Client record with Address (non empty list of those) and I provide functions that retrieve address from Client and then you can do a bunch of stuff with any address. But I also have getSendingLabel that accepts Client. Some of my modules does not care about Address, it cares about transformation from client to label.

Yep, so it starts to become Domain Driven and weighing up whether something deserves its own entity status, or dependent on other aspects of context.

The address list is a good example. Say for example a list of addresses only exists on a client. Should all functions that deal with a list of addresses take the client as the parameter and never a list of addresses?

Or is the concept of a list of addresses so strong that it is an entity itself with its own set of functions that take a list of addresses as the parameter and do things like sort, takeFirst, find business, getPrintLabel etc.

So when things just “depends” then we make adhoc decisions depending how it appears on the day.

I’m looking to see if there are some strong rules of thumb around this, or if it’s better to just be extreme and ignore the domain side in respect to parameter consistency in modules, go for the most narrow signatures, seeking the benefits that Feldman talked about in videos.

1 Like

But accepting Person as argument can still get benefits of narrow types. Just require person to be only {a | address: Person.Address }. You can easily create unit tests, you can easily extend Person type elsewhere, or if you need to add additional field to function (maybe address depends on age?) you can do that with full type checking.

That’s never good idea because it leads to proliferation of types. It is partially solved with extensible records (you can match different types structurally) but it easily breeds complexity.

I think extensible records are a way to go. There’s much boilerplate in elm, but language features are there. Nice ideas are in Rich Hickey’s talk: Maybe Not where he separates shape of types and what is required (selection). This is in the context of Clojure, but similar ideas float around in Haskell with Classy Lenses

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.