How do you define non-commutable variables? I'm new to julia and have been reading the doc and found nothing so far.
By non-commutable I mean if variables (say symbols) a and b are multiplied. ab =\= ba
About commutativity: Julia does not assume that a*b is the same as b*a (example: let a and b be matrices). However, methods of the function *(a,b) for some specific combinations of types act in a commutative manner, such as when a and b are numbers.
Since you are talking about symbols, I guess that want to work with a symbolic representation of expressions. There's at least two ways to go about this:
Work with the AST of an expression.
You can quote any expression by :(expr) (sometimes :expr is enough, depends on operator precedence):
julia> ex = :(x*y*z)
:(x * y * z)
julia> typeof(ex)
Expr
Note that the order of the factors has been preserved (though the associativity has not).
You can inspect the Expr by looking at it's head and args fields:
julia> ex.head
:call
julia> ex.args
4-element Array{Any,1}:
:*
:x
:y
:z
The args may be e.g. symbols (of type Symbol), constant values, and other expressions.
This technique works well if you want to write your own macros that process expressions, since the input to a macro is the AST of its arguments.
Define your own types and overload the * function. Example: with
abstract Symbolic
# might as well make these immutable
immutable Sym <: Symbolic
name::Symbol
end
immutable Prod <: Symbolic
x::Symbolic
y::Symbolic
end
# The * function is imported by default.
# Usually, we need to import a function
# from Base before extending it.
*(x::Symbolic, y::Symbolic) = Prod(x, y)
you can do
julia> x, y = Sym(:x), Sym(:y)
(Sym(:x),Sym(:y))
julia> x*y
Prod(Sym(:x),Sym(:y))
Since our *(x::Symbolic, y::Symbolic) function preserves the order of the arguments, we can see it in the created Prod object.
In most languages, the second alternative is your only option. The first alternative is more direct since you don't have to write a new AST framework for yourself, and don't have to execute the expression just to get at it's representation. Which one is more suitable depends on the application, however. If you want to associate different properties with your variables, the second approach seems easier.
Was it something like this that you were looking for?
Related
What type should I specify in Julia for function arguments that can be either scalars or arrays? For instance, in the function below x and y could be e.g. Float64 or Array{Float64}.
function myfun(x, y)
return x .+ y
end
Is there an appropriate type declaration for such variables? Or should I just refrain from declaring the type there (or writing functions that are that generic)?
You can safely refrain from specifying types. This will not have an impact on performance of your code.
However, if you want to explicitly specify the type restriction you provided do (this is mostly useful to make sure your function is called with proper arguments and fail fast if it is not):
function myfun(x::Union{Float64, Array{Float64}},
y::Union{Float64, Array{Float64}})
return x .+ y
end
However, most likely you will rather want the following signature:
function myfun(x::Union{AbstractFloat, AbstractArray{<:AbstractFloat}},
y::Union{AbstractFloat, AbstractArray{<:AbstractFloat}})
return x .+ y
end
which says you accept any scalar float or any array of floats (not necessarily only Float64 and Array). This is more flexible, as e.g. you can accept views then or other floats (BigFloat or Float32) if you prefer to switch precision of your computations. Such a signature clearly signals your users what types of inputs you expect them to pass to myfun while remaining flexible.
I recommend this as being overly restrictive (Union{Float64, Array{Float64}}), while accepted by the compiler, usually leads to problems later when you start using your function with various input types.
I have two methods with the same name and same amount of "normal" arguments. However, they differ in the number of keyword arguments (which are all non-optional).
I think the following minimal example illustrates my point well:
julia> f(a; b) = a+b
f (generic function with 1 method)
julia> f(a; b, c)= a+b+c
f (generic function with 1 method)
julia> f(1; b=1)
ERROR: UndefKeywordError: keyword argument c not assigned
Stacktrace:
[1] top-level scope
# REPL[72]:1
julia> f(1; b=1, c=2)
4
The second line of output ("f (generic function with 1 method)") already shows that Julia doesn't understand what I want - it should be 2 methods (which it would be e.g. if I wrote f(a, b; c) = a+b+c).
I already found this in the Julia discourse forum, which seems to explain why this doesn't work for multiple dispatch. However, in my case not the types but the counts differ, so I hope that there is a (neat) solution.
Regarding why I want this: I have a function that makes some calculations (to be precise it calculates Stark curves) for different molecules. This function needs to act differently for different molecule types (to be precise different symmetries) and also takes different arguments (different quantum numbers).
So that's why I need multiple dispatch - but why keyword arguments, you might ask. That is because I create the quantum numbers using distributions and passing them as NamedTuple in connection with ....
I'd like to not change this behaviour, but maybe you were curious to why I would need something like this.
In your example, you can reuse the keyword arguments as positional arguments to make f(1; b=1) work, but it won't behave like true keyword dispatch. For one, f(1; c=2) would call _f(a,b).
function f(a; b=missing, c=missing)
_f(a, skipmissing((b, c))...)
end
_f(a,b,c) = a+b+c
_f(a,b) = a+b
Not sure if this is applicable to the actual use case you described, though, things can be harder to reorder than b and c, and as a comment noted, a NamedTuple (which is ordered) is already dispatchable.
I try to understand typing in Julia and encounter the following problem with Array. I wrote a function bloch_vector_2d(Array{Complex,2}); the detailed implementation is irrelevant. When calling, here is the complaint:
julia> bloch_vector_2d(rhoA)
ERROR: MethodError: no method matching bloch_vector_2d(::Array{Complex{Float64},2})
Closest candidates are:
bloch_vector_2d(::Array{Complex,2}) at REPL[56]:2
bloch_vector_2d(::StateAB) at REPL[54]:1
Stacktrace:
[1] top-level scope at REPL[64]:1
The problem is that an array of parent type is not automatically a parent of an array of child type.
julia> Complex{Float64} <: Complex
true
julia> Array{Complex{Float64},2} <: Array{Complex,2}
false
I think it would make sense to impose in julia that Array{Complex{Float64},2} <: Array{Complex,2}. Or what is the right way to implement this in Julia? Any helps or comments are appreciated!
This issue is discussed in detail in the Julia Manual here.
Quoting the relevant part of it:
In other words, in the parlance of type theory, Julia's type parameters are invariant, rather than being covariant (or even contravariant). This is for practical reasons: while any instance of Point{Float64} may conceptually be like an instance of Point{Real} as well, the two types have different representations in memory:
An instance of Point{Float64} can be represented compactly and efficiently as an immediate pair of 64-bit values;
An instance of Point{Real} must be able to hold any pair of instances of Real. Since objects that are instances of Real can be of arbitrary size and structure, in practice an instance of Point{Real} must be represented as a pair of pointers to individually allocated Real objects.
Now going back to your question how to write a method signature then you have:
julia> Array{Complex{Float64},2} <: Array{<:Complex,2}
true
Note the difference:
Array{<:Complex,2} represents a union of all types that are 2D arrays whose eltype is a subtype of Complex (i.e. no array will have this exact type).
Array{Complex,2} is a type that an array can have and this type means that you can store Complex values in it that can have mixed parameter.
Here is an example:
julia> x = Complex[im 1im;
1.0im Float16(1)im]
2×2 Array{Complex,2}:
im 0+1im
0.0+1.0im 0.0+1.0im
julia> typeof.(x)
2×2 Array{DataType,2}:
Complex{Bool} Complex{Int64}
Complex{Float64} Complex{Float16}
Also note that the notation Array{<:Complex,2} is the same as writing Array{T,2} where T<:Complex (or more compactly Matrix{T} where T<:Complex).
This is more of a comment, but I can't hesitate posting it. This question apprars so often. I'll tell you why that phenomenon must arise.
A Bag{Apple} is a Bag{Fruit}, right? Because, when I have a JuicePress{Fruit}, I can give it a Bag{Apple} to make some juice, because Apples are Fruits.
But now we run into a problem: my fruit juice factory, in which I process different fruits, has a failure. I order a new JuicePress{Fruit}. Now, I unfortunately get delivered a replacement JuicePress{Lemon} -- but Lemons are Fruits, so surely a JuicePress{Lemon} is a JuicePress{Fruit}, right?
However, the next day, I feed apples to the new press, and the machine explodes. I hope you see why: JuicePress{Lemon} is not a JuicePress{Fruit}. On the contrary: a JuicePress{Fruit} is a JuicePress{Lemon} -- I can press lemons with a fruit-agnostic press! They could have sent me a JuicePress{Plant}, though, since Fruits are Plants.
Now we can get more abstract. The real reason is: function input arguments are contravariant, while function output arguments are covariant (in an idealized setting)2. That is, when we have
f : A -> B
then I can pass in supertypes of A, and end up with subtypes of B. Hence, when we fix the first argument, the induced function
(Tree -> Apple) <: (Tree -> Fruit)
whenever Apple <: Fruit -- this is the covariant case, it preserves the direction of <:. But when we fix the second one,
(Fruit -> Juice) <: (Apple -> Juice)
whenever Fruit >: Apple -- this inverts the diretion of <:, and therefore is called contravariant.
This carries over to other parametric data types, since there, too, you usually have "output-like" parameters (as in the Bag), and "input-like" parameters (as with the JuicePress). There can also be parameters that behave like neither (e.g., when they occur in both fashions) -- these are then called invariant.
There are now two ways in which languages with parametric types solve this problem. The, in my opinion, more elegant one is to mark every parameter: no annotation means invariant, + means covariant, - means contravariant (this has technical reasons -- those parameters are said to occur in "positive" and "negative position"). So we had the Bag[+T <: Fruit], or the JuicePress[-T <: Fruit] (should be Scala syntax, but I haven't tried it). This makes subtyping more complicated, though.
The other route to go is what Julia does (and, BTW, Java): all types are invariant1, but you can specify upper and lower unions at the call site. So you have to say
makejuice(::JoicePress{>:T}, ::Bag{<:T}) where {T}
And that's how we arrive at the other answers.
1Except for tuples, but that's weird.
2This terminology comes from category theory. The Hom-functor is contravariant in the first, and covariant in the second argument. There's an intuitive realization of subtyping through the "forgetful" functor from the category Typ to the poset of Types under the <: relation. And the CT terminology in turn comes from tensors.
While the "how it works" discussion has been done in the another answer, the best way to implement your method is the following:
function bloch_vector_2d(a::AbstractArray{Complex{T}}) where T<:Real
sum(a) + 5*one(T) # returning something to see how this is working
end
Now this will work like this:
julia> bloch_vector_2d(ones(Complex{Float64},4,3))
17.0 + 0.0im
I try to understand typing in Julia and encounter the following problem with Array. I wrote a function bloch_vector_2d(Array{Complex,2}); the detailed implementation is irrelevant. When calling, here is the complaint:
julia> bloch_vector_2d(rhoA)
ERROR: MethodError: no method matching bloch_vector_2d(::Array{Complex{Float64},2})
Closest candidates are:
bloch_vector_2d(::Array{Complex,2}) at REPL[56]:2
bloch_vector_2d(::StateAB) at REPL[54]:1
Stacktrace:
[1] top-level scope at REPL[64]:1
The problem is that an array of parent type is not automatically a parent of an array of child type.
julia> Complex{Float64} <: Complex
true
julia> Array{Complex{Float64},2} <: Array{Complex,2}
false
I think it would make sense to impose in julia that Array{Complex{Float64},2} <: Array{Complex,2}. Or what is the right way to implement this in Julia? Any helps or comments are appreciated!
This issue is discussed in detail in the Julia Manual here.
Quoting the relevant part of it:
In other words, in the parlance of type theory, Julia's type parameters are invariant, rather than being covariant (or even contravariant). This is for practical reasons: while any instance of Point{Float64} may conceptually be like an instance of Point{Real} as well, the two types have different representations in memory:
An instance of Point{Float64} can be represented compactly and efficiently as an immediate pair of 64-bit values;
An instance of Point{Real} must be able to hold any pair of instances of Real. Since objects that are instances of Real can be of arbitrary size and structure, in practice an instance of Point{Real} must be represented as a pair of pointers to individually allocated Real objects.
Now going back to your question how to write a method signature then you have:
julia> Array{Complex{Float64},2} <: Array{<:Complex,2}
true
Note the difference:
Array{<:Complex,2} represents a union of all types that are 2D arrays whose eltype is a subtype of Complex (i.e. no array will have this exact type).
Array{Complex,2} is a type that an array can have and this type means that you can store Complex values in it that can have mixed parameter.
Here is an example:
julia> x = Complex[im 1im;
1.0im Float16(1)im]
2×2 Array{Complex,2}:
im 0+1im
0.0+1.0im 0.0+1.0im
julia> typeof.(x)
2×2 Array{DataType,2}:
Complex{Bool} Complex{Int64}
Complex{Float64} Complex{Float16}
Also note that the notation Array{<:Complex,2} is the same as writing Array{T,2} where T<:Complex (or more compactly Matrix{T} where T<:Complex).
This is more of a comment, but I can't hesitate posting it. This question apprars so often. I'll tell you why that phenomenon must arise.
A Bag{Apple} is a Bag{Fruit}, right? Because, when I have a JuicePress{Fruit}, I can give it a Bag{Apple} to make some juice, because Apples are Fruits.
But now we run into a problem: my fruit juice factory, in which I process different fruits, has a failure. I order a new JuicePress{Fruit}. Now, I unfortunately get delivered a replacement JuicePress{Lemon} -- but Lemons are Fruits, so surely a JuicePress{Lemon} is a JuicePress{Fruit}, right?
However, the next day, I feed apples to the new press, and the machine explodes. I hope you see why: JuicePress{Lemon} is not a JuicePress{Fruit}. On the contrary: a JuicePress{Fruit} is a JuicePress{Lemon} -- I can press lemons with a fruit-agnostic press! They could have sent me a JuicePress{Plant}, though, since Fruits are Plants.
Now we can get more abstract. The real reason is: function input arguments are contravariant, while function output arguments are covariant (in an idealized setting)2. That is, when we have
f : A -> B
then I can pass in supertypes of A, and end up with subtypes of B. Hence, when we fix the first argument, the induced function
(Tree -> Apple) <: (Tree -> Fruit)
whenever Apple <: Fruit -- this is the covariant case, it preserves the direction of <:. But when we fix the second one,
(Fruit -> Juice) <: (Apple -> Juice)
whenever Fruit >: Apple -- this inverts the diretion of <:, and therefore is called contravariant.
This carries over to other parametric data types, since there, too, you usually have "output-like" parameters (as in the Bag), and "input-like" parameters (as with the JuicePress). There can also be parameters that behave like neither (e.g., when they occur in both fashions) -- these are then called invariant.
There are now two ways in which languages with parametric types solve this problem. The, in my opinion, more elegant one is to mark every parameter: no annotation means invariant, + means covariant, - means contravariant (this has technical reasons -- those parameters are said to occur in "positive" and "negative position"). So we had the Bag[+T <: Fruit], or the JuicePress[-T <: Fruit] (should be Scala syntax, but I haven't tried it). This makes subtyping more complicated, though.
The other route to go is what Julia does (and, BTW, Java): all types are invariant1, but you can specify upper and lower unions at the call site. So you have to say
makejuice(::JoicePress{>:T}, ::Bag{<:T}) where {T}
And that's how we arrive at the other answers.
1Except for tuples, but that's weird.
2This terminology comes from category theory. The Hom-functor is contravariant in the first, and covariant in the second argument. There's an intuitive realization of subtyping through the "forgetful" functor from the category Typ to the poset of Types under the <: relation. And the CT terminology in turn comes from tensors.
While the "how it works" discussion has been done in the another answer, the best way to implement your method is the following:
function bloch_vector_2d(a::AbstractArray{Complex{T}}) where T<:Real
sum(a) + 5*one(T) # returning something to see how this is working
end
Now this will work like this:
julia> bloch_vector_2d(ones(Complex{Float64},4,3))
17.0 + 0.0im
I am puzzled by the following results of typeof in the Julia 1.0.0 REPL:
# This makes sense.
julia> typeof(10)
Int64
# This surprised me.
julia> typeof(function)
ERROR: syntax: unexpected ")"
# No answer at all for return example and no error either.
julia> typeof(return)
# In the next two examples the REPL returns the input code.
julia> typeof(in)
typeof(in)
julia> typeof(typeof)
typeof(typeof)
# The "for" word returns an error like the "function" word.
julia> typeof(for)
ERROR: syntax: unexpected ")"
The Julia 1.0.0 documentation says for typeof
"Get the concrete type of x."
The typeof(function) example is the one that really surprised me. I expected a function to be a first-class object in Julia and have a type. I guess I need to understand types in Julia.
Any suggestions?
Edit
Per some comment questions below, here is an example based on a small function:
julia> function test() return "test"; end
test (generic function with 1 method)
julia> test()
"test"
julia> typeof(test)
typeof(test)
Based on this example, I would have expected typeof(test) to return generic function, not typeof(test).
To be clear, I am not a hardcore user of the Julia internals. What follows is an answer designed to be (hopefully) an intuitive explanation of what functions are in Julia for the non-hardcore user. I do think this (very good) question could also benefit from a more technical answer provided by one of the more core developers of the language. Also, this answer is longer than I'd like, but I've used multiple examples to try and make things as intuitive as possible.
As has been pointed out in the comments, function itself is a reserved keyword, and is not an actual function istself per se, and so is orthogonal to the actual question. This answer is intended to address your edit to the question.
Since Julia v0.6+, Function is an abstract supertype, much in the same way that Number is an abstract supertype. All functions, e.g. mean, user-defined functions, and anonymous functions, are subtypes of Function, in the same way that Float64 and Int are subtypes of Number.
This structure is deliberate and has several advantages.
Firstly, for reasons I don't fully understand, structuring functions in this way was the key to allowing anonymous functions in Julia to run just as fast as in-built functions from Base. See here and here as starting points if you want to learn more about this.
Secondly, because each function is its own subtype, you can now dispatch on specific functions. For example:
f1(f::T, x) where {T<:typeof(mean)} = f(x)
and:
f1(f::T, x) where {T<:typeof(sum)} = f(x) + 1
are different dispatch methods for the function f1
So, given all this, why does, e.g. typeof(sum) return typeof(sum), especially given that typeof(Float64) returns DataType? The issue here is that, roughly speaking, from a syntactical perspective, sum needs to serves two purposes simultaneously. It needs to be both a value, like e.g. 1.0, albeit one that is used to call the sum function on some input. But, it is also needs to be a type name, like Float64.
Obviously, it can't do both at the same time. So sum on its own behaves like a value. You can write f = sum ; f(randn(5)) to see how it behaves like a value. But we also need some way of representing the type of sum that will work not just for sum, but for any user-defined function, and any anonymous function. The developers decided to go with the (arguably) simplest option and have the type of sum print literally as typeof(sum), hence the behaviour you observe. Similarly if I write f1(x) = x ; typeof(f1), that will also return typeof(f1).
Anonymous functions are a bit more tricky, since they are not named as such. What should we do for typeof(x -> x^2)? What actually happens is that when you build an anonymous function, it is stored as a temporary global variable in the module Main, and given a number that serves as its type for lookup purposes. So if you write f = (x -> x^2), you'll get something back like #3 (generic function with 1 method), and typeof(f) will return something like getfield(Main, Symbol("##3#4")), where you can see that Symbol("##3#4") is the temporary type of this anonymous function stored in Main. (a side effect of this is that if you write code that keeps arbitrarily generating the same anonymous function over and over you will eventually overflow memory, since they are all actually being stored as separate global variables of their own type - however, this does not prevent you from doing something like this for n = 1:largenumber ; findall(y -> y > 1.0, x) ; end inside a function, since in this case the anonymous function is only compiled once at compile-time).
Relating all of this back to the Function supertype, you'll note that typeof(sum) <: Function returns true, showing that the type of sum, aka typeof(sum) is indeed a subtype of Function. And note also that typeof(typeof(sum)) returns DataType, in much the same way that typeof(typeof(1.0)) returns DataType, which shows how sum actually behaves like a value.
Now, given everything I've said, all the examples in your question now make sense. typeof(function) and typeof(for) return errors as they should, since function and for are reserved syntax. typeof(typeof) and typeof(in) correctly return (respectively) typeof(typeof), and typeof(in), since typeof and in are both functions. Note of course that typeof(typeof(typeof)) returns DataType.