DEV Community

edA‑qa mort‑ora‑y
edA‑qa mort‑ora‑y

Posted on

Sometimes, the elegant implementation is just a function

““Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.” - John Carmack

This week we discuss this sage advice from John Carmack. We both agree on it, at least in principle, but we still discuss its relevance. Feel free to argue with us, we'll be happy to prove you wrong. :P

play pause Edaqa & Stephane Podcast

This perhaps relates to our last episode about lasagna code. A lot of code is simply overly complex -- there's too much overhead and architecture to accomplish something simple.

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

What do you think? Is a plain function sometimes the right thing to do?

Top comments (22)

Collapse
 
kungtotte profile image
Thomas Landin

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...

Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

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.

Collapse
 
kungtotte profile image
Thomas Landin • Edited

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)
Collapse
 
stevbov profile image
Steven

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.

Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

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.

Collapse
 
kungtotte profile image
Thomas Landin

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.

Thread Thread
 
mortoray profile image
edA‑qa mort‑ora‑y

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.

Collapse
 
thomasjunkos profile image
Thomas Junkツ

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.

Collapse
 
joelnet profile image
JavaScript Joel

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.

Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

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.

Collapse
 
joelnet profile image
JavaScript Joel

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.

Collapse
 
awwsmm profile image
Andrew (he/him)

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

Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

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.

Collapse
 
awwsmm profile image
Andrew (he/him)

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.

Thread Thread
 
mortoray profile image
edA‑qa mort‑ora‑y

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.

Thread Thread
 
awwsmm profile image
Andrew (he/him)

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

Thread Thread
 
mortoray profile image
edA‑qa mort‑ora‑y

Yes.

Thread Thread
 
bertilmuth profile image
Bertil Muth

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.

Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

Also, I wrote about globals being bad once. I got into a bit of detail. ;)

Collapse
 
swfisher profile image
Sam Fisher • Edited

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.

Collapse
 
mortoray profile image
edA‑qa mort‑ora‑y

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

Collapse
 
yucer profile image
yucer

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.