Sometimes, the elegant implementation is just a function

edA‑qa mort‑ora‑y on December 15, 2018

““Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.” - John Carmack This ... [Read Full]
markdown guide
 

One of my favourite videos on YouTube is the talk Stop Writing Classes by Jack Diederich. He shows a clear example where they go from a full library down to a single function that still does everything the library did.

I am very much a fan of the building upwards method of constructing programs where you start with just the code that you then lift into procedures and then into classes or modules if the need arises. Sometimes the best solution is a class, but it's really hard to know that from the outset and if you start out by writing the classes you think you'll need you will end up with classes like the first one Jack shows in that video (Greeter) that are what he calls obfuscated function calls.

This is why I think multi-paradigm programming languages are the bees-knees and any language that strictly forces a certain paradigm on you are not that fun to use. This goes both for OOP languages like C# or Java and functional languages like Haskell. Their strictness lead to hairy corner-cases where you can't write the best possible code because the language forbids it. Sometimes a global function (Python's len() comes to mind) is the ultimate solution, but if your language can't do it you do something like a Singleton which really is just a hidden global variable in most cases.

Tell someone you're using global variables and they'll jump down your throat telling you you're a shitty coder. Tell them you're using Singletons and they'll applaud you for being a diligent practitioner of the Patterns as proscribed by the holy Gang of Four...

 

I agree that multi-paradigm languages are definitely the preferred approach. I think it's essential that people can write in all paradigms and learn to use the right one.

To followup from your other comment here as well, I think it's helpful when a language gives multiple ways to define operations on data. Either as standalone functions, or as member functions of a type, preferably allowing both. Some functions just work better as globals, some work better as member functions.

 

I agree completely. Languages should enable the programmer, not hamper them. This is one of the reasons why my new favourite language is Nim. It lets you do pretty much whatever you want and with its Unified Function Call Syntax you can mix freely between OOP-style code and functional-style code without changing your coding style.

Example:

# These two are equivalent and will result in 
# some_procedure being called with arg1 and arg2
# as the first and second argument respectively
# 
# This works for independent procedures and class
# methods alike.
some_procedure(arg1, arg2)
arg1.some_procedure(arg2)
 

I always try to use the simplest abstraction that solves the problem. Oftentimes that's just a function.

Functions are also way more reusable than classes. Classes by design tie state to methods, oftentimes grouped with state I don't care about or have. If instead they used functions I could just reuse the functions I care about with the state I have.

 

It's not true to say the functions are more reusable than classes. These are different entities serving different purposes. A function is a set of instructions, a class is a type of data.

Similarly, we don't say that an integer is more reusable than a square root function.

Classes represent types, they could be simple types, like a complex number or vector, which represent data, or they can be a service type that encapsulate global state, bind to the OS. In my language I was separating these two concepts to be more pure.

 

If I am allowed to be a bit pedantic, a class is not just a type. A class is a bundling of code/instructions and data. If all you have is some data and no code, then it should be something like a struct or tuple or maybe even a container type such as list or hash depending on your language and use-case.

I can also be pedantic and say it is a type. :)

Any type is a reference to a value and operations on that value. A class is one way of expressing a type. Types are defined by the operations that can be performed on them, not just their raw value -- if one can even define a value absent of any operations on it.

The part I dislike is that classes can represent a bunch of things that don't work like value types as well. I wish these were distinct entities. I called them "service" in the language I was working on.

 

I find it unfortunate that some languages, ahem Java and C#, don't even offer global functions.

This isn't completely accurate. C# has static classes and static functions that can achieve the same results.

You could even create a static class called Global and then call functions like:

Global.MyFunction()

This would act the same as a global function, it would just be prefixed with a namespace or type.

 

I understand this, but I don't understand that reason why it was done. Why not allow functions at the namespace scope instead?

By overloading the functionality of a class it needlessly confuses its purpose.

 

C# was created at the peak of OOP mania. Other options weren't as popular. It's one of the reasons why I transitioned from C# to JavaScript.

 

