How do we do classes in Julia? - julia

I have a problem with writing classes in Julia. I have looked at the documentation and I haven't seen any docs on classes.
In Python, classes are, for example,
class Dog:
# ----blah blah---
How is this possible in Julia?

Julia does not have classes. Instead we define new types and then define methods on those types. Methods are not "owned" by the types they operate on. Instead, a method can be said to belong to a generic function of the same name as the method. For instance, there are many versions ("methods") of the length function; together they form the generic function length.
Here's an extended example of the Julian approach to programming with types and methods. New types are declared using the struct keyword:
struct Person
name::String
age::Int64
end
Now we can define methods on the Person type:
name(p::Person) = p.name
age(p::Person) = p.age
bio(p::Person) = println("My name is ", name(p)," and I am ", age(p), " years old.")
Methods can be defined for different combinations of argument types. To illustrate this, let's first define some new types:
abstract type Pet end
struct Cat <: Pet
name::String
color::String
end
name(c::Cat) = c.name
color(c::Cat) = c.color
species(::Cat) = "cat"
struct Dog <: Pet
name::String
color::String
end
name(d::Dog) = d.name
color(d::Dog) = d.color
species(::Dog) = "dog"
bio(p::Pet) = println("I have a ", color(p), " ", species(p), " named ", name(p), ".")
struct Plant
type::String
end
type(p::Plant) = p.type
bio(p::Plant) = println("I have a ", type(p), " house plant.")
At this point we can see that we've defined three different one-argument methods for bio:
julia> methods(bio)
3 methods for generic function "bio":
[1] bio(p::Plant) in Main at REPL[17]:1
[2] bio(p::Person) in Main at REPL[4]:1
[3] bio(p::Pet) in Main at REPL[14]:1
Note the comment in the output of methods(bio): "3 methods for generic function 'bio'". We see that bio is a generic function that currently has 3 methods defined for different function signatures. Now let's add a two-argument method for bio:
function bio(person::Person, possession)
bio(person)
bio(possession)
end
Notice that this function is generic in the possession argument, since the internal call to bio(possession) will work whether the possession is a plant, cat, or dog! So we now have four total methods for bio:
julia> methods(bio)
4 methods for generic function "bio":
[1] bio(p::Plant) in Main at REPL[17]:1
[2] bio(p::Person) in Main at REPL[4]:1
[3] bio(p::Pet) in Main at REPL[14]:1
[4] bio(person::Person, possession) in Main at REPL[18]:1
Now let's create a few instances of our types:
alice = Person("Alice", 37)
cat = Cat("Socks", "black")
dog = Dog("Roger", "brown")
plant = Plant("Boston Fern")
So finally we can test our bio methods:
julia> bio(alice, cat)
My name is Alice and I am 37 years old.
I have a black cat named Socks.
julia> bio(alice, dog)
My name is Alice and I am 37 years old.
I have a brown dog named Roger.
julia> bio(alice, plant)
My name is Alice and I am 37 years old.
I have a Boston Fern house plant.
Side note: Modules are used primarily for namespace management. A single module can contain the definitions for multiple types and multiple methods.

The closest one can get to classes with methods in Julia is the module:
module DogClass
export Dog, bark
struct Dog
name::String
end
function bark(d::Dog)
println(d.name, " says woof!")
end
end #MODULE
using .DogClass # note the . here, means look locally for module, not library
mydog = Dog("Fido")
bark(mydog)

Related

Pretty printer of objects in Julia

