2
$\begingroup$

What mechanisms could languages use to detect certain methods and handle them specially?

For example, in Python, special methods are named differently, using double underscores:

def __add__(self, other): ...

What other ways are there?

$\endgroup$
4
  • 1
    $\begingroup$ What do you mean by "implementing" here? Generally it's the user's job to implement these methods, since they exist to allow user-defined behaviour for other parts of the language. $\endgroup$
    – kaya3
    Jun 4, 2023 at 12:02
  • 2
    $\begingroup$ @kaya3 I mean implementing special methods. How language understand that method is special. For example this method should calls when you are adding it to other object. $\endgroup$
    – nchistov
    Jun 4, 2023 at 12:04
  • 1
    $\begingroup$ What's wrong, why down votes? $\endgroup$
    – nchistov
    Jun 5, 2023 at 9:17
  • $\begingroup$ What do you mean by 'special'? Built-in? Intrinsics? $\endgroup$
    – Pablo H
    Jul 18, 2023 at 12:27

4 Answers 4

6
$\begingroup$

Special things require special markers.

That's the only thing that really matters. As long as your language has some easy way to differentiate a special method from a regular one, and that marker shows up early enough in parsing to resolve potential ambiguities, that is sufficient.

But to answer the question as intended, here are a few examples:

  • Special name. Python does this.
  • Special keyword. C# does this with getters and setters.
  • Special syntax.
  • Or any combination of the above. C++ and Swift do this with operators.
$\endgroup$
3
  • 2
    $\begingroup$ Not sure about "early enough in parsing" ─ in Python's case, these methods don't have any special status until runtime when they're looked up on the class. They don't have to be distinguished at parse-time, and can even be implemented dynamically by generating methods at runtime with the correct names. $\endgroup$
    – kaya3
    Jun 4, 2023 at 18:31
  • $\begingroup$ Unlike C++, which models calls with an operator (operator()), Swift models calls with the method callAsFunction. I'm not quite sure why they made that decision, but given that operators are just static functions whose names aren't valid identifiers (e.g. static func +(lhs:rhs:)), trying to have a static func() might cause some parsing ambiguities. $\endgroup$
    – Bbrk24
    Jun 4, 2023 at 19:05
  • $\begingroup$ @kaya3, sorry for the poor wording. Bbrk24 got my intention right: I meant resolving ambiguities. I'll update my answer. $\endgroup$ Jun 4, 2023 at 19:23
3
$\begingroup$

I haven't seen this approach used anywhere, but in my language method (and attribute) names aren't limited to being strings. Essentially, any hashable and comparable object is allowed, with the syntax of foo.(bar) (whrere bar must be a valid expression resulting in an acceptable object). foo.bar is the equivalent of foo.("bar"), in particular. With this representation, special methods (and special attributes in general) can be named with dedicated key-objects, like foo.(std::add). This has a very nice benefit of simultaneously avoiding name collisions and allowing to introduce user-defined special attributes (whereas with Python's approach you would risk conflicting with either someone else's special attributes, or a newer language version).

$\endgroup$
1
  • 2
    $\begingroup$ JavaScript works like this. x.y is a shorthand for x["y"], and everything being a hash table means anything can be used as key. Then unique symbols can be used to make "special" members (eg. obj[Symbol.iterator]). $\endgroup$
    – Longinus
    Jun 6, 2023 at 21:15
3
$\begingroup$

There's also Haskell's approach, which is to make them (mostly) not special in the first place.

I'm going to use comparison as my example, because the addition operation is a little bit quirky in Haskell, and many feel that arithmetic needs to be cleaned up.

If you want to make a type comparable, you make it a member of the class Ord:

data Foo = Foo Int

instance Ord Foo where
    comparing (Foo x) (Foo y) = comparing x y

Operators such as <= are defined in terms of comparing, so now you can just compare two Foo values and it all works.

This relies on several interacting language and compiler features:

  • "Built in" operations like comparison are generic by default, and basic operations are handled by intrinsic functions. This is the only sense in which operations like addition and comparison are "special".
  • You do not need to declare a type to be a member of a class at the point where the type is declared; you can do it anywhere.
  • Aggressive specialisation and inlining optimisations to compile away any layers of abstraction (e.g. to get from <=, through comparing, to the underlying intrinsic function).

As a programmer, you would very much like a comparison on word-sized integers to compile down to one instruction. Intrinsic functions is one way to achieve this.

The second point is what makes the system ergonomic. In a typical Simula-style programming language, the only place where you can declare a type to be a member of a class is at the point where the type is declared.

A declaration like this:

class Foo extends Comparable
{
}

...means three distinct things.

  1. It declares a new data type Foo.
  2. It declares a new class Foo, and declares it to be a subclass of Comparable.
  3. It declares that the type Foo is a member of the class Foo.

Haskell keeps types and classes distinct, so you can declare a type to be a member of a class anywhere.

$\endgroup$
2
$\begingroup$

As a separate table

Lua's solution to this problem is Metatables which the base table references.

These Metatables are themselves tables containing all the special methods for whatever it is attached to (via a special method).

Additionally, Lua prefixes "Metamethods" with __, eg. __add, which prevents collisions when using Metatables as regular tables a well, a trick often used to implement classes.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .