In Haskell when you declare a function you are expected to say before hand what you expect as input and output for instance [Integers] → Integer. How does one do this is Julia. I am no Haskell expert by any strech but I really like the discipline. Partly because i miss that my code is starting to get sloppy.
For instance:
b and e are inputs that look like this :source or :type. Its the annotation used by Metagraphs to denote different metadata parts of a node or vertix. Its is not a Symbol.
function get_metadata(g::AbstractMetaGraph, l::Array,b,e)
meta_list = []
for i in 1:nv(g)
if isin(l,get_prop(g,i,b))
push!(meta_list,get_prop(g,i,e))
else
nothing
end
end
return meta_list
end
How can I check the type julia has given this? And how, in this particular case can I specify it?
The output is an array of elements that are infact always Integers. How do I specify that instead of getting any? Do I do that here or at the moment I add the metadata to a node?
3)is there documentation or a book specific to julia, which shows the way how you structure this discipline inside of Julia?
In julia you do not need to annotate the output type as long as it can be inferred from the input types you are good, see this section on type stability. This way you do not need to write the same method over and over again.
I don’t think you should hardcode in the return type. It makes your code inflexible. Let Julia figure out the types, using, for example, an array comprehension:
meta_list = [get_prop(g, i, b) for i in 1:nv(g) if isin(l,get_prop(g,i,b))]
Maybe try to figure out a way to avoid evaluating get_prop twice per iteration.
BTW, the else nothing clause has no effect whatsoever, and should be removed.
It can be done more generically as well. If you know that get_prop(g,i,b) returns the same type of value for every i, (in your case always an Int), you could do:
meta_list = typeof(get_prop(g,1,e))[]
that way the meta_list will contain the elements of the same type returned by get_prop always, even if in some other case they turn out to be floats or something else.
julia> get(x,i) = x[i]
get (generic function with 1 method)
julia> function f(x)
list = typeof(get(x,1))[]
push!(list,get(x,1))
list
end
f (generic function with 1 method)
julia> f([1,1])
1-element Array{Int64,1}:
1
julia> f([1.0,2.0])
1-element Array{Float64,1}:
1.0
Julia doesn’t need argument/return type annotations for speed because the method isn’t the final product, per se. When a function is called, Julia finds the most suitable method for that call’s argument types (dispatch). Then, Julia compiles the method for that call’s argument types (specialization), if it hasn’t already. One method can (and often is) called for different argument types, so one method can have many specializations. If you really want to document that a particular set of argument types results in a particular return type, you can jot it down in a comment or docstring. Just use @code_warntype to make sure it really does.
Annotating the return type just adds a type conversion step, maybe. If you already wrote your method to return that type given the function call’s argument types, then that step is omitted by the compiler. It’s rarely used because as others have demonstrated, this really limits what the method can do. Instead, you might be able to reuse this code for [Floats] → Float, [Bunnies] → Bunny, etc.
Well, first of all it is another approach and it is good to know another approaches, isn’t it? Secondly, they are used in various packages like Transducers.jl and as a next step one can switch from loops to transducers and the problem of initial value will go away by itself, but it is still good to understand how it works under the hood.
And thirdly, typeof construction is making additional call, and depending on the nature of the problem it may be a bad thing.
Consider following example
function g(x)
sleep(5)
x*x
end
function f1(x)
res = InitialValue(push!!)
for el in x
if el == 2
res = push!!(res, g(el))
end
end
return res
end
function f2(x)
list = typeof(g(x[1]))[]
for el in x
if el == 2
push!(list, g(el))
end
end
return list
end
julia> x1 = [1, 2, 3]
julia> @time f1(x1)
5.015492 seconds (6.64 k allocations: 349.130 KiB, 0.21% compilation time)
julia> @time f2(x1)
10.021660 seconds (6.21 k allocations: 325.627 KiB, 0.11% compilation time)
At fourth, push!! is making type expansion, so in the following example
function g(x)
if x < 2
return 1
else
return 2.5
end
end
julia> f1(x1)
1-element Vector{Float64}:
2.5
julia> f2(x1)
ERROR: InexactError: Int64(2.5)
typeof function errors out.
Fifth, beauty in the eye of the beholder, I do not find that push!! is obscuring
Thanks! (I didn’t mean to complain at all, sorry if I gave that impression). I was really curious and by reading the the package page I could not find out what the package did that a straight approach does not.
Indeed, I thought that avoiding that initial call was one of the things, but that seemed at first glance not much to justify someone writing a package. The type expansion is something nice, indeed.
Well, that’s up to you, but to me it’s confusing that it’s there. It’s easy to start wondering what its purpose is, and whether there is some non-obvious effect. I’ve never seen something like that before.