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
    Commented 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$ Commented Jun 4, 2023 at 12:04
  • 1
    $\begingroup$ What's wrong, why down votes? $\endgroup$ Commented Jun 5, 2023 at 9:17
  • 1
    $\begingroup$ What do you mean by 'special'? Built-in? Intrinsics? $\endgroup$
    – Pablo H
    Commented Jul 18, 2023 at 12:27

5 Answers 5

8
$\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$
6
  • 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
    Commented 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
    Commented 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$ Commented Jun 4, 2023 at 19:23
  • $\begingroup$ In Python's case, __XXX__ is just a naming convention. The parser doesn't treat them specially. Rather, the parser treats the + operator specially and translates it into a call to the __add__() method (I'm simplifying). $\endgroup$
    – Barmar
    Commented Jun 15 at 21:37
  • $\begingroup$ Empty interfaces and/or attributes. For example, empty interfaces that instruct the compiler to work that class differently. This is a form of normal syntax and normal names (as these empty interfaces/atributes are declared normally in compiler source). $\endgroup$ Commented Jun 16 at 16:00
5
$\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$
4
$\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
  • 3
    $\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
    Commented Jun 6, 2023 at 21:15
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$
0
$\begingroup$

Various languages introduce special syntax to disambiguate their own special affordances (or even internalities) from similar regular names by the user in user-space, based on the notion that both use the same language construct. Like C/C++ "reserved" __ preprocessor directives present themselves being not conceptually different from other user-defined preprocessor directives, just these with __ are built-in or reserved for the compiler, etc. Could be the same with functions, classes, and so on. Sometimes, these special affordances are not formally reserved/defined in the language spec, and it's just a convention, also allowing implementers, tool-makers (and also users, if they want to or need to and accept the potential risks), etc. to add their things using this special convention. Maybe sometimes not all of these might be present or implemented in the same way in different environments.

Similar to abel1502's answer, there's the other school of design, which is not in favor of polluting/reducing the user-space of names with special/reserved names. Especially in languages which do not make heavy use of lots of special symbols with special meanings, and instead follow the idea that one has a small language "core" of a few reserved keyword names for a few constructs, and everything else is constructed by using/combining these constructs. So the other option is to just make these a regular function or constant or preprocessor directive that's part of the standard library or provided as such among the set of implementer/compiler functions - why should these "special" constructs need to be syntactically or naming-convention-wise be different from a math or file system access function? Or there might be just a function with a fixed name that takes a string as an argument, to request values/settings from the internal representation (as also is often already the case in many languages). Or if really unavoidable, have one special keyword or namespace to put these constructs under, so users remain free of using any names, don't run the risk of conflicting with reserved/special naming conventions, and there's less different styles of notation if encountering these things for the first time, wondering where these come from (is it from a third-party module, where is it defined, how do these just magically work, but are not among the language's keywords? How to look these up per search?).

New contributor
skreutzer is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
$\endgroup$

You must log in to answer this question.

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