Generic programming

The lecture Generic Programming is interesting and useful, as is the whole set of slides until that point (I’ve not gotten further yet). It leaves me wanting more though. The notebook lays out how generic programming should NOT be handled (writing OO pseudo-classes, in Unlearning Object Oriented Programming), but it doesn’t say very clearly how is SHOULD be done. The code I’ve been writing very much follows your anti-pattern and while I see that that’s not ideal, I’m unsure about what to do instead. Please provide the appropriate pattern!

Thanks. Just to be clear here, are you saying that you would like to see the pattern for that precise MyModel example? For me, there would be no reason to get fancy. Just use a named tuple to hold the collections of parameters and create functions on those. i.e. replace all that boilerplate with something like

using Parameters
mymodel = @with_kw (a = 2.0, b = 3.0)  # enables default arguments
myalgorithm(m, x) = m.a + m.b + x

m = mymodel(a = 4.1)  # generate a tuple swapping out the `a` from the default
@show myalgorithm(m, 0.1)

Even the @with_kw isn’t necessary, and you could just use raw named tuples (ie. m = (a = 4.1, b = 3.0) directly).

Now, if things start to get fancy (e.g. you have variations on myalgorithm depending on fundamentally different types of parameters) then you could turn the named tuple into an (immutable if possible!) structure and dispatch on it. But since generic programming is all about the decoupling of datastructures from algorithms that operate on them, there is no need to get fancy until you have algorithms that need it.

Most of the material in the lecture, such as is trying to teach generic programming by examples since it doesn’t have boilerplate-style of OO.


Thanks for the swift reply!

Okay, so I think that you’re saying that the advice could be summarised as “no need for declaring a class-like type to hold a couple of parameters - just declare a tuple and your algorithm”. That all makes sense for this toy model, but our model may be much richer than than two parameters and a data point.

The point about named tuples for parameter storage and unpacking is well taken. Beyond that, you seem to be saying “don’t use types as data containers” as there’s no need to do that, not even a scoping benefit. “Write your algorithms and see what they require”, so we create types only if we need them, e.g. (i.e.?) for multiple dispatch. The flavour comes across a bit like test-driven development, where here it is “first write your algorithm, then create the data structure necessary to feed it.”

I may be over-interpreting what you’re really trying to say. In my case, I’ve written a first draft of an agent-based model that should eventually run with >1000 agents. The data is organised in types, even though they’re not necessary for multiple dispatch. The agents and environment type are initialised with init! functions. Then the model algorithms run, filling the agent.timeseries containers with data, timestep by timestep. Pretty much exactly like you said not to.

How should I have approached it instead? The anti-pattern is clear and insightful, but the pattern is a bit too simple to be instructive. When should we use types, when shouldn’t we? What’s wrong with init! functions? Yes, there are Distributions examples, but what principles / development approach does generic programming suggest for designing model components?

I see. That is one of the (rare) exceptions where having a mutable structure to hold data in a structure can make things more clear in Julia. An OO-style design in that case may be reasonable - although you can still use things like multiple-dispatch to help your code deal with different types of agent interactions.

The one thing I would say is not to worry about encapsulation and the other OO things, and just to have functions which take in the two agents and modify them as part of the interaction. Having a bunch of “getter” and “setter” functions for the individual fields may not be worth your trouble.

1 Like