Hi,
I’m new to Julia, but with long strong background in object-oriented design. What I haven’t yet got my mind around is how to design a type with private state. An example might be an object containing a Dirty flag, indicating whether its state needs recalculating. Clients of the object are only interested in public aspects of this state, whereas the Dirty flag is only to signal internally whether the current values can be given straight to the user, or whether they must be recalculated first. The user shouldn’t be able to access this purely implementation information.
As a simple case example, I think it would help if someone could tell me how to design types and modules to implement a Wallet class. Wallet objects contain a certain amount of money which can be spent or credited, but the client cannot ask how much is in the Wallet - only ask to spend a certain amount of money or credit money to the Wallet. If there isn’t enough in the Wallet, the client gets to spend zero money. Can anyone help?
Like in Python, there is no way to completely shield fields in objects from outside access. Instead this is done by convention, whereby internal fields are not part of the API (unless documented as such).
The general consensus has been that accessing any field of a type makes for a tightly-coupled, non-OO interface. Using accessor functions to manage state provides flexible access and behavior controls, and overloading.
Is that really the case? I can’t quite imagine that steering clear of internal fields purely by convention would protect my Wallet from corruption in an industrial setting. What about a client programmer who happens to find it convenient in some particular case to directly ‘top up’ the amount of money in the Wallet as a temporary workaround which then never gets corrected until the day it causes an embarrassing crash? Don’t we want some way to prevent that?
My issue isn’t with using accessor methods, but with the direct exposure of state involved in exporting a type from a module. It seems to me I should be able to instantiate two Wallets w1 and w2, each containing their own amount of money, but which is accessible to clients only via an accessor such as spend(w1,5). As far as I can see at present, instance fields are only available in Julia via “type Wallet”, which can be packaged together with the functionality spend() and credit() in a module, but then w1.contents is directly accessible. It seems to me there’s some vital aspect of Julia that I’m missing here.
also a documentation issue. the only thing julia does not have here is a distinction between “not exported because rarely needed”, “not exported because subject to change” and “not exported because unsafe”. whether the language should handle this, or documentation is fine, i couldn’t tell. but if there is some feature to give warning to the user, it can’t hurt that much.
@lobingera: Thanks very much for this reference - it discusses the issue thoroughly. I personally am convinced that a ‘private’ keyword for_type_fields would be a Good Thing, to avoid reliance on labile code, and also to prevent corruption of internal state. However, clearly there’s a large body of opinion out there that disagrees with me, but at least I now know that there is no way to encapsulate type data. Ah well. Thanks for all the help!
I think it’s important to understand that there’s a design tradeoff here. Some languages (like Python or Lisps) allow the user complete freedom to do basically anything they want, at the cost of allowing them to do bad things. Meanwhile, languages like Java or Go will prevent you from doing bad things (like accessing fields) at the cost of burning you when you have a good reason to break the rules.
Those who are doing more exploratory programming are going to have completely different priorities to those building a banking system in a large team. As Julia is aimed at the former, it generally takes a “consenting adults” approach and opts for flexibility. For example: operator overloading is easy to abuse but essential for convenience when working with mathematical objects other than numbers. Macros introduce a higher learning curve but make custom optimisations easy. Access control helps encapsulation but will prevent you from writing a display system that does better than <Foo@0x5faf02>, or implementing automatic serialisation to JSON, etc. In each case Julia takes the opposite choice compared to Java-likes.
This doesn’t mean those issues aren’t valued at all. But it might mean taking a different approach, e.g. having field access be caught by a linter rather than enforced by the compiler. In the mean time you’re welcome to use PrivateRyan.
I’d avoid using the phrase “consenting adults” with regards to Julia’s philosophy, for two reasons (and this was discussed back a year and a half ago)
Outside of some discussions of Python, it is almost exclusively used in a sexual context (every dictionary definition I’ve found as been along the lines of:
a person who is considered old enough, and therefore responsible enough, to decide if they want sex and who they want to have sex with
Since people take great pains to avoid even references to gender (unavoidable in many languages) with respect to Julia, this seems to be very inappropriate.
If there is no means of denying consent (i.e. by having the means to make something private, or to mark something as public [even if it is not exported because you don’t want to pollute the namespace] even if it just causes a warning if you access something private, like a deprecation, and/or requires some special “sin-tax” (like .. instead of ., as CL does with :: instead of :), then you can’t really claim there is consent (just as in the US at least, being married doesn’t imply a blanket consent)
Most of the time, I think that types/fields of types/constants within a module are considered private (which is why having to use _ in front of all of them a la Python to indicate that would be a big pain), but also many times, modules or packages don’t necessarily want to export everything that they mean to be part of a public API, because that would cause a lot of namespace pollution (Pkg and PkgDev are examples).
My proposal would be to add a “public” keyword, like “export”.
Names not exported or public would still be accessible outside the module via new syntax (.. seems to be the prefered syntax that I’ve seen).
‘using’ would bring into the current namespace all the exported names, all public names would be available via normal . syntax, and all others would be available via ... Trying to access a name not exported or public via . could simply give a warning like deprecation does (so that people 1) know that the name/field is not part of the public API 2) they can push to get something added to the public API if they need to 3) they can pay the “sin tax” of adding the extra . to avoid the warning - which means those places can easily be found if things later break [like accesses to .data now with jb/fasterstring] import would act the same, except would not bring anything into the current namespace.
This would make it very clear exactly what is intended to be available via the public API (even if not exported), something this is not the case currently.
I think this approach would be enough for those of us who need to manage large projects in Julia with many developers, without really slowing down people who just want to get something done in Julia.
In other words, I do think we can have our cake and eat it too.
what is now exported, would be public
what is now not exported but considered useful outside, would be export
these two together would make the interface
what is unsafe or under construction, will not be exported, and accessible only through special syntax
personally, i don’t think the last part is necessary. as soon as the intended visibility is clearly marked, we are done. it is all about information.
export would imply public, as well as (via using) being added to the namespace
public names would be available without any special syntax via qualification (<module>.<name>)
any other names require the <module>..<name> qualification (of course, you can do const foo = SPJ..foo to avoid paying the sin tax more than once in your module )
The last part is what helps later on, when you are trying to find all the places that might be broken due to changes in internals, and also helps people remember that they are accessing something that might change in the future
(like using functions with the unsafe_ prefix, you know you are treading into deep waters).
That’s why I like the concept of a “sin tax”, it doesn’t prevent you from “sinning”, but you do know that you are breaking one of the (encapsulation) commandments.
Up to and including Julia 1.0, the language is very much in a “can do” design phase: we’re focused on allowing people to do difficult things, not preventing them from doing stupid things. It turns out that high-productivity, high-performance numerical computing is a gold mine for problems that have not been well addressed by traditional computer science programming language designs. Once we’ve satisfactorily allowed people to do all the things we want to, we can start thinking about features that prevent people from doing things.
My proposal is not about preventing people from doing anything - it’s more about guiding people into using public APIs instead of accessing things that very well may be changed in the future (like .data in strings), and making it easy for people to find out where their code needs to be changed if for some reason they had to use something that was not part of that public API and the implementation changes. That can be a big help on large projects.