When you want a value from a module/object/function/whatever, whether or not it's fetched from a location in memory is an implementation detail. Java and co provide a short syntax for exposing that implementation detail. Python doesn't: o.x does not necessarily mean accessing an x slot, and you aren't locking yourself into any implementation by exposing that interface as the way to get that value. It's more complicated than Java or whatever, here, but it hides that complexity behind a nice syntax that encourages you to do the right thing.
Some languages provide short syntax for something you shouldn't do and make you write things by hand that could be easily generated in the common case. Reducing coupling is still a good idea.
That is the important thing sometimes. At other times the important thing is to provide a flexible, fluent public interface that can be used in ways you didn't intend.
It really depends on what you're building and what properties of a codebase are most valuable to you. Encapsulation always comes at a cost. The current swing back towards strong typing and "bondage and discipline" languages tends to forget this in favour of it's benefits.
That scares me. How do you maintain and extend software used in ways you didn't intend?
Quality assurance should be challenging.
It scares you because you're making some assumptions:
1. You assume that I'm writing software that I expect to use for a long period of time.
2. Even if I plan to use my software for an extended period of time, you're assuming that I want future updates from you.
Let me give you an example of my present experience where neither of these things are true. I'm writing some code to create visual effects based on an API provided by a 3rd party. Potentially - once I render the effects (or for interactive applications - once I create a build) my software has done it's job. Even if I want to archive the code for future reuse - I can pin it to a specific version of the API. I don't care if future changes cause breakage.
And going even further - if neither of these conditions apply the worst that happens is that I have to update my code. That's a much less onerous outcome than "I couldn't do what I wanted in the first place because the API had the smallest possible surface area".
I'll happily trade future breakage in return for power and flexibility right now.
You get into the same issue with eg making your interface be that you return an array, instead of a higher-level sequence abstraction like "something that responds to #each". By keeping a minimal interface that clearly expresses your intent, you can easily hook into modules specialised on providing functionality around that intent, and get power and flexibility right now in a way that doesn't hamstring you later. Other code can use that broad interface with your minimal implementation. Think about what you actually mean by the code you write, and try to be aware when you write code that says more than that.
I think it's interesting that you associate that interface-conscious viewpoint with bondage and discipline languages. I mostly think of it in terms of Lisp and Python and languages like that where interfaces are mostly conceptual and access control is mostly conventional. If anything, I think stricter type systems let you be more lax with exposing implementations. In a highly dynamic language, you don't have that guard rail protecting you from changing implementations falling out of sync with interfaces they used to provide, so writing good interfaces and being aware of what implementation details you're exposing becomes even more crucial to writing maintainable code, even if you don't have clients you care about breaking.
Of course all this stuff goes out the window if you're planning to ditch the codebase in a week.
getArea() {
return this.width * this.height;
}
getIcon() {
// if icon hasn't been loaded, load it
return this.icon;
}Most people don't have a problem with getters and setters, they have a problem with writing pure boilerplate by hand. Languages like Python and Lisp save you from the boilerplate and don't provide a nicer syntax for the implementation-exposing way, so people don't generally complain about getters and setters in those languages, only in Java and C++ and things.
I specifically object to the useless abstraction, not the boilerplate (boilerplate is cheap).
I don't view it as a useless abstraction, because I view it as the natural way of things. I view specifying that your get/set implementation is and always will be implemented as slot access to be uselessly sharing implementation details that does nothing but freeze your current implementation strategy.
I think a better question is when that abstraction gets in your way. When does it bother you that nullary functions aren't reading memory locations? Why do you feel that's an essential thing to specify in your public interface, as a default? There's nothing stopping you from writing code in Python and mentally modelling o.x as slot access, because it follows the interface you want from it.
If you only care because it's something extra you have to do, then that's what I meant by boilerplate. I think it's a misfeature of Java's that it presents a model where that's something extra you have to do.
I understand your point, but I think you misunderstand what "abstraction" means. "abstraction" doesn't mean "function" (although functions are frequently used to build abstractions), and if you have "dynamic properties" (or whatever you'd like to call them) a la Python, then you're still abstracting. My point is that abstracting over property access (regardless of property-vs-function syntax) is not useful, or rather, I'm skeptical that it's useful.
> I think a better question is when that abstraction gets in your way. When does it bother you that nullary functions aren't reading memory locations? Why do you feel that's an essential thing to specify in your public interface, as a default? There's nothing stopping you from writing code in Python and mentally modelling o.x as slot access, because it follows the interface you want from it.
I think this is a good question, because it illustrates a philosophical difference--if I understand your position correctly, you'd prefer to be as abstract as possible until it's problematic; I prefer to be as concrete as possible until abstraction is necessary. There's a lot of mathematical elegance in your position, and when I'm programming for fun I sometimes try to be maximally abstract; however, when I'm building something and _working with people_, experience and conventional wisdom tells me that I should be as concrete and flat-footed as possible (needless abstraction only makes it harder to understand).
To answer your question, that abstraction gets in your way all the time. The performance difference between a memory access (especially a cache-hit) and an HTTP request is several orders of magnitude. If you're doing that property access in a tight loop, you're wasting time on human-perceivable timescales. While you can "just be aware that any given property access could incur a network call", that really sucks for developers, and I see them miss this all the time (I work in a Python shop). We moved away from this kind of "smart object" pattern in our latest product, and I think everyone would agree that our code is much cleaner as a result (obviously this is subjective).
TL;DR: It's useful to have semantics for "this is a memory access", but that's unrelated to my original point :)
Using an array without having to (manually) calculate the size of the objects contained within is like the major triumph of OO. This is a getter that you almost certainly use constantly.
Please try to consider your statements and potential counter factuals before spraying nonsense into the void
Er, aside from C and ASM, few non-OO languages require that kind of manual effort. That's not a triumph of OO, it's a triumph of using just about any language that has an approach to memory management above the level of assembly.
My claim was that getter abstractions as described by the GP (abstracting over the “accessed from memory” implementation detail) are not useful. Why do you imagine that your array length example is a reasonable rebuttal?
Sorry for the way I communicated- I was tired and should have reconsidered.
No worries, it happens. :)
> Its not the length of the array. Its using things like array[20]. Yes that exists pre-OO and outside of OO, but its the foundational aspect of OO and one of the strongest use cases.
I'm not sure what you're getting at then. Indexing into an array? Are you making a more general point than arrays? I'm not following at all, I'm afraid.
You aren't responsible for maintaining any of the internal details, it just works like you want it to. My example was with the getter for the item at index 21 (since you had specifically called out useless getters), but equally well applies to inserting, deleting, capacity changes, etc.
I think I see what you mean, although I think it's worth being precise here--arrays can be operated on via functions/methods. This isn't special to OO; you can do the same in C (the reason it's tedious in C is that it lacks generics, not because it lacks some OO feature) or Go or Rust or lisp.
These functions aren't even abstractions, but rather they're concrete implementations; however, they can implement abstractions as evidenced by Java's `ArrayList<T> implements List<T>`.
And to the extent that an abstract container item access is a "getter", you're right that it's a useful abstraction; however, I don't think that's what most people think of when they think of "getter" and it falls outside the intended scope of my original claim.
I've used arrays in countless OO and non-OO programming languages, and I do not recall ever having to manually calculate the size of objects contained therein – what are you talking about? Only C requires crap like that, but precisely because it doesn't have first class arrays.
size_t len_a = sizeof(a)/sizeof(a[0]);
or my_pseudo_foo_array = (foo*) malloc(len * sizeof(foo));C# also 'fixes' that. o.x could be a slot or it could be a getter/setter.
fooobj.events += my_eventhandler;
Which isn't surprising, given that Delphi took the idea from Eiffel, which share the same Pascal influence, and was designed by Anders.
My guess is there's a correlation between overengineering and career success, which drives it. Simple, 'KISS' style code is the easiest to work with, but usually involves ditching less essential libraries and sticking more to standards, which looks crap on your resume. Most interviewers are more interested in whether you can piece together whatever stack they're using rather than whether you can implement a tough bit of logic and leave great documentation for it; so from a career perspective there's zero reason for me to go for a (relatively) simple 100 line solution to a tough problem when I can instead go for a library that solves 100 different use cases and has 10k lines of documentation that future devs have to wade through. The former might be the 'best' solution for maintainability but the latter will make me appear to be a better engineer, especially to non-technical people, of which there are far too many of on the average team.
Recent writings by Joe Armstrong are also resonate with me the same way.
1. By declaring a getter without a setter, you could make a field read-only.
2. A setter could trigger other side effects. Specifically, the JavaBeans spec allowed for an arbitrary number of listeners to register callbacks that trigger whenever a value gets changed.
Of course, nobody actually understood or correctly implemented all this, and it all got cargo culted to hell.
A big problem is cargo culting without reading the CS references.
Ruby is far from being my favorite language, but I like how Structs "solve" the getter/setter problem in it:
my_struct = Struct.new(:field_one, :field_two)
It doesn't clutter your code with multiple lines of boilerplate, and it returns a regular class for you to use, not breaking the "everything is a class" constraint.With respect to your setters and getters question, particularly in regards to Python... The @Property feature in Python is just a specific implementation of the setters/getters OO design principle. I can easily be convinced typing foo.x is better than foo.getX(), but I have a hard time having a strong emotional reaction to one vs the other if the language allows them to have the same benefits.
It seems to be what we do, I’d say fp is in the same place. My CS program was heavily built around the ML family of languages, specifically Standard ML, with the algebraic types, functions, pattern matching (on your types,) etc. it seems like that “functional programming” is a radically different thing than what people do in js or erlang and call it that. It all comes around, I guess, static types were pretty gauche 10-15 years back and now how many folks are using typescript to make their js better?
Or you get values from your objects. You need getters for this, but you can guarantee immutability and apply functional programming principles to your code.
You can't have your cake and eat it too. At the end of the day, you need values.