I want to write a function in Julia that can take any composite type and pretty-print the names of the nested members, their types and their values, and I want to put this function in a package for the community to use.
Imagine a user has the following structs:
struct House
value::Int32
rooms::Int32
number::Int32
end
struct Street
name::String
houses::AbstractArray{House}
end
struct Town
name::String
streets::AbstractArray{Street}
end
town = Town(<initialization code here>)
This user can call PrettyPrinter.print(town), which should output something like
town::Town
name::String = "London"
streets[1]::Street
name::String = "Downing Street"
houses[1]::House
value::Int32 = 100000
rooms::Int32 = 5
number::Int32 = 10
houses[2]::House
value::Int32 = 300000
rooms::Int32 = 6
number::Int32 = 40210
But of course, the PrettyPrinter package can not parse the struct implementation, it has to do its job by low level Julia trickery. My problem is not with the recursive programming.
Question: How do I access the member names, their types and their values?
This functionality is already built-in into dump function:
julia> t = Town("London", [Street("Downing Street",[House(100000,5,10),House(300000,6,40210)])])
Town("London", Street[Street("Downing Street", House[House(100000, 5, 10), House(300000, 6, 40210)])]);
julia> dump(t)
Town
name: String "London"
streets: Array{Street}((1,))
1: Street
name: String "Downing Street"
houses: Array{House}((2,))
1: House
value: Int32 100000
rooms: Int32 5
number: Int32 10
2: House
value: Int32 300000
rooms: Int32 6
number: Int32 40210
However, if you want query the data, you can use the fieldnames function:
julia> fieldnames(typeof(t))
(:name, :streets)
julia> getfield(t, :name)
"London"

How to dispatch based on the type of any of the splatted args?

Consider an existing function in Base, which takes in a variable number of arguments of some abstract type T. I have defined a subtype S<:T and would like to write a method which dispatches if any of the arguments is my subtype S.
As an example, consider function Base.cat, with T being an AbstractArray and S being some MyCustomArray <: AbstractArray.
Desired behaviour:
julia> v = [1, 2, 3];
julia> cat(v, v, v, dims=2)
3×3 Array{Int64,2}:
1 1 1
2 2 2
3 3 3
julia> w = MyCustomArray([1,2,3])
julia> cat(v, v, w, dims=2)
"do something fancy"
Attempt:
function Base.cat(w::MyCustomArray, a::AbstractArray...; dims)
pritnln("do something fancy")
end
But this only works if the first argument is MyCustomArray.
What is an elegant way of achieving this?
I would say that it is not possible to do it cleanly without type piracy (but if it is possible I would also like to learn how).
For example consider cat that you asked about. It has one very general signature in Base (actually not requiring A to be AbstractArray as you write):
julia> methods(cat)
# 1 method for generic function "cat":
[1] cat(A...; dims) in Base at abstractarray.jl:1654
You could write a specific method:
Base.cat(A::AbstractArray...; dims) = ...
and check if any of elements of A is your special array, but this would be type piracy.
Now the problem is that you cannot even write Union{S, T} as since S <: T it will be resolved as just T.
This would mean that you would have to use S explicitly in the signature, but then even:
f(::S, ::T) = ...
f(::T, ::S) = ...
is problematic and a compiler will ask you to define f(::S, ::S) as the above definitions lead to dispatch ambiguity. So, even if you wanted to limit the number of varargs to some maximum number you would have to annotate types for all divisions of A into subsets to avoid dispatch ambiguity (which is doable using macros, but grows the number of required methods exponentially).
For general usage, I concur with Bogumił, but let me make an additional comment. If you have control over how cat is called, you can at least write some kind of trait-dispatch code:
struct MyCustomArray{T, N} <: AbstractArray{T, N}
x::Array{T, N}
end
HasCustom() = Val(false)
HasCustom(::MyCustomArray, rest...) = Val(true)
HasCustom(::AbstractArray, rest...) = HasCustom(rest...)
# `IsCustom` or something would be more elegant, but `Val` is quicker for now
Base.cat(::Val{true}, args...; dims) = println("something fancy")
Base.cat(::Val{false}, args...; dims) = cat(args...; dims=dims)
And the compiler is cool enough to optimize that away:
julia> args = (v, v, w);
julia> #code_warntype cat(HasCustom(args...), args...; dims=2);
Variables
#self#::Core.Compiler.Const(cat, false)
#unused#::Core.Compiler.Const(Val{true}(), false)
args::Tuple{Array{Int64,1},Array{Int64,1},MyCustomArray{Int64,1}}
Body::Nothing
1 ─ %1 = Main.println("something fancy")::Core.Compiler.Const(nothing, false)
└── return %1
If you don't have control over calls to cat, the only resort I can think of to make the above technique work is to overdub methods containing such call, to replace matching calls by the custom implementation. In which case you don't even need to overload cat, but can directly replace it by some mycat doing your fancy stuff.

