I’ve just been trying to pick up Julia this morning.
And I thought, what’s better than abusing so struct behaves like Python class!
Totally understand things are preferably immutable, and methods declaration are external to structs etc. But just for the sake of curiosity and learning:
Is there a more succinct way to define the inner constructor to achieve the same as below code?
What’s the meaning of defining variables or functions in the struct (but outside of constructor) scope? Only to be used in inner constructor?
julia> mutable struct C
id::String
speak::Function
self::C
f(self::C, word::String) = println("I'm $(self.id) yeah. $word.")
function C(id::String)
instance = new(id)
instance.speak = (word::String) -> f(instance, word)
instance.self = instance
end
end
julia> julia = C("julia")
C("julia", var"#5#7"{C,var"#f#6"}(C(#= circular reference @-2 =#), var"#f#6"()), C(#= circular reference @-1 =#))
julia> julia.speak("hey")
I'm julia yeah. hey.
julia> julia.id = "eve"
"eve"
julia> julia.speak("yo")
I'm eve yeah. yo.
You can use @with_kw from Parameters.jl to define your default values like:
@with_kw mutable struct C
id::String
speak::Function = (word) -> println(id, word)
end
(I’ve shortened your example a bit, but hopefully the idea makes sense).
I guess you could use it for that–it’s extremely unusual to define anything inside the struct other than its fields, and I don’t think I’ve ever seen it done in Julia code in the wild. It’s not something I’ve ever needed, particularly because as soon as you start putting your behavior outside of your structs, it doesn’t really make sense.
It’s worth noting that Function is an abstract type, every function has it’s own type which is a subtype of Function. If you want your struct to have concrete storage, you can parameterize it by the type of the function, e.g.
or you could just abuse getproperty to add methods:
julia> mutable struct C
id::String
end
julia> function Base.getproperty(self::C, s::Symbol)
if s === :speak
word -> println("I'm $(self.id) yeah. $word.")
else
getfield(self, s)
end
end
julia> julia = C("julia")
C("julia")
julia> julia.speak("hi")
I'm julia yeah. hi.
Thanks fellas! I think I’ve learned more than I set out for.
Less importantly, after looking at Parameters.jl examples and the implementation a bit turns out there’s seems to be no easy way to combine @with_kw with self referential construction
(I was trying to game it so method call outcome changes based on instance state without it being supplied as argument just like Python, thus the self)
The best (without spending a lot of time) I can come up with is something like this but it isn’t nice because I’m redefining speak() which changes the type so it only works when it’s abstract:
For a real life use case of self referential struct having default value I think it might be useful for certain kind of tree nodes say?
Anyway.
Love to see the language can indeed express obscure things outside of idiomatic approach with relative ease.
Love to see I’m getting reply here. Thank you.
And of course multi dispatch, which is why I’m drawn here. (I started using Kotlin at work a year ago, think it’s amazing, a bit more functional, less OO, If only it supports multi dispatch, would get rid of a lot of “design patterns” - boiler plates)
I think my next project would most likely be in Julia. Even for a case like backend web service, I have a feeling the pros would outweigh the cons, e.g. comparatively less mature frameworks (routes + authn/z + openapi code generation, cloud clients etc…), I can see the pieces out there just expecting there would be more work to stitches them together or help patch things up.