OOP-like dot notation in Julia

My two cents are probably already well covered in those Github issues, but here they are anyway.

This isn’t the first time someone wanted the streamed code completion in OOP languages, not even the only time in the last week. But it’s worth looking at what those languages actually do to pull this off, and they don’t all do the same things.

In most OOP languages, object.method(...) calls a member method, which has been thought of as “message” between instances of classes. If you want to discover an object’s possible messages, you only need to search its class. In OOP languages like Java where all methods are encapsulated by classes, tab completion can reach everything, though the leading object is not necessarily the input you’re interested in.

Other OOP languages however have functions outside classes, and typical tab-completion cannot get those from objects at all because of the different syntax. Functions outside a class or the class’s module are very important for extending behavior; while inheritance+overriding and dynamic languages’ monkeypatching can do that within classes, it often traps code in limitations and boilerplate that neither developers nor users want to deal with. Some domains like scientific computing, which Julia started in, use functions much more than member methods, even implementing functions to forward to member methods so users won’t need to occasionally switch to member method syntax.

Like Lisp’s CLOS (also OOP), Julia encapsulates methods in multiply dispatched generic functions instead of types; for brevity, I won’t explain how multiple dispatch was found to mix poorly with class encapsulation in several language’s approaches. So, objects have no member methods to find, only fields and what may be inferred from getproperty. If property syntax were to lower to function calls (a breaking change) to emulate member method syntax, tab-completion might try to find methods that can take our object as a first argument. Seems reasonable right? Let’s check help mode for the single-argument case:

julia> struct X end # totally new type, no methods

help?> ?(X(),)
broadcast(f, x::Number...) @ Base.Broadcast broadcast.jl:844
oneunit(x::T) where T @ Base number.jl:371
Text(content::T) where T @ Base.Docs docs\utils.jl:88
widen(x::T) where T @ Base operators.jl:891
error(s::Vararg{Any, N}) where N @ Base error.jl:42
VecElement(arg::T) where T @ Core boot.jl:408
replace(A, old_new::Pair...; count) @ Base set.jl:686
fill(v, dims::Union{Integer, AbstractUnitRange}...) @ Base array.jl:582
vcat(X::T...) where T @ Base abstractarray.jl:1613
replace!(A, old_new::Pair...; count) @ Base set.jl:606
HTML(content::T) where T @ Base.Docs docs\utils.jl:30
bitstring(x::T) where T @ Base intfuncs.jl:918
Some(value::T) where T @ Base some.jl:12
hcat(X::T...) where T @ Base abstractarray.jl:1615

Wow, we already have some hits in Base even without implementing anything on our own! Let’s try one (pun unintended):

julia> oneunit(X())
ERROR: MethodError: no method matching one(::X)

Closest candidates are:
  one(::Type{Union{}}, Any...)
   @ Base number.jl:349
  one(::Type{Missing})
   @ Base missing.jl:104
  one(::Missing)
   @ Base missing.jl:101
  ...

Stacktrace:
 [1] oneunit(x::X)
   @ Base .\number.jl:371

While we would dispatch to the oneunit method, we hit a MethodError on the callee one. In fact most of the printed methods will hit a downstream error, and it’s no surprise because we implemented nothing for X. We don’t actually want to find all the methods that can merely take our object as an argument, we want methods that work on our object; I don’t want to see a method for iterables until after I implement Base.iterate for my type. The reason why this feature isn’t mentioned much is simple; false positives are just not useful.

Barring extreme incompetence, member methods are indeed designed to work on their classes’ instances, so they’re worth discovering. However, discovering other useful functions is still an active area of development; typically we still read library documentation. For Julia in particular, formalizing interfaces could help, especially in the case of iterables.

16 Likes