custom ordering in Julia SortedSet

In the Julia documentation for SortedSet, there is a reference to "ordering objects", which can be used in the constructor. I'm working on a project where I need to implement a custom sort on a set of structs. I'd like to use a functor for this, since there is additional state I need for my comparisons.
Here is a somewhat simplified version of the problem I want to solve. I have two structs, Point and Edge:
struct Point{T<:Real}
x::T
y::T
end
struct Edge{T<:Real}
first::Point{T}
second::Point{T}
end
I have a Point called 'vantage', and I want to order Edges by their distance from 'vantage'. Conceptually:
function edge_ordering(vantage::Point, e1::Edge, e2::Edge)
d1 = distance(vantage, e1)
d2 = distance(vantage, e2)
return d1 < d2
end
Are "ordering objects" functors (or functor-ish)? Is there some other conventional way of doing this sort of ordering in Julia?
An Ordering object can contain fields, you can store your state there. This is an example of a Remainder Ordering which sort integers by it's remainder:
using DataStructures
struct RemainderOrdering <: Base.Order.Ordering
r::Int
end
import Base.Order.lt
lt(o::RemainderOrdering, a, b) = isless(a % o.r, b % o.r)
SortedSet(RemainderOrdering(3), [1,2,3]) # 3, 1, 2
I'm not sure how it is related to functors, so I may misunderstand your question. This is an alternative implementation that defines an Ordering functor. I made explanations in comments.
using DataStructures
import Base: isless, map
struct Foo # this is your structure
x::Int
end
struct PrimaryOrdered{T, F} # this is the functor, F is the additional state.
x::T
end
map(f::Base.Callable, x::T) where {T <: PrimaryOrdered} = T(f(x.x)) # this makes it a functor?
isless(x::PrimaryOrdered{T, F}, y::PrimaryOrdered{T, F}) where {T, F} =
F(x.x) < F(y.x) # do comparison with your additional state, here I assume it is a closure
const OrderR3 = PrimaryOrdered{Foo, x -> x.x % 3} # a order that order by the remainder by 3
a = OrderR3(Foo(2))
f(x::Foo) = Foo(x.x + 1) # this is a Foo -> Foo
a = map(f, a) # you can map f on a OrderR3 object
a == OrderR3(Foo(33)) # true
a = map(OrderR3 ∘ Foo, [1, 2, 3])
s = SortedSet(a)
map(x->x.x, s) # Foo[3, 1, 2]
As always, an MWE is important for a question to be understood better. You can include a piece of code to show how you want to construct and use your SortedSet, instead of the vague "state" and "functor".
The sorting is based on the method isless for the type. So for instance if you have a type in which you want to sort on the b field. For instance you can do
struct Foo{T}
a::T
b::T
end
Base.:isless(x::T,y::T) where {T<:Foo} = isless(x.b,y.b)
s=[Foo(1,2),Foo(2,-1)]
res=SortedSet(s)
#SortedSet(Foo[Foo(2, -1), Foo(1, 2)],
#Base.Order.ForwardOrdering())
Tuples are also sorted in order, so you can also use
sort(s,by=x->(x.b,x.a)) to sort by b,thena without having to define isless for the type.

Evaluate expression with local variables

