Equivalent of `@something` for field access

I really like the @something operation because it lets me write concise and readable code such as:

function get_operators(expr, operators=nothing)
    return @something(operators, get_metadata(expr).operators)
end

This is shorter, and I find it more readable and even more robust since it includes additional assertions !== nothing which I wouldn’t normally write.

I was wondering if there’s something similar for field access. Throughout my library I often have code like this:

y = isnothing(x) ? nothing : x.val
# OR
y = isnothing(x) ? nothing : x[i]

Essentially, it tries to perform a getproperty/getindex operation, but propagates nothing if the object itself is nothing.

Before writing my own utility function, I just wanted to check: am I missing something already available in Base? It took me a few years to discover @something, so I wouldn’t be surprised if there’s another built-in shorthand I’m overlooking.

Hmm, maybe it’s a bit too magical but I almost wouldn’t mind it if single-arg @something f(x) worked as if !isnothing(x); f(x) end (where f may also be getfield, getindex, etc.)

1 Like

Yes! Something like

y = @maybe x.val

would be very nice.

It is a somewhat distinct idea, so maybe a new name like @maybe is worth it, but I’m really not sure why you’d ever use single-arg @something without it doing something like what I mention — at least I feel like it could be cool (worth mentioning at least).

Also, this is a code pattern I’ve run into a bunch myself, so FWIW it’s not just you getting fed up of manually writing !isnothing checks.

1 Like

I guess I feel like @something should always return something that !== nothing, with the sole exception being if you wrap it with Some. So a single arg version feels a bit semantically uncomfy. Idk. Maybe even y = @safe x.val could be nice.

1 Like

Oh yes, good point, I’m with you. Obviously it’s not an option here, but this kind of reminds me of syntax in other languages like thing?.property where nulls/errors are propagated before .property is hit.

2 Likes

See:

using AccessorsExtra

@oget obj.a
@oget obj.a.b
@oget first(obj.a)

returns nothing if it encounters nothing at any step, or the property/… doesn’t exist. Works oob with many objects and accessors.
Also:

@oget obj.a.b obj.c.d 123

does what you expect from @something – goes left to right until finds something.


@maybe also exists, but creates a function like @optic in Accessors.jl but optional.

3 Likes

Nice! I like that a lot. Also seems like it has no overhead which is great

julia> function foo(x)
           return @oget x.a.b
       end
foo (generic function with 1 method)

julia> x = (; a=(; b=1))
(a = (b = 1,),)

julia> @code_llvm foo(x)
; Function Signature: foo(NamedTuple{(:a,), Tuple{NamedTuple{(:b,), Tuple{Int64}}}})
;  @ REPL[9]:1 within `foo`
define i64 @julia_foo_14575(ptr nocapture noundef nonnull readonly align 8 dereferenceable(8) %"x::NamedTuple") #0 {
top:
;  @ REPL[9]:2 within `foo`
; ┌ @ /Users/mcranmer/.julia/packages/AccessorsExtra/4VGss/src/maybe.jl:201 within `macro expansion`
   %"x::NamedTuple.a_ptr.b_ptr.unbox" = load i64, ptr %"x::NamedTuple", align 8
   ret i64 %"x::NamedTuple.a_ptr.b_ptr.unbox"
; └
}

If there’s any appetite to put some of that into Base I’d put my hat in the ring.

Neat! This reminds me of how much I’d like it if generalised references (think @optic) were a feature you got with Julia OOTB.

1 Like

That’s a key part of Accessors.jl philosophy in general :slight_smile:

1 Like

Optics are already part of a lightweight, stable, and very popular Accessors.jl :slight_smile: I like the Julian approach of very lightweight Base.

These “optional” optics (incl @maybe, @oget) can in general be upstreamed there from AccessorsExtra.jl, it just needs a more careful consideration of the api surface and edgecases. I never really came to do this.

Actually, maybe they shouldn’t be upstreamed to Accessors proper, but to a separate AccessorsMaybe package?.. The full AccessorsExtra.jl has a lot more stuff and I don’t focus on keeping it lightweight.

4 Likes

I think that would be a great idea.

In my libraries, I try to minimise dependencies, particularly those that aren’t (yet) widely used. Small quality-of-life features (especially ones stable enough to rarely need updates) I tend to stick to only the ones from Base. Not only to reduce dependency management, but also as a type of language standardization, so that newcomers don’t have to learn different syntaxes for every utility library I am using. e.g., @something is in Julia itself so it isn’t too exotic for someone to encounter in my codebase.

However, if a dependency is well-contained and unambitious, I feel much more comfortable relying on it.

All of this is to say that, yes, I think AccessorsMaybe would be a great idea.

Light is generally pretty good, but in my mind at least I see just the basic but extensible interface as the sort of thing that could be pretty lightweight.

That said, these days package extensions are great for making it possible to have an optional implementation of an interface in a package.

I’d love to have this syntax supported in Base. We have the Union{T, Nothing} pattern all over the place and it’s well-established as a pattern in other languages. It would be super convenient to be able to do x?.y?.z?.a and have the nothings propagate.

5 Likes

Funnily enough, despite bringing up the ?. syntax, I’m actually not a fan of introducing it to Julia :face_with_tongue: — I’m still pining for the ability to use ? in identifiers like ! (#22025).

There’s also the competing proposal to make T? sugar for Union{T, Missing} (#36628).

If we did have an error-value system like Zig/Rust I think having a dedicated syntax would be justified (this or some similar syntax), but I don’t think we’re quite there in Julia.

That said, I would very much like something to make this pattern easier, and this seems like something that could easily be accomplished with a macro. It occurs to me that the name @passnothing is pretty explicit, but maybe not terse enough to provide the sort of convenience that Jacob is looking for?

1 Like