Thank you all again for responding!
Let me explain my train of thougth step by step because I’m starting to think that I have made some false assumptions.
There are languages that allow developers to define functions inside of the definition of a type. Such functions are called methods.
In class-based programming, methods are defined within a class, and objects are instances of a given class.
Method (computer programming) - Wikipedia
This is how a method is defined in Python (note that f
is defined inside of MyClass
):
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
Now, there is an obvious question: What are the advantages of defining a function inside a class?
The less important here is encapsulation. In many languages, functions defined within a class are allowed to access the private fields of the class whereas functions defined elsewhere are not allowed to do so.
This is correct C++ code:
class MyClass {
private:
int x = 0;
public:
int y = 0;
void inc() {
++x;
++y;
}
};
int main() {
MyClass mc;
mc.inc();
}
But the following doesn’t complie:
class MyClass {
private:
int x = 0;
public:
int y = 0;
};
void inc(MyClass &mc) {
++mc.x;
++mc.y;
}
int main() {
MyClass mc;
inc(mc);
}
error: ‘int MyClass::x’ is private within this context
More importantly, if a function f
is defined inside a class, then it lives inside the namespace of that class. See for instance: Python Scopes and Namespaces. Let’s return to the previous Python example and look at how f
is invoked:
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
mc = MyClass()
assert mc.f() == 'hello world'
assert MyClass.f(mc) == 'hello world'
#assert f(mc) == 'hello world'
Clearly, f
can not only be invoked using method syntax mc.f()
but also using function syntax MyClass.f(mc)
. In this case, however, f
needs to be prefixed with MyClass
because that is the namespace it lives in. Try uncommenting the last line and see what happens.
This mc.f()
syntax is important because it allows for dynamic dispatch. It, basically, says: Find f
in the namespace of the type mc
is an instance of and call f
with mc
. However, in Python, dynamic dispatch can also be achieved using the @singledispatch
annotation. Note how using @singledispatch
does away with method syntax.
Encapsulation aside, since polymorphism can be achieved by means other than defining functions inside the body of a type (at least in Python which is enough for proof of concept, I think), why do people define functions within classes?
For me, the answer seems to be convenience and habit. In particular, as the author of a package, what do I do in class-based object-oriented languages? I, mentally, assign functions to types and implement those functions within the bodies of classes. And this is what my question is about! In Julia, a language where I cannot (and also do not want to) implement fuctions inside types, I seem to have a lot of freedom that I wasn’t exposed to before. By the way, I’ve realized that I would have a similar problem in C, even though C features neither multiple dispatch nor modules… hence the title
Even within a single file, do I write this?
struct A{};
struct B{};
struct C{};
void f() {}
void g(struct A a) {}
void h(struct B b) {}
void i(struct C c) {}
void j(struct A a, struct B b) {}
void k(struct A a, struct C c) {}
void l(struct B b, struct C c) {}
void m(struct A a, struct B b, struct C c) {}
or this?
void f() {}
struct A{};
void g(struct A a) {}
struct B{};
void h(struct B b) {}
struct C{};
void i(struct C c) {}
void j(struct A a, struct B b) {}
void k(struct A a, struct C c) {}
void l(struct B b, struct C c) {}
void m(struct A a, struct B b, struct C c) {}
maybe this? (or something similar because of the missing forward declarations)
void f() {}
struct A{};
void g(struct A a) {}
void j(struct A a, struct B b) {}
void k(struct A a, struct C c) {}
void m(struct A a, struct B b, struct C c) {}
struct B{};
void h(struct B b) {}
void l(struct B b, struct C c) {}
struct C{};
void i(struct C c) {}
I think, I, unconnsciously, came to the conclusion that a more powerful module system would make this question a no-brainer. Where do I define a function f
that takes an A
, a B
and a C
? Well, in the 'A-B-C'
module… And how do I find functions that take exacly an A
, B
and C
? Well, I just bring A
, B
and C
into scope and let the module system bring in f
for me automatically.
But maybe it is imprtant to consider other factors and not to let the combination of types a function takes alone dictate its location…
One last thing I missed to acknowledge is that, as far as I see, functions defined within a class are the reason why it is easy to implement an online documentation like cppreference.com where I can search for a type and immediately see what actions can be performed on that type.