Do julia structs have member functions (and this->)


#1

I see that structs can have constructors, but can they also contain functions? I tried

julia> mutable struct b; x::Int64; function y() x=22; end; end

which gave no error, but b.y() does not work.


#2

Sure, you just can’t initialize them like that:

julia> struct Foo
         f
       end

julia> F = Foo(x -> 2x)
Foo(#9)

julia> F.f(3)
6

For this to be fast you’d want to parametrize Foo on the type of f though:

struct Foo{T}
  f::T
end

Just noticed that you probably wanted your y to mutate b.x or be a static method or something, so the above is probably irrelevant.


#3

thanks, pfitzseb. alas, I did not mean just the ability for structs to contain functions. I meant member functions with convenient default access to the contents of the struct (aka, C++ member functions). I can accomplish the same with

function y(Foo foo)
    foo.x=22
end#function y

## not, as in C++, grouped inside the struct, and with access to member [this->x]
struct Foo
   x::Int64
   function y() x=22; end#function
end#struct

so, it is more of a style question, coming from C++.


#4

Short answer: No. You need to pass the this-pointer (mutables are de-facto pointers) explicitly, i.e. define
function y(some_b::b) some_b.x=22; end;

If this isn’t about syntactic sugar but name-spaces, consider writing a module where you would otherwise write a class with lots of methods (and then just don’t export the stuff you consider private).

Or will this end up about virtual functions / OOP? Then it is complicated and you need to plan somewhat differently in julia.

Inner constructors are AFAIK really only meant for immutable structs.

edit: Forget about the pointer/mutable/immutable, you always need to pass this explicitly, regardless of whether it is mutable or not. C-style.


#5

You could do the following…

function Foo()
      x::Int = -1
      setx(x_) = (x = x_) 
      getx() = x
      () -> (getx;setx)
end

f = Foo()
f.getx()
f.setx(22)
f.getx()
f.setx("22") # (fails)

#6

@vvjn do you know, whether the fact, that getx and setx are fields of f is just an implementation detail, that may go away at any point, or if this is behaviour that one can rely on?


#7

I’m not completely sure but I don’t think this will go away. Besides, in 1.0 you will be able to overload the . operator. So, this can be replicated pretty easily by overload ..

And btw, this style is not really recommended for julia since it doesn’t look very pretty to most julia people and you can’t perform dispatch on the resulting object.


#8

I came across a similar discussion on stack overflow that might be of interest.


#9

My understanding is that object methods are not part of the design, and won’t be in order to preserve the benefits of multiple dispatch. There’s much written on this out there even in the language doc (see here in Methods for a start) .


#10

Don’t do this!

using BenchmarkTools

function Foo()
      x::Int = -1
      setx(x_) = (x = x_) 
      getx() = x
      () -> (getx;setx)
end

f = Foo()

function fob(f, x)
       f.setx(x)
end

mutable struct Foo2
    x::Int64
end

function setx(a::Foo2, v)
 a.x=v
end

foo=Foo2(4)

@btime fob($f, $22)
  72.118 ns
@btime setx($foo, $22)
  2.209 ns

Alternatively, read the @code_native or @code_llvm generated by this abomination to see why it is not nice.


#11

the stack overflow example is interesting and unexpected, so let me repeat it:

julia> function Person(name, age)
        getName() = name
        getAge() = age
        getOlder() = (age+=1)
        ()->(getName;getAge;getOlder)
       end
Person (generic function with 1 method)

julia> o = Person("bob", 26)
(::#3) (generic function with 1 method)

julia> o.getName()
"bob"

julia> o.getAge()
26

julia> o.getOlder()
27

julia> o.getAge()
27

as to my original question, I am perfectly happy with julia’s way of doing things, so no object-orientation ala C++ or Java. I just wanted to be sure that I am using julia as intended.


#12

Got curious, and it looks like f.setx is faster than python method calling.

class Foo:
    def __init__(self):
        self.x = -1

    def setx(self, x_):
        self.x = x_
        
if __name__ == '__main__':
    import timeit
    n = 10000000
    t = timeit.timeit("f.setx(22)", setup="from __main__ import Foo; f = Foo()", number=n)
    print(str((t/n)/1e-9) + " ns")

Above gives me around 100ns, while running @btime fob($f, $22) from your code gives me around 40ns.


#13

I strongly suggest you read this and this before designing anything that contains structs with functions as members. Multiple dispatch is a beautiful paradigm for writing code, it’s definitely worth getting familiar with it and how Julia instantiates it. I came to Julia for the performance, but it’s probably this more than anything else that keeps me not wanting to ever use another language. Sometimes it make sense to have functions as struct members even within the paradigm of multiple dispatch, but usually it doesn’t.


#14

Also bear in mind that Julia 0.7 is slated to have simple get/set accessors for struct fields. So, there is little point in writing lots of those.

Mainly I second @ExpandingMan - each language will have slightly different usage patterns, and Multiple Dispatch is amazing in use.


#15

You could also do this…

      type Foo
         x::Int
         setx
         getx
         function Foo(x)
           f = new(x)
           f.setx = (x_) -> (f.x = x_)
           f.getx = () -> f.x
           f
         end
       end

setx and getx are still slow but faster than the function one, though x is modifiable now.


#16

In 0.7, this will be:

mutable struct Foo
    x::Int
end

function Base.setproperty!(f::Foo, v, s::Symbol)
    if s == :x
        f.x = v
    else
        error("unknown property $s")
    end
end

function Base.getproperty(f::Foo, s::Symbol)
    if s == :x
        return f.x
    else
        error("unknown property $s")
    end
end

And will have no performance penalty.


#17

I’m curious, this would seem to imply that Julia runs the setproperty! and getproperty functions at compile time. Otherwise, there certainly would be a performance penalty because running getproperty at run-time and checking a conditional structure would certainly be slower than simply accessing a field.

Is that indeed what happens, getproperty and setproperty! will run at compile time?


#18

The functions themselves can’t be run at compile time (you won’t know what e.g. f.x is when you compile) but all the branches and stuff will be gotten rid of since the symbol is a constant, and 0.7 have new fancy constant propagations spanning function boundaries.