mrkeen
a day ago
The benefit of values over classes is that they do not hide machinery.
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);