Read my previous comment for the bug that would happen if what us being used in Linux were actually used for dynamic dispatch when implementing inheritance, and it should be clear this is something similar, but different.
4,481 karma · joined August 18, 2014
Read my previous comment for the bug that would happen if what us being used in Linux were actually used for dynamic dispatch when implementing inheritance, and it should be clear this is something similar, but different.
There are some languages in which I am extremely proficient. My best language is C, which is my favorite and I have used most features of every version of C from C89 to C11. My second best is probably either C++ or POSIX shell (although I have moments where I forget certain syntax and need to look it up, especially in POSIX shell for variants on variable substitution, e.g. ${VAR%%foobar}). I have used most features of C++98 and some from newer versions. My experiences with C++ have soured me on it, so I now try to avoid C++ whenever I can in favor of C and Python.
My first language was actually PHP 4.2.y, and I was fairly proficient in it, having spent a long time learning it while simultaneously writing my own code for a website as a teenager. However, I never once touched the portions describing objects/classes, namespaces or exceptions. Someone else at work writes Modern PHP code using Symfony and I have taken a peek at it. It looks very different from the PHP I knew because it uses the features I had avoided learning (and probably some new language features too), although I can sort of read it thanks to having learned those concepts in other languages.
I used SML/NJ and Java in college. Years after college, I modified an open source Android TV application written in Java to add some things I wanted, although honestly, beyond that I have not really touched either language. Give me an arbitrary application written in either to improve and I would have some difficulty, although I will probably be able to do it after filling the gaps in my understanding (and doing plenty of head banging if it is a large program).
I have used JavaScript for a few recent projects via electron/nodejs and I have done several small things in Python over the past several years. Each time, I only worked with the subset that I need. I am far from a master of either language that can understand arbitrary code written in them, but I am able to manage as far as my specific needs are concerned.
I could continue listing my experiences doing things in languages (like the time in college that I wrote some basic programs in FORTRAN 90 to try to learn it), but they really are not that interesting. It was often a project here or a small application there, and as I readily admitted, I only used a subset of most of the languages. For programming, a subset of commonly used bits is often all you need.
There is a saying “If Your Only Tool Is a Hammer Then Every Problem Looks Like a Nail”. That is precisely what is happening here with the insistence to call what appears to be all structures of function pointers vtables. A vtable is something that follows a fairly well defined pattern for implementing inheritance. Not all things containing function pointers are vtables.
Every few years, someone tells me I should learn another language, and in recent years, there just is no desire in my mind to want to learn yet another language that is merely another way of doing something that I already can do elsewhere and the only way I will is if I am forced (that is how I used Go).
That said, I do see what you are saying. C++ for example has an “support every paradigm” philosophy, so whenever someone who has learned C++ encounters a language using a paradigm that C++ assimilated, there is a huge temptation to try to view it through the lens of C++. I also can see the other side too: “C++ took me forever to learn. Why go through that again when I can use C++ as a shortcut to understand something else?”. C++ is essentially the Borg of programming languages.
That said, similar macro magic is used in C generic data structures and it works very well.
https://history-computer.com/software/simula-guide/
Thus, while I had thought Lisp had ADT concepts before the first OOL existed, now I am not sure. My remark that they originated in Lisp had been said with the intention that I was talking about the first language to have it. The idea that the concept had been described outside of an actual language is tangential to what I had intended to say, which is news to me. Thanks for the link.
It is from experience with object oriented languages (mainly C++ and Java). Technically, you can do everything manually, but that involves shoehorning things into the OO paradigm that do not naturally fit, like the article author did when he claimed struct file_operations was a vtable when it has ->check_flags(), which would be equivalent to a static member function in C++. That is never in a vtable.
If Al Viro were trying to restrict himself to object oriented programming, he would need to remove function pointers to what are effectively the equivalent of static member functions in C++ to turn it into a proper vtable, and handle accesses to that function through the “class”, rather than the “object”.
Of course, since he is not doing object oriented programming, placing pointers to what would be virtual member functions and static member functions into the same structure is fine. There will never be a use case where you want to inherit from a filesystem implementation’s struct file_operations, so there is no need for the decoupling that object oriented programming forces.
> I've always thought OOP as a way of organizing your data and your code, sometimes supported by language-specific constructs, but not necessarily.
It certainly can be, but it is not the only way.
> Can you organize your data into lists, trees, and hashmaps even if your language does not have those as native types?
This is an odd question. First, exactly what is a native type? If you mean primitive types, then yes. Even C++ does that. If you mean standard library compound types, again, yes. The C++ STL started as a third party library at SGI before becoming part of the C++ standard. If you mean types that you can define, then probably not without a bunch of pain, as then we are going back to the dark days of manually remembering offsets as people had to do in assembly language, although it is technically possible to do in both C and C++.
What you are asking seems to be exactly what data abstraction is, which involves making an interface that separates use and implementation, allowing different data structures to be used to organize data using the same interface. As per Wikipedia:
> For example, one could define an abstract data type called lookup table which uniquely associates keys with values, and in which values may be retrieved by specifying their corresponding keys. Such a lookup table may be implemented in various ways: as a hash table, a binary search tree, or even a simple linear list of (key:value) pairs. As far as client code is concerned, the abstract properties of the type are the same in each case.
https://en.wikipedia.org/wiki/Abstraction_(computer_science)...
Getting back to doing data structures without object oriented programming, this is often done in C using a structure definition and the CPP (C PreProcessor) via intrusive data structures. Those break encapsulation, but are great for performance since they can coalesce memory allocations and reduce pointer indirections for objects indexed by multiple structures. They also are extremely beneficial for debugging, since you can see all data structures indexing the object. Here are some of the more common examples:
https://github.com/openbsd/src/blob/master/sys/sys/queue.h
https://github.com/openbsd/src/blob/master/sys/sys/tree.h
sys/queue.h is actually part of the POSIX standard, while sys/tree.h never achieved standardization. You will find a number of libraries that implement trees like libuutil on Solaris/Illumos, glib on GNU, sys/tree.h on BSD, and others. The implementations are portable to other platforms, so you can pick the one you want and use it.
As for “hash maps” or hash tables, those tend to be more purpose built in practice to fit the data from what I have seen. However, generic implementations exist:
https://stackoverflow.com/questions/6118539/why-are-there-no...
That said, anyone using hash tables at scale should pay very close attention to how their hash function distributes keys to ensure it is as close to uniformly random as possible, or you are going to have a bad time. Most other applications would be fine using binary search trees. It probably is not a good idea to use hash tables with user controlled keys from a security perspective, since then a guy named Bob can pick keys that cause collisions to slow everything down in a DoS attack. An upgrade from binary search trees that does not risk issues from hash function collisions would be B-trees.
By the way, B-trees are containers and cannot function as intrusive data structures, so you give up some convenience when debugging if you use B-Trees.
You can implement OOP in C if you do vtables for inheritance hierarchies manually, among other things, but that is different than what Linux does.
https://www.kernel.org/doc/html/latest/filesystems/vfs.html
As I said previously, it is helpful to remember that this is not object oriented programming and not try to shoehorn this into the paradigm of object oriented programming. Calling this a vtable is wrong.
If you think of the differences as being OOP implies contracts with the compiler and data abstraction does not (beyond a simple structure saying where the members are in memory), it becomes easier to see the two as different things.
The entire point of OOP is to make contracts with the compiler that forcibly tie certain things together that are not tied together with data abstraction. Member functions are subject to inheritance and polymorphism. Member function pointers are not. Changing the type of your class will never magically change the contents of a member function pointer, but it will change the constants of a non-virtual member function. A member function will have a this pointer to refer to the class. A member function pointer does not unless you explicitly add one (named something other than this in C++).
For full disclosure, I have never verified that leaving ->read() unimplemented when ->read_iter() is implemented is safe, but I have seen enough examples of code that I strongly suspect it is and if it is not, it is probably a bug.
error = old_dir->i_op->rename(rd->new_mnt_idmap, old_dir, old_dentry, new_dir, new_dentry, flags);
https://github.com/torvalds/linux/blob/master/fs/namei.c#L51...
That is a close match for the first example, with additional arguments.
It is helpful to remember that this is not object oriented programming and not try to shoehorn this into the paradigm of object oriented programming. This is data abstraction, which has similarities (and inspired OOP), but is subtly different. Data abstraction does not automatically imply any sort of inheritance. Thus you cannot treat things as necessarily having a subclass and superclass. If you must think of it in OOP terms, imagine that your superclass is an abstract class, with no implemented members, except you can instantiate a child class that is also abstract, and you will never do any inheritance on the so called child class.
Now, it is possible to implement things in such a way where they actually do have something that resembles a subclass and a superclass. This is often done in filesystem inode structures. The filesystem will have its own specialized inode structure where the generic VFS inode structure is the first member and thus you can cast safely from the generic inode structure to the specialized one. There is no need to cast in the other direction since you can access all of the generic inode structure’s members. This trick is useful when the VFS calls us via inode operations. We know that the inode pointer is really a pointer to our specialized inode structure, so we can safely cast to it to access the specialized fields. This is essentially `superclass->op->start(object)`, which was the second example.
Data abstraction is a really powerful technique and honestly, object oriented programming rarely does anything that makes me want it over data abstraction. The only thing that I have seen object oriented programming do better in practice than data abstraction is marketing. The second example is similar to C++’s curiously recurring template pattern, which adds boilerplate and fighting with the compiler with absurdly long error messages due to absurdly verbose types to achieve a result that often at best is the same thing. On top of those headaches, all of the language complexity makes the compile times slow. Only marketing could convince someone that the C++ OOP way is better.