That is the nice thing about multiparadigm languages like e.g. Python. You could start with a simple function. If you realize that there is need for another function, you group this functions together in a module. If you realize, this module contains data and functions which could go well together, you could start writing a class.

 

Allowing global functions seems like you're just asking for namespace pollution. I'm not a fan of the idea.

 

I'm not adverse to namespaces, but I don't think classes should serve that role. A class with a bunch of static methods just isn't a class. It confuses the purpose of the features.

 

Could you elaborate? In Java, at least, a common pattern is to make "Utils" classes, which are usually singleton or non-instantiable classes that just contain methods for working within a particular domain. Like a StringUtils class that provides methods for working with Strings and so on. If you want to see if you can perform a particular operation on a String, you just look for that source code.

If what you're saying is that you shouldn't just have one class that contains all of your static methods, then I totally agree. Grouping by functionality is important.

First, let me agree that in Java, and C#, a class with static methods is the correct approach.

What I lament is that the language forces you to do this. They have packages/namespaces which should be used for this purpose. A class is meant to represent an instantiable type, if you have only statics in it it violates this definition. That is, I'm complaining the languages are creating confusion as to what a "class" is.

Ah I see. The Utils pattern really does fly in the face of OOP, doesn't it?

I understand that you see this as a problem, conceptually. In practice in Java, you could statically import the class and use the methods like functions. In that case, the class would ask more like a namespace than a „true“ OOP class.

 
 

I think the industry, having over-worshipped the object-oriented paradigm, is now veering in the opposite direction. While functional programming is a simpler idea, the complexity lies in the problem domain, and a complex enough implementation will make your head spin, classes or no classes. In contrast, the effort required to get comfortable with either classes or pure functions is tiny.

ahem Java and C#, don't even offer global functions.

I think global functions don't add any objective value. Remember that classes do not always have to represent a type; they can also be a namespace, a module of sorts, contained a few useful functions. If all global functions were wrapped in a single module, at least I'd know where to look to find them all!

Is a plain function sometimes the right thing to do?

Definitely! Design patterns, fancy databases, clusters, classes . . . often you're better off starting with the easiest, simplest approach. 😇

 

I think the industry, having over-worshipped the object-oriented paradigm, is now veering in the opposite direction.

I have also the same feeling. And I feel it is because of the type of problems that the industry is facing nowadays.

I strongly support the OOP paradigm not only because it aligns to the way the complexity is organized in the real world, also because it provides solid mechanisms for extensibility.

I did program games 20 years ago with old programming languages that didn't support OOP. It was very hard to make abstractions. Since I found the OOP paradigm it was easier to build the behaviour of the game components and reuse it in others. Many enemies shared the way to move, or shoot, etc.

Of course there were problems that, by its nature, didn't need such mechanisms. But at the end they also feed into a class method.

So what can I do in a function that can not be done in a class method ?

I don't see the OOP as a replacement of the old procedural paradigm. I see it as an extension, the same way that the theory of relativity is an extension to the theory of mechanics.

 

Here’s a mixture of facts and opinions :p

Let’s take simplicity of expression and implementation as our goal. Consider that functions are considerably simpler than the proposed alternatives, which add layers upon the primitive idea of a function. It is clearly true that a function is the better choice when it provides equivalent functionality to the more complex structure. The more complex solution adds useless information and therefore obfuscates the purpose of the software.

Another question is whether to use the mathematical definition of a function or consider them to be equivalent to procedures. I prefer the mathematical definition because it is easy to reason about, easy to test, and therefore harder to get wrong.

The idea of elegance can be misleading, suggesting intricacy as a virtue. Elegance really comes from simplicity and compactness, which actually minimize the level of intricacy. The effect of beauty comes from the stucture of the design clearly expressing the problem that it solves.

 

Yes, in programming terms we've ended up with the name function and pure function to refer to functions and procedures. I agree that pure functions are an ideal solution, when they apply. I wrote about it in my article on functional programming

code of conduct - report abuse