How can I use use something like variant (which is in C++) in Julia?
In C++, I can do something like this
variant<int, float> value;
How can I do the same thing in Julia for a parameter of a function?
function func(??)
# something
end
Sorry if it does not convey what I want to do.
You can use union types to do the same.
function func(x::Union{Int, AbstractFloat})
x + 1
end
Note that C++ std::variant is a tagged union, so you can always ask "which" variant it is, whereas a Julia union is a proper set union. Pragmatically, that means that in std::variant<int, int>, you can distinguish between a "left int" and a "right int", but a Union{Int, Int} really is just an Int. There's no way to tell which "side" it came from. To emulate the exact tagged union behavior, you'll need to make some custom structs to represent each case.
struct LeftInt
value :: Int
end
struct RightInt
value :: Int
end
function func(x::Union{LeftInt, RightInt})
# ... Check whether x is a LeftInt or not ...
end
but, in Julia, you often don't need this, and if you do then it's generally smarter to work with generic functions to do your dispatching anyway.
In Julia, you can just write
function func(value)
# something
end
and the compiler will then automatically compile for you separate type-specific method of this untyped function whenever you call it with another type.
In Julia, unlike in C++, you usually don't provide a parameter type at all, and you instead let the compiler figure out the types for you. Any parameter types that you provide are just assertions, or help to dispatch to the right method in case you want to provide different implementations for different types yourself.
Related
I am new to Julia. Have a quick question.
In Fortran, we can do
implicit none
Therefore whenever we use a variable, we need to define it first. Otherwise it will give error.
In Julia, I want to do the same time thing. I want to define the type of each variable first, like Float64, Int64, etc. So that I wish that Julia no longer need to automatically do type conversion, which may slow down the code.
Because I know if Julia code looks like Fortran it usually runs fast.
So, in short, is there a way in Julia such that, I can force it to only use variables whose types has been defined, and otherwise give me an error message? I just want to be strict.
I just wanted to define all the variables first, then use them. Just like Fortran does.
Thanks!
[Edit]
As suggested by the experts who answers the questions, thank you very much! I know that perhaps in Julia there is no need to manually define each variables one by one. Because for one thing, if I do that, the code will become just like Fortran, and it can be very very long especially if I have many variables.
But if I do not define the type of the each variables, is Julia smart enough to know the type? Will it do some conversions which may slow down the code?
Also, even if there is no need to define variables one by one, is there in some situations we may have to manually define the type manually?
No, this is not possible* as such. You can, however, add type annotations at any point in your code, which will raise an error if the variable is not of the expected type:
julia> i = 1
1
julia> i::Int64
1
julia> i = 1.0
1.0
julia> i::Int64
ERROR: TypeError: in typeassert, expected Int64, got a value of type Float64
Stacktrace:
[1] top-level scope
# REPL[4]:1
julia> i=0x01::UInt8
0x01
*Julia is a dynamically-typed language, though there are packages such as https://github.com/aviatesk/JET.jl for static type-checking (i.e., no type annotations required) and https://github.com/JuliaDebug/Cthulhu.jl for exploring Julia's type inference system.
Strict type declaration is not how you achieve performance in Julia.
You achieve the performance by type stability.
Moreover declaring the types is usually not recommended because it decreases interoperability.
Consider the following function:
f(x::Int) = rand() < 4+x ? 1 : "0"
The types of everything seem to be known. However, this is a terrible (from performance point of view) function because the type of output can not be calcuated by looking types of input. And this is exactly how you write the performant code with regard to types.
So how to check your code? There is a special macro #code_warntype to detect such cases so you can correct your code:
Another type related issue are the containers that should not be specified by abstract elements. So you never want to have neither Vector{Any} nor Vector{Real} - rather than that you want Vector{Int} or Vector{Float64}.
See also https://docs.julialang.org/en/v1/manual/performance-tips/ for further discussion.
I can define a function that handles Integers:
function twice(a::Int64) a + a end
This function cannot handle Floats. If I want that, I need to define another method:
function twice(a::Float64) a + a end
But wait, this looks exactly the same, apart from the type definition. So, when I dream at night, there is a possibility to create such method definitions (everything identical apart from the type/combination of types) with a... macro maybe? Something like
#create_all_methods ("twice(a::$type ) a + a end", ["Int64", "Float64"])
Is this possible and if so, how?
Does the question maybe make no sense at all, because there is no situation where function twice(a) a + a end wouldn't achieve the exact same thing anyway?
Thanks for your help in advance.
There are multiple ways to achieve that. The easiest would be to just omit the type of aT then you methodn would look like:
function twice(a) a + a end
This is equivalent to
function twice(a::Any) a + a end
But maybe you don't want to define this for all types, or you already have another definition for twice(a::Any), so you could restrict your definition to the common supertype of Int64 and Float64. This common supertype can be found with typejoin(Float64, Int64) and yields the result Real, so your definition would now be
function twice(a::Real) a + a end
This also creates a method for other subtypes of Real such asa::Int32, so if you really want the method only for Int64 and Float64 you can create a union type. Then the method would look like
function twice(a::Union{Int64, Float64}) a + a end
Finally, it is indeed possible to achieve what you wanted to achieve with your macro. It does not make sense in this case, but either the function eval or the macro #eval is often used in more complicated cases. Your code could then look like
for T in (Int64, Float64)
#eval function twice(a::$T) a + a end
end
If you you just started learning Julia, I would not advice to use eval, as there are some dangers/anti-patterns associated with the usage of eval.
A straightforward way of creating methods for two or more input types is to use Union:
twice(a::Union{T1, T2, T3}) = a + a
where T1, T2, T3, ect. are concrete types, such as Int or Float64.
More commonly, you would define it for some abstract supertype instead, for example
twice(a::Number) = a + a
But most of the time, you should just start by defining a generic function
twice(a) = a + a
and then add types when you find that it becomes necessary. Most of the time it isn't.
In Pascal, I understand that one could create a function returning a pointer which can be dereferenced and then assign a value to that, such as in the following (obnoxiously useless) example:
type ptr = ^integer;
var d: integer;
function f(x: integer): ptr;
begin
f := #x;
end;
begin
f(d)^ := 4;
end.
And now d is 4.
(The actual usage is to access part of a quite complicated array of records data structure. I know that a class would be better than an array of nested records, but it isn't my code (it's TeX: The Program) and was written before Pascal implementations supported object-orientation. The code was written using essentially a language built on top of Pascal that added macros which expand before the compiler sees them. Thus you could define some macro m that takes an argument x and expands into thearray[x + 1].f1.f2 instead of writing that every time; the usage would be m(x) := somevalue. I want to replicate this functionality with a function instead of a macro.)
However, is it possible to achieve this functionality without the ^ operator? Can a function f be written such that f(x) := y (no caret) assigns the value y to x? I know that this is stupid and the answer is probably no, but I just (a) don't really like the look of it and (b) am trying to mimic exactly the form of the macro I mentioned above.
References are not first class objects in Pascal, unlike languages such as C++ or D. So the simple answer is that you cannot directly achieve what you want.
Using a pointer as you illustrated is one way to achieve the same effect although in real code you'd need to return the address of an object whose lifetime extends beyond that of the function. In your code that is not the case because the argument x is only valid until the function returns.
You could use an enhanced record with operator overloading to encapsulate the pointer, and so encapsulate the pointer dereferencing code. That may be a good option, but it very much depends on your overall problem, of which we do not have sight.
The Julia documentation says:
A primitive type is a concrete type whose data consists of plain old
bits. Classic examples of primitive types are integers and
floating-point values. Unlike most languages, Julia lets you declare
your own primitive types, rather than providing only a fixed set of
built-in ones. In fact, the standard primitive types are all defined
in the language itself:
I'm unable to find an example of how to do this, though, either in the docs or in the source code or anywhere else. What I'm looking for is an example of how to declare a primitive type, and how to subsequently implement a function or method on that type that works by manipulating those bits.
Is anyone able to point me towards an example? Thanks.
Edit: It's clear how to declare a primitive type, as there are examples immediately below the above quote in the doc. I'm hoping for information about how to subsequently manipulate them. For example, say I wanted to (pointlessly) implement my own primitive type MyInt8. I could declare that with primitive type MyInt8 <: Signed 8 end. But how would I subsequently implement a function myplus that manipulated the bits within Myint8?
PS in case it helps, the reason I'm asking is not because I need to do anything specific in Julia; I'm designing my own language for fun, and am researching how other languages implement various things.
# Declare the new type.
primitive type MyInt8 <: Signed 8 end
# A constructor to create values of the type MyInt8.
MyInt8(x :: Int8) = reinterpret(MyInt8, x)
# A constructor to convert back.
Int8(x :: MyInt8) = reinterpret(Int8, x)
# This allows the REPL to show values of type MyInt8.
Base.show(io :: IO, x :: MyInt8) = print(io, Int8(x))
# Declare an operator for the new type.
import Base: +
+ (a :: MyInt8, b :: MyInt8) = MyInt8(Int8(a) + Int8(b))
The key function here is reinterpret. It allows the bit representation of an Int8 to be treated as the new type.
To store a value with a custom bit layout, inside the MyInt8 constructor, you could perform any of the standard bit manipulation functions on the Int8 before 'reinterpreting' them as a MyInt8.
Say I have a Julia trait that relates to two types: one type is a sort of "base" type that may satisfy a sort of partial trait, the other is an associated type that is uniquely determined by the base type. (That is, the relation from BaseType -> AssociatedType is a function.) Together, these types satisfy a composite trait that is the one of interest to me.
For example:
using Traits
#traitdef IsProduct{X} begin
isnew(X) -> Bool
coolness(X) -> Float64
end
#traitdef IsProductWithMeasurement{X,M} begin
#constraints begin
istrait(IsProduct{X})
end
measurements(X) -> M
#Maybe some other stuff that dispatches on (X,M), e.g.
#fits_in(X,M) -> Bool
#how_many_fit_in(X,M) -> Int64
#But I don't want to implement these now
end
Now here are a couple of example types. Please ignore the particulars of the examples; they are just meant as MWEs and there is nothing relevant in the details:
type Rope
color::ASCIIString
age_in_years::Float64
strength::Float64
length::Float64
end
type Paper
color::ASCIIString
age_in_years::Int64
content::ASCIIString
width::Float64
height::Float64
end
function isnew(x::Rope)
(x.age_in_years < 10.0)::Bool
end
function coolness(x::Rope)
if x.color=="Orange"
return 2.0::Float64
elseif x.color!="Taupe"
return 1.0::Float64
else
return 0.0::Float64
end
end
function isnew(x::Paper)
(x.age_in_years < 1.0)::Bool
end
function coolness(x::Paper)
(x.content=="StackOverflow Answers" ? 1000.0 : 0.0)::Float64
end
Since I've defined these functions, I can do
#assert istrait(IsProduct{Rope})
#assert istrait(IsProduct{Paper})
And now if I define
function measurements(x::Rope)
(x.length)::Float64
end
function measurements(x::Paper)
(x.height,x.width)::Tuple{Float64,Float64}
end
Then I can do
#assert istrait(IsProductWithMeasurement{Rope,Float64})
#assert istrait(IsProductWithMeasurement{Paper,Tuple{Float64,Float64}})
So far so good; these run without error. Now, what I want to do is write a function like the following:
#traitfn function get_measurements{X,M;IsProductWithMeasurement{X,M}}(similar_items::Array{X,1})
all_measurements = Array{M,1}(length(similar_items))
for i in eachindex(similar_items)
all_measurements[i] = measurements(similar_items[i])::M
end
all_measurements::Array{M,1}
end
Generically, this function is meant to be an example of "I want to use the fact that I, as the programmer, know that BaseType is always associated to AssociatedType to help the compiler with type inference. I know that whenever I do a certain task [in this case, get_measurements, but generically this could work in a bunch of cases] then I want the compiler to infer the output type of that function in a consistently patterned way."
That is, e.g.
do_something_that_makes_arrays_of_assoc_type(x::BaseType)
will always spit out Array{AssociatedType}, and
do_something_that_makes_tuples(x::BaseType)
will always spit out Tuple{Int64,BaseType,AssociatedType}.
AND, one such relationship holds for all pairs of <BaseType,AssociatedType>; e.g. if BatmanType is the base type to which RobinType is associated, and SupermanType is the base type to which LexLutherType is always associated, then
do_something_that_makes_tuple(x::BatManType)
will always output Tuple{Int64,BatmanType,RobinType}, and
do_something_that_makes_tuple(x::SuperManType)
will always output Tuple{Int64,SupermanType,LexLutherType}.
So, I understand this relationship, and I want the compiler to understand it for the sake of speed.
Now, back to the function example. If this makes sense, you will have realized that while the function definition I gave as an example is 'correct' in the sense that it satisfies this relationship and does compile, it is un-callable because the compiler doesn't understand the relationship between X and M, even though I do. In particular, since M doesn't appear in the method signature, there is no way for Julia to dispatch on the function.
So far, the only thing I have thought to do to solve this problem is to create a sort of workaround where I "compute" the associated type on the fly, and I can still use method dispatch to do this computation. Consider:
function get_measurement_type_of_product(x::Rope)
Float64
end
function get_measurement_type_of_product(x::Paper)
Tuple{Float64,Float64}
end
#traitfn function get_measurements{X;IsProduct{X}}(similar_items::Array{X,1})
M = get_measurement_type_of_product(similar_items[1]::X)
all_measurements = Array{M,1}(length(similar_items))
for i in eachindex(similar_items)
all_measurements[i] = measurements(similar_items[i])::M
end
all_measurements::Array{M,1}
end
Then indeed this compiles and is callable:
julia> get_measurements(Array{Rope,1}([Rope("blue",1.0,1.0,1.0),Rope("red",2.0,2.0,2.0)]))
2-element Array{Float64,1}:
1.0
2.0
But this is not ideal, because (a) I have to redefine this map each time, even though I feel as though I already told the compiler about the relationship between X and M by making them satisfy the trait, and (b) as far as I can guess--maybe this is wrong; I don't have direct evidence for this--the compiler won't necessarily be able to optimize as well as I want, since the relationship between X and M is "hidden" inside the return value of the function call.
One last thought: if I had the ability, what I would ideally do is something like this:
#traitdef IsProduct{X} begin
isnew(X) -> Bool
coolness(X) -> Float64
∃ ! M s.t. measurements(X) -> M
end
and then have some way of referring to the type that uniquely witnesses the existence relationship, so e.g.
#traitfn function get_measurements{X;IsProduct{X},IsWitnessType{IsProduct{X},M}}(similar_items::Array{X,1})
all_measurements = Array{M,1}(length(similar_items))
for i in eachindex(similar_items)
all_measurements[i] = measurements(similar_items[i])::M
end
all_measurements::Array{M,1}
end
because this would be somehow dispatchable.
So: what is my specific question? I am asking, given that you presumably by this point understand that my goals are
Have my code exhibit this sort of structure generically, so that
I can effectively repeat this design pattern across a lot of cases
and then program in the abstract at the high-level of X and M,
and
do (1) in such a way that the compiler can still optimize to the best of its ability / is as aware of the relationship among
types as I, the coder, am
then, how should I do this? I think the answer is
Use Traits.jl
Do something pretty similar to what you've done so far
Also do ____some clever thing____ that the answerer will indicate,
but I'm open to the idea that in fact the correct answer is
Abandon this approach, you're thinking about the problem the wrong way
Instead, think about it this way: ____MWE____
I'd also be perfectly satisfied by answers of the form
What you are asking for is a "sophisticated" feature of Julia that is still under development, and is expected to be included in v0.x.y, so just wait...
and I'm less enthusiastic about (but still curious to hear) an answer such as
Abandon Julia; instead use the language ________ that is designed for this type of thing
I also think this might be related to the question of typing Julia's function outputs, which as I take it is also under consideration, though I haven't been able to puzzle out the exact representation of this problem in terms of that one.