list-comprehension-like expansion inside Julia expression? - julia

Is it possible to have something like a list comprehension for building complicated expressions in Julia?
For example, say I have some symbols and types, and want to build a type from them. Right now, I have to do something like.
syms = [:a, :b, :c]
typs = [Int, Float32, Char]
new_type = :(type Foo end)
new_type.args[3].args = [:($sym::$typ) for (sym,typ) in zip(syms,typs)]
This works in that new_type is an expression containing
:(type Foo
a::Int64
b::Float32
c::Char
end)
But building complicated expressions like this is both extremely error prone (because you have be intimately knowledgeable with the Expr data type in order to know, eg that the expressions for the data types of the tuple have to be stored in new_type.args[3].args) and also extremely brittle in that any change to the AST of the expression being built would mean having to change where/how every sub-expression is stored.
So is there a way to do something like
:(type Foo
$(sym::typ for (sym,typ) in zip(syms,typs))
end)
and end up with the same expression as above?

Yes, you can splat arrays of expressions directly into the syntax:
julia> :(type Foo
$([:($sym::$typ) for (sym,typ) in zip(syms,typs)]...)
end)
:(type Foo # none, line 2:
a::Int64
b::Float32
c::Char
end)

Related

Julia DiGraph with user-defined object as nodes

In Julia, I can easily create a graph using the very nicely written Graphs.jl library:
julia> using Graphs
julia> g = SimpleDiGraph(2)
{2, 0} directed simple Int64 graph
julia> add_edge!(g, 1,2)
true
julia> add_edge!(g, 2, 1)
true
However, I cannot seem to make the nodes anything other than integers. To wit, I would like to do something like this:
julia> using Graphs
julia> abstract type Foo end
julia> s = SimpleDiGraph(typeof(Foo)) # doesn't work.
julia> mutable struct Bar <: Foo
b::Bool
end
julia> mutable struct Baz <: Foo
b::Bool
end
julia> bar = Bar(true)
Bar(true)
julia> baz = Baz(false)
Baz(false)
julia> add_edge!(g, bar, baz) # Again, doesn't work.
How can I make the nodes a custom type?
Note: I have tried the following workaround, as it appears that only integral types are allowed in the nodes:
julia> add_edge!(g, Int64(pointer_from_objref(bar)), Int64(pointer_from_objref(baz)))
but this one, although not throwing an exception, returns false.
In the Graphs.jl tutorial, it is stated that "integers, and only integers, may be used for describing vertices." So my first goal is out of the question (although it may be possible in, say, MetaGraphs.jl). The recommendation is to use the integer value in the vertex as an index into a vector and store the objects in the vector.
Moreover, as it is stated that the goal is to index into a vector, the integers must be contiguous, and hence my second idea of using the address of the object is also not feasible.
However, using the graph to index into a vector is a perfectly acceptable solution for me.

Is there a Collection supertype between Set and Array? If not, how can a function be polymorphic over both Sets and Arrays (for iteration)?

Is there in Julia a Collection type from which both Set and Array derive ?
I have both:
julia> supertype(Array)
DenseArray{T,N} where N where T
julia> supertype(DenseArray)
AbstractArray{T,N} where N where T
julia> supertype(AbstractArray)
Any
And:
julia> supertype(Set)
AbstractSet{T} where T
julia> supertype(AbstractSet)
Any
What I try to achieve is to write function that can take both Array or Set as argument, because the type of collection doesn't matter as long as I can iterate over it.
function(Collection{SomeOtherType} myCollection)
for elem in myCollection
doSomeStuff(elem)
end
end
No, there is no Collection type, nor is there an Iterable one.
In theory, what you ask can be accomplished through traits, which you can read about elsewhere. However, I would argue that you should not use traits here, and instead simply refrain from restricting the type of your argument to the function. That is, instead of doing
foo(x::Container) = bar(x)
, do
foo(x) = bar(x)
There will be no performance difference.
If you want to restrict your argument types you could create a type union:
julia> ty = Union{AbstractArray,AbstractSet}
Union{AbstractSet, AbstractArray}
julia> f(aarg :: ty) = 5
f (generic function with 1 method)
This will work on both sets and arrays
julia> f(1:10)
5
julia> f(rand(10))
5
julia> f(Set([1,2,5]))
5
But not on numbers, for example
julia> f(5)
ERROR: MethodError: no method matching f(::Int64)
Closest candidates are:
f(::Union{AbstractSet, AbstractArray}) at REPL[2]:1

