If you x.getY(), is that a member access, a computed value, or a network fetch? Will it lazy-instantiate a log4j singleton and start spamming my IDE with red text? I refuse the OO dogma that x should be a black box, and I as the programmer on the outside should not concern myself with its implementation details.
Onto TFA's parser example:
typedef struct Parser {
// fields
} Parser;
Statement parser_parse_statement(Parser *p);
Expression parser_parse_expression(Parser *p);
void parser_expect(Parser *p, const char *message);
void parser_next(Parser *p);
TokenKind parser_current(const Parser *p);
> It sounds like “data should not have ‘behavior’” is being violated here, no? If not, what’s the difference between this module and a class?Correct. You can write OO in any language, and this parser hides machinery and is acting like a class, not a value. Look at those voids - those methods either do nothing, or they modify the insides of the parser. I certainly can't combine them like I could with values, e.g.:
Parser *p = choice (parse_statement, parse_expression);
The above cannot work - whose internal machinery should choice adopt?It is much easier to parse without hidden machinery. Combine them like values, e.g.:
Parser parser_block = many (parse_statement);
Parser parser_sum = seperateWith (parser_plus, parser_product)
Parser parser_product = separateWith (parser_times, parser_value)
Parser parser_value = choice (parser_literal, parser_variable)
No voids, no internal machinery, any parser_literal is the exact same thing as any other parser_literal.Then just unhide the internal cursor or success/failure state (or whatever other machinery is hidden in there) and pass them in as values:
(result, remainder_string) = run_parser (parser_block, input_string);An 'aggr' is equivalent to C 'struct' combined with 'typedef', and an 'adt' is an 'aggr' having methods.
[1] https://en.wikipedia.org/wiki/Alef_(programming_language)
Why should the first argument be so special? And how do you decide which struct should get method if you have a function that operates on two different types?
In my opinion: there is a better argument for making new languages not have methods, or more accurately member functions (as what the author describes).
Consider the following situation: you are user of a library that declares a type called SomeType which has "methods" (member functions) in it. You want to add more "methods" to this type.
Now, there is a problem regarding consisteny w.r.t syntax, your new "methods" now have to be called via `foo(&bar)` instead of `bar.foo()`. You as a user of the library and language have to choice to make (regarding code style):
1. Accept this difference in syntax. Maybe you like this style difference, because now you can clearly see what procedures are declared in your codebase vs. the library's codebase, or:
2. Use freeform functions everywhere. Well actually, you can't do this without a refactor of the library (or with additional language features), i.e. you will need to fork the library and rewrite SomeType and the associated member functions in this freeform function/procedure style.
From a language designer's perspective, you can choose to solve the problem by either (a) forcing the declaration of procedures to be consistent or (b) introducing language features to make the calling code consistent. Odin obviously chose (a), but languages like Swift and C# chose (b) - whereas languages such as Nim chose both (a) & (b).
For (b), here's some possible features you could add:
* Extension methods (Swift, C#). This let's user declared "methods" feel like like "proper methods" of the class/struct, or
* UFCS (Nim, D). Declare everything as a freeform procedure/function but enable the syntax `arr.push(10)` when the procedure is declared as `proc push(arr: var Array, x: int)`
From this, you can see why languages such as Odin chose to go with option (a). It's simpler.