I'm writing a genetic program in order to test the fitness of randomly generated expressions. Shown here is the function to generate the expression as well a the main function. DIV and GT are defined elsewhere in the code:
function create_single_full_tree(depth, fs, ts)
"""
Creates a single AST with full depth
Inputs
depth Current depth of tree. Initially called from main() with max depth
fs Function Set - Array of allowed functions
ts Terminal Set - Array of allowed terminal values
Output
Full AST of typeof()==Expr
"""
# If we are at the bottom
if depth == 1
# End of tree, return function with two terminal nodes
return Expr(:call, fs[rand(1:length(fs))], ts[rand(1:length(ts))], ts[rand(1:length(ts))])
else
# Not end of expression, recurively go back through and create functions for each new node
return Expr(:call, fs[rand(1:length(fs))], create_single_full_tree(depth-1, fs, ts), create_single_full_tree(depth-1, fs, ts))
end
end
function main()
"""
Main function
"""
# Define functional and terminal sets
fs = [:+, :-, :DIV, :GT]
ts = [:x, :v, -1]
# Create the tree
ast = create_single_full_tree(4, fs, ts)
#println(typeof(ast))
#println(ast)
#println(dump(ast))
x = 1
v = 1
eval(ast) # Error out unless x and v are globals
end
main()
I am generating a random expression based on certain allowed functions and variables. As seen in the code, the expression can only have symbols x and v, as well as the value -1. I will need to test the expression with a variety of x and v values; here I am just using x=1 and v=1 to test the code.
The expression is being returned correctly, however, eval() can only be used with global variables, so it will error out when run unless I declare x and v to be global (ERROR: LoadError: UndefVarError: x not defined). I would like to avoid globals if possible. Is there a better way to generate and evaluate these generated expressions with locally defined variables?
Here is an example for generating an (anonymous) function. The result of eval can be called as a function and your variable can be passed as parameters:
myfun = eval(Expr(:->,:x, Expr(:block, Expr(:call,:*,3,:x) )))
myfun(14)
# returns 42
The dump function is very useful to inspect the expression that the parsers has created. For two input arguments you would use a tuple for example as args[1]:
julia> dump(parse("(x,y) -> 3x + y"))
Expr
head: Symbol ->
args: Array{Any}((2,))
1: Expr
head: Symbol tuple
args: Array{Any}((2,))
1: Symbol x
2: Symbol y
typ: Any
2: Expr
[...]
Does this help?
In the Metaprogramming part of the Julia documentation, there is a sentence under the eval() and effects section which says
Every module has its own eval() function that evaluates expressions in its global scope.
Similarly, the REPL help ?eval will give you, on Julia 0.6.2, the following help:
Evaluate an expression in the given module and return the result. Every Module (except those defined with baremodule) has its own 1-argument definition of eval, which evaluates expressions in that module.
I assume, you are working in the Main module in your example. That's why you need to have the globals defined there. For your problem, you can use macros and interpolate the values of x and y directly inside the macro.
A minimal working example would be:
macro eval_line(a, b, x)
isa(a, Real) || (warn("$a is not a real number."); return :(throw(DomainError())))
isa(b, Real) || (warn("$b is not a real number."); return :(throw(DomainError())))
return :($a * $x + $b) # interpolate the variables
end
Here, #eval_line macro does the following:
Main> #macroexpand #eval_line(5, 6, 2)
:(5 * 2 + 6)
As you can see, the values of macro's arguments are interpolated inside the macro and the expression is given to the user accordingly. When the user does not behave,
Main> #macroexpand #eval_line([1,2,3], 7, 8)
WARNING: [1, 2, 3] is not a real number.
:((Main.throw)((Main.DomainError)()))
a user-friendly warning message is provided to the user at parse-time, and a DomainError is thrown at run-time.
Of course, you can do these things within your functions, again by interpolating the variables --- you do not need to use macros. However, what you would like to achieve in the end is to combine eval with the output of a function that returns Expr. This is what the macro functionality is for. Finally, you would simply call your macros with an # sign preceding the macro name:
Main> #eval_line(5, 6, 2)
16
Main> #eval_line([1,2,3], 7, 8)
WARNING: [1, 2, 3] is not a real number.
ERROR: DomainError:
Stacktrace:
[1] eval(::Module, ::Any) at ./boot.jl:235
EDIT 1. You can take this one step further, and create functions accordingly:
macro define_lines(linedefs)
for (name, a, b) in eval(linedefs)
ex = quote
function $(Symbol(name))(x) # interpolate name
return $a * x + $b # interpolate a and b here
end
end
eval(ex) # evaluate the function definition expression in the module
end
end
Then, you can call this macro to create different line definitions in the form of functions to be called later on:
#define_lines([
("identity_line", 1, 0);
("null_line", 0, 0);
("unit_shift", 0, 1)
])
identity_line(5) # returns 5
null_line(5) # returns 0
unit_shift(5) # returns 1
EDIT 2. You can, I guess, achieve what you would like to achieve by using a macro similar to that below:
macro random_oper(depth, fs, ts)
operations = eval(fs)
oper = operations[rand(1:length(operations))]
terminals = eval(ts)
ts = terminals[rand(1:length(terminals), 2)]
ex = :($oper($ts...))
for d in 2:depth
oper = operations[rand(1:length(operations))]
t = terminals[rand(1:length(terminals))]
ex = :($oper($ex, $t))
end
return ex
end
which will give the following, for instance:
Main> #macroexpand #random_oper(1, [+, -, /], [1,2,3])
:((-)([3, 3]...))
Main> #macroexpand #random_oper(2, [+, -, /], [1,2,3])
:((+)((-)([2, 3]...), 3))
Thanks Arda for the thorough response! This helped, but part of me thinks there may be a better way to do this as it seems too roundabout. Since I am writing a genetic program, I will need to create 500 of these ASTs, all with random functions and terminals from a set of allowed functions and terminals (fs and ts in the code). I will also need to test each function with 20 different values of x and v.
In order to accomplish this with the information you have given, I have come up with the following macro:
macro create_function(defs)
for name in eval(defs)
ex = quote
function $(Symbol(name))(x,v)
fs = [:+, :-, :DIV, :GT]
ts = [x,v,-1]
return create_single_full_tree(4, fs, ts)
end
end
eval(ex)
end
end
I can then supply a list of 500 random function names in my main() function, such as ["func1, func2, func3,.....". Which I can eval with any x and v values in my main function. This has solved my issue, however, this seems to be a very roundabout way of doing this, and may make it difficult to evolve each AST with each iteration.

Different ways to declare Enum datatype in Julia-Lang

Is using #enum the only way to declare Julia Enum datatype? If so why?
It's the only (easy) way, yes. The answer, as often (or, rather, always) in Julia, can be found by looking at the source code.
This can be a bit scary at first, but you get used to it after a while!
Normally to create an object of a given type, you call the type's constructor.
So you might expect to be able to do
Enum(...)
and create an object of type Enum.
In this case, however, Enum is an abstract type, so you cannot do that.
What does #enum do, then? The example from the manual is
julia> #enum FRUIT apple=1 orange=2 kiwi=3
This actually creates a completely new type, called FRUIT, that is a subtype of Enum, and objects of that type called apple, orange and kiwi, which are converted to those numbers by calling Int(apple) etc. This is done by generating the Julia code to do so, inside the macro.
In principle, you could, yourself, do all the work that the macro does, but the macro is there to make our life easier!
Since Julia 0.7, enums can be defined using #enum macros as you mentioned but can also be used with a begin block:
julia> #enum Fruit begin
apple = 1
orange = 2
kiwi = 3
end
julia> Fruit
Enum Fruit:
apple = 1
orange = 2
kiwi = 3
julia> apple
apple::Fruit = 1
julia> orange
orange::Fruit = 2
julia> kiwi
kiwi::Fruit = 3
julia> Int(orange)
2
julia> string(orange)
"orange"
Enums can also be defined with this begin block without specifying values (in such a case values are starting with 0, not 1)
julia> #enum Fruit begin
apple
orange
kiwi
end
julia> Fruit
Enum Fruit:
apple = 0
orange = 1
kiwi = 2
...and then there is the type abusing way of doing it; that I stumbled upon while thinking of types as names for sets:
typealias Sunday Val{:Sunday}
typealias Monday Val{:Monday}
typealias Tuesday Val{:Tuesday}
typealias Wednesday Val{:Wednesday}
typealias Thursday Val{:Thursday}
typealias Friday Val{:Friday}
typealias Saturday Val{:Saturday}
typealias Days Union{
Type{Sunday},
Type{Monday},
Type{Tuesday},
Type{Wednesday},
Type{Thursday},
Type{Friday},
Type{Saturday}
}
function daynumber(d::Days)
if d == Sunday return 0
elseif d == Monday return 1
elseif d == Tuesday return 2
elseif d == Wednesday return 3
elseif d == Thursday return 4
elseif d == Friday return 5
elseif d == Saturday return 6
end
-1
end
> daynumber(Friday)
5
> daynumber(:Friday)
> MethodError:`daynumber` has no method matching (::Symbol)
Note that the use of symbols is just a pretty bit of reflection, and is completely superfluous. You can put anything in there, and then recover it through type inspection
> x = Saturday.parameters[1]
:Saturday
> typeof(x)
Symbol
> eval(x) == Saturday
true
I am pretty sure the documentation explicitly recommends against this. Nevertheless #code_warntype does not particularly balk at this construct.
In set theoretic terms, each day alias is a singleton type, and thus a name for a set of exactly one element. The "Union" of "Type"s is then the set theoretic union of single element sets, forming the enumerated finite set type.
...and yet more type mangling ways of doing enumeration
abstract Fruits{N} <: Enum
immutable Apples <: Fruits{1} end
immutable Oranges <: Fruits{2} end
immutable Bananas <: Fruits{3} end
fruitsalad{N}(x::Fruits{N}) = N
> anorange = Oranges()
> fruitsalad(anorange)
2
Again #code_warntype does not seem to mind this at all. Finally one last technique that also provides a protected namespace for the enumeration
immutable Fruits{N} <: Enum
apples::Fruits
bananas::Fruits
oranges::Fruits
function Base.call(::Type{Fruits})
new{"anything"}(
Fruits{"crunchy"}(),
Fruits{"mushy"}(),
Fruits{"tangy"}()
)
end
function Base.call{N}(::Type{Fruits{N}})
if N != "crunchy" && N != "mushy" && N != "tangy"
error("Invalid enumeration parameter")
end
new{N}()
end
end
fruitsalad{N}(x::Fruits{N}) = N
> fruitsalad(Fruits().apples)
"crunchy"
In this last example to access the convenience property that gives an instance of a specific fruit we had to first instantiate the general fruits type. In the parlance of object oriented design Julia has no sense of static properties of types. The properties of types are only available once an explicit instance of that type has been constructed. The thought being that anything that is statically available about a particular type should be represented in some form of method overloading.
You can use SuperEnum (author here) to do some cool stuff:
using Pkg
Pkg.add("https://github.com/kindlychung/SuperEnum.jl")
using SuperEnum
#se Vehical plane train car truck
julia> Vehical.VehicalEnum
Enum Main.Vehical.VehicalEnum:
plane = 0
train = 1
car = 2
truck = 3
julia> Vehical.car
car::VehicalEnum = 2
julia> #se Lang zh=>"中文"*"Chinese" en=>"English" ja=>"日本语"
Main.Lang
julia> string(Lang.zh)
"中文Chinese"

Resources