Accessing values in expression using a macro

I'm wondering whether it's possible to define a macro that can modify the values of an expression only if the values are of a specific type?
Here's a minimal example:
type Special
x::Int
end
f1(s, n::Special) = println("f1", s, n)
f2(s, n::Special) = println("f2", s, n)
x1 = Special(3)
x2 = Special(5)
expr = :(
f1("this is f1", x1),
f2("this is f2", x2)
)
Now a macro might be able to examine the values of the arguments to the functions, determine that x1 and x2 are of type Special, run some function to modify their values, say by changing 3 to 4 and 5 to 2 (it might involve comparing two values), then pass the expression back to the caller. The final result would be equivalent to calling:
f1("this is f1", 4)
f2("this is f2", 2)
I found that it's possible to access the values in a macro via:
eval(eval(filter(x -> typeof(eval(x)) == Special, expr.args[1].args))[1]).x
=> 3
but although this works it looks wrong, and I'm might either be doing it wrong or trying to do something too way out...
No, you should never try to check types or values inside macros. Using eval to figure out the type or value of something in a macro may work in very limited situations, but it'll break in almost every real use. Instead, just have the macro insert a call to a generic function — that's where Julia excels at picking apart types (as method dispatch) and values (within the method):
munge_special(x::Special) = Special(x.x + 42)
munge_special(x) = x
macro do_something_special(x)
return :(munge_special($(esc(x))))
end
julia> #do_something_special Special(2)
Special(44)
julia> #do_something_special 3
3

Define multiple variables of same type in one line in Julia?

In C++ you can write:
double x = 0.0, y = 0.0, z = 0.0;
instead of:
double x = 0.0; double y = 0.0; double z = 0.0;
to define three variables of the same type, double in this example.
In Julia, the analogous would be:
x::Float64 = 0.0, y::Float64 = 0.0, z::Float64 = 0.0
Is there a syntactic sugar, like the above for C++, allowing me to omit the the three type specifiers, replacing them with just one when they are all the same?
Note: I know I can just write
x = y = z = 0.0
and let Julia infer the type. This is not what I want. I want to state explicitly that the types of x,y,z are constant.
Actually, the following doesn't declare those as variables in Julia
x::Float64, y::Float64, z::Float64
At least based on my investigation, you'd need something more like:
local x::Float64 = zero(Float64)
local y::Float64 = zero(Float64)
local z::Float64 = zero(Float64)
There is no syntactic sugar for doing that, AFAIK, forcing a particular type is not that common or necessary in Julia.
EDIT: I should be clearer, esp. after #dpsanders comment below, that I don't feel this is something that should normally ever be done in Julia, and even though type assertions can be useful for performance reasons in limited cases, using them so much you want syntactic sugar or a macro to shorten the code seems like a case of premature optimization.
If you really want to do that a lot, you can write a macro to take care of it for you, like the following:
macro dcl(T, args...)
r = quote end
for var in args
push!(r.args, :( local $(esc(var))::$T = zero($T)) )
end
r
end
I'm sure some Julian with more macro-"fu" than me can do a much better job, but it does seem to work, in my testing, i.e.:
#dcl(Float64, x, y, z)
Edit: The following demonstrates that the syntax doesn't declare any variables at all, just constrains the types of variables taken from the enclosing scope:
julia> function testdecl()
x::Float64, y::Int, z::String
println(x, y, z)
end
testdecl (generic function with 1 method)
julia> #code_warntype testdecl()
Variables:
#self#::#testdecl
Body:
begin # REPL[107], line 2:
(Core.typeassert)(Main.x,Main.Float64)::Float64
(Core.typeassert)(Main.y,Main.Int)::Int64
(Core.typeassert)(Main.z,Main.String)::String # REPL[107], line 4:
return (Main.println)(Main.x,Main.y,Main.z)::Any
end::Any
I'm wondering if there may also be a bug here, looking at the following:
julia> function testdcl()
println(y)
x::Float64, y::String
x = 1
println(x)
end
testdcl (generic function with 1 method)
julia> y = 1.234
1.234
julia> testdcl()
1.234
ERROR: UndefVarError: x not defined
in testdcl() at ./REPL[131]:2
in eval(::Module, ::Any) at ./boot.jl:226
julia> #code_warntype testdcl()
Variables:
#self#::#testdcl
x::Union{}
Body:
begin # REPL[131], line 1:
(Main.println)(Main.y)::Any # REPL[131], line 2:
(Core.tuple)((Core.typeassert)(x::Union{},Main.Float64)::Union{},(Core.typeassert)(Main.y,Main.String)::String)::Union{}
end::Union{}
Variables don't have types in Julia. Values have types.
But you can use const to ensure that a variable cannot be assigned a value of a different type. Here's one way, though it may not be the most concise:
const a, b, c = (zeros(Float64, 3)...)
Edit: But are you sure this is what you really want? Julia is a dynamically typed language, and what you're asking is something you normally do in a statically typed language.

