2 ms·
The best argument is that the compiler doing it add complexity to the language. One might say adding complexity to the language is justified if it means removin
by adrusi 11y ago
The best argument is that the compiler doing it add complexity to the language. One might say adding complexity to the language is justified if it means removing complexity from the programmer's workload, but in many languages that attempt to solve this problem that's not how it works out.
Most solutions to this amount to allowing programmers to intercept field access via getters/setters. This way they can just use public fields and switch to getters/setters when they need to add special behavior without breaking the interface. Pseudocode:
// using methods; no special behavior
class Foo {
private bar
public get_bar() { return bar }
public set_bar(_bar) { bar = _bar }
}
// using methods; special behavior
class Foo {
private bar
public get_bar() { return decode(bar) }
public set_bar(_bar) { bar = encode(_bar) }
}
// using getters/setters; no special behavior
class Foo {
public bar
}
// using methods; special behavior
class Foo {
public get bar { return decode(bar) }
public set bar(_bar) { bar = encode(_bar) }
}
Rather than eliminate complexity from the programmer's workload, this just moves it elsewhere. Instead of tedium, which is something that can be alleviated by simple tooling (copy/paste, editor macros, templated code snippets), you get a constant concern over whether a field access will have unexpected behavior, which is something that can only be alleviated by complex tooling (semantic code analysis).
So ultimately this approach adds complexity to the language/compiler, adds complexity to the tooling, and has no effect on the programmer's workload.
I'm trying to think of a better approach and the best I can come up with right now is a macro system in the language (one that makes it very clear where a macro is being expanded, to avoid the same problem of "constant concern"). For example:
macro #generate_accessors(field) {
let paramname = unique_identifier()
return #[
public #[ identifier_from_string("get_" + field.as_string()) ]() { return #[ field ] }
public #[ identifier_from_string("set_" + field.as_string()) ](#[ paramname ]) { #[ field ] = #[ paramname ] }
]
}
// no special behavior
class Foo {
private bar
#generate_accessors(bar)
}
// special behavior
class Foo {
private bar
public get_bar() { return decode(bar) }
public set_bar(_bar) { bar = encode(_bar) }
}
But I haven't seen any language where this is the canonical approach.
- cballard 11y agoYou also have that problem in a language with non-synthesized setters, because there's no guarantee that they don't do anything - and people avoid permitting direct field access, so you'll be going through a setter with unknown behavior. That being said, if you're throwing off side-effects like mad in setters, short of prohibiting either mutability or side-effects, there's not much the language can do to prevent that.