Constrain Vector of DataType to be of particular abstract type

Is it possible to create a function which takes a ::Vector{DataType} but constrains all members to be types which inherit from a particular abstract type?
Example:
# this code works but does not constrain the type
function foo{D<:DataType}(arr::Vector{D})
...
end
# this is kind of the syntax I'd like, but Type{Int} !<: Type{Integer}
function bar{D<:Type{Integer}}(arr::Vector{D})
...
end
Thank you
I'm not sure this is possible (cleanly) with a compile-time check. You could consider using a Val type, but this will be messy and probably slower. I would just make it a run-time check:
julia> function bar{T}(::Type{T}, arr::Vector{DataType})
if all(x->x<:T, arr)
println("ok")
else
println("bad")
end
end
bar (generic function with 1 method)
julia> bar(Integer, [Int,Int32])
ok
julia> bar(Integer, [Int,Int32,Float64])
bad
What's your use case for this? There might be an alternative that's cleaner.
just to clarify why function bar{T<:Integer}(arr::Vector{Type{T}}) = println(arr) won't work.
in a nutshell, this is because julia's type parameter is invariant.
firstly, take a look a OP's definition:
function bar{D<:Type{Integer}}(arr::Vector{D})
...
end
the problem here, as OP pointed out, is Type{Int} !<: Type{Integer}.
the reason is that Type{T} is a parametric type, even though Int <: Integer, we don't have Type{Int} <: Type{Integer}.
"bearing in mind"(yes, that's sarcasm) that the type parameter of julia's parametric type is invariant, i suggested to use this version:
function bar{T<:Integer}(arr::Vector{Type{T}})
...
end
it seems good! this time i'm using T instead of Type{T}, so i won't fall into the pit of Type{Int} !<: Type{Integer}.
however, as i wrote down that comment, i had just fallen into another pit -- Vector{} is also a parametric type. even if DataType <: Type{T}, we don't have Vector{DataType} <: Vector{Type{T}}.
as a result, a error will occur when running bar([Int64, Int32]).
julia> bar([Int64, Int32])
ERROR: MethodError: `bar` has no method matching bar(::Array{DataType,1})
julia> methods(bar)
bar{T<:Integer}(arr::Array{Type{T<:Integer},1})
julia> [Int64, Int32]
2-element Array{DataType,1}:
Int64
Int32
EDIT:
hmm, it seems that this problem is not that simple. the key point here is the mysterious relationship between DataType and Type{T}.
# we know that DataType is a subtype of Type{T},
# where T is a `TypeVar` \in [Bottom, Any].
julia> subtypes(Type)
3-element Array{Any,1}:
DataType
TypeConstructor
Union
julia> S = TypeVar(:S, Union{}, Integer, true)
S<:Integer
# but Type{S} is not a subtype of DataType
julia> Type{S} <: DataType
false
julia> Type{S} <: Type
true
i therefore conclude that it's impossible to make ::Vector{DataType} work in your case.
DataType has NO type parameters.
the below definition won't work, which seems like a bug.
julia> a = Array(Type{S}, 2)
2-element Array{Type{S<:Integer},1}:
#undef
#undef
julia> a[1] = Type{Int32} # or Int32
Type{Int32}
julia> a[2] = Type{Float32} # or Float32
Type{Float32}
julia> a
2-element Array{Type{S<:Integer},1}:
Type{Int32}
Type{Float32}
i'll post a question about this strange behavior. #Mageek

Resources