JuliaCall
JuliaCall copied to clipboard
'julia_call` appears to have trouble with typing
When I try to run the convert
function using julia_call
, I get a MethodError
from Julia that I need to provide a Type
as the first argument. This does not occur when I run using julia_command
or julia_eval
. I am able to fix the error by providing julia_eval("Float32")
as the argument instead.
> julia_command("convert(Float32,1/3)")
0.33333334f0
> julia_call("convert","Float32","1/3")
Show Traceback
Rerun with Debug
Error: Error happens in Julia.
MethodError: First argument to `convert` must be a Type, got Float32
Stacktrace:
[1] docall(::Ptr{Nothing}) at /Users/aguang/CORE/scratch/JuliaCall/inst/julia/setup.jl:168
> julia_eval("convert(Float32,1/3)")
[1] 0.3333333
> julia_call("convert", julia_eval("Float32"),1/3)
[1] 0.3333333
Similarly, I have been testing JuliaCall out with the BioSequences package following along their examples. When I attempt to use julia_call
to run certain functions I will get MethodError: no method matching xyz
where the arguments all appear to have the type String:
> julia_install_package("BioSequences")
> julia_library("BioSequences")
> julia_command('dna"TACGTANNATC"')
11nt DNA Sequence:
TACGTANNATC
> julia_command('DNASequence(dna"ACGT", dna"NNNN", dna"TGCA")')
12nt DNA Sequence:
ACGTNNNNTGCA
> julia_call("DNASequence",'dna"ACGT"','dna"NNNN"','dna"TGCA"')
Show Traceback
Rerun with Debug
Error: Error happens in Julia.
MethodError: no method matching BioSequence{DNAAlphabet{4}}(::String, ::String, ::String)
Closest candidates are:
BioSequence{DNAAlphabet{4}}(::Union{AbstractString, AbstractArray{T,1} where T}) where A<:Alphabet at /Users/aguang/.julia/packages/BioSequences/7i86L/src/bioseq/constructors.jl:29
BioSequence{DNAAlphabet{4}}(::Union{AbstractString, AbstractArray{T,1} where T}, !Matched::Integer) where A<:Alphabet at /Users/aguang/.julia/packages/BioSequences/7i86L/src/bioseq/constructors.jl:29
BioSequence{DNAAlphabet{4}}(::Union{AbstractString, AbstractArray{T,1} where T}, !Matched::Integer, !Matched::Integer) where A<:Alphabet at /Users/aguang/.julia/packages/BioSequences/7i86L/src/bioseq/constructors.jl:29
Stacktrace:
[1] docall(::Ptr{Nothing}) at /Users/aguang/CORE/scratch/JuliaCall/inst/julia/setup.jl:168
> julia_call("repeat", 'dna"TA"',10)
Show Traceback
Rerun with Debug
Error: Error happens in Julia.
MethodError: no method matching repeat(::String, ::Float64)
Closest candidates are:
repeat(::Union{SubString{String}, String}, !Matched::Integer) at strings/substring.jl:189
repeat(::AbstractString, !Matched::Integer) at strings/basic.jl:659
Stacktrace:
[1] docall(::Ptr{Nothing}) at /Users/aguang/CORE/scratch/JuliaCall/inst/julia/setup.jl:168
> julia_command('repeat(dna"TA",10)')
20nt DNA Sequence:
TATATATATATATATATATA
It seems that JuliaCall is treating the arguments as String inputs. In a similar vein using julia_eval
on the arguments will work but I have to convert all the types it seems. (For example, 10 gets treated as ::Float64)
> julia_call("DNASequence",julia_eval('dna"ACGT"'),julia_eval('dna"NNNN"'),julia_eval('dna"TGCA"'))
Julia Object of type BioSequence{DNAAlphabet{4}}.
ACGTNNNNTGCA
> julia_call("repeat", julia_eval('dna"TA"'),10)
Show Traceback
Rerun with Debug
Error: Error happens in Julia.
MethodError: no method matching repeat(::BioSequence{DNAAlphabet{4}}, ::Float64)
Closest candidates are:
repeat(::BioSequence{A<:Alphabet}, !Matched::Integer) where A<:Alphabet at /Users/aguang/.julia/packages/BioSequences/7i86L/src/bioseq/constructors.jl:96
Stacktrace:
[1] docall(::Ptr{Nothing}) at /Users/aguang/CORE/scratch/JuliaCall/inst/julia/setup.jl:168
> julia_call("repeat", julia_eval('dna"TA"',need_return="Julia"),10)
Show Traceback
Rerun with Debug
Error: Error happens in Julia.
MethodError: no method matching repeat(::BioSequence{DNAAlphabet{4}}, ::Float64)
Closest candidates are:
repeat(::BioSequence{A<:Alphabet}, !Matched::Integer) where A<:Alphabet at /Users/aguang/.julia/packages/BioSequences/7i86L/src/bioseq/constructors.jl:96
Stacktrace:
[1] docall(::Ptr{Nothing}) at /Users/aguang/CORE/scratch/JuliaCall/inst/julia/setup.jl:168
> julia_call("repeat", julia_eval('dna"TA"'),julia_eval('convert(Int,10)'))
Julia Object of type BioSequence{DNAAlphabet{4}}.
TATATATATATATATATATA
> julia_call("repeat", julia_eval('dna"TA"'),julia_eval('10'))
Julia Object of type BioSequence{DNAAlphabet{4}}.
TATATATATATATATATATA
Is there a way for julia_call
to behave more like what would be expected in Julia with typing? One solution could be implementing a parameter eval=TRUE
or eval=FALSE
could be one solution to this. So for example if you did want to pass in ::String arguments you would do eval=FALSE
, but if not you would provide eval=TRUE
. Another solution could be having default behavior of julia_call
to be evaluating the arguments. If the evaluation is undefined then it passes on the argument as a ::String instead.
Linking to openjournals/joss-reviews#1284
This is in part due to the design of julia_call
, whose purpose is to call Julia functions on arguments in R (which will be converted to Julia object automatically).
When I created functions in JuliaCall
, I wanted to make them somewhat corresponding to basic functions in R.For example, julia_eval
corresponds to eval
, julia_help
corresponds to help
and julia_source
corresponds to source
.
The R functionalities I had in mind for julia_do.call
(yes, there is a function called this, but it may not be an appropriate name) and julia_call
were do.call
and eval(call(name, ...))
in R, which means that julia_call
will not try to evaluate the argument in Julia, but try to convert the argument to a corresponding Julia object. So eval(call("round", 10.5))
corresponds to julia_call("round", 10.5)
, not julia_call("round", "10.5")
.
The string evaluation is really a brilliant idea! Things like
julia_call("DNASequence",julia_eval('dna"ACGT"'),julia_eval('dna"NNNN"'),julia_eval('dna"TGCA"'))
definately doesn't look good.
However, I think it may not be a good idea to add an eval
argument in julia_call
since it makes the logic of julia_call
mentioned above more complicated and may lead to more potential confusion and bugs.
After more investigation, I think a string interpolation approach or something like that can be tried, for example, function str_interp
in package stringr
can interpolate R objects into strings, and we can follow a similar idea in JuliaCall
to make some new function or utilize julia_eval
and julia_command
function to evaluate interpolated julia commands.
For example, if such a hypothetical function is implemented, then
a = 2;
## the following is equivalent to julia_command("sqrt(2)")
julia_command("sqrt(${a})")
## we can even something more complicated
julia_eval("sqrt(${a+a}) + log(${a}, ${a}^${a})")
The logic here seems to be more clear, concise, and consistent than adding eval
argument to julia_call
.
And I need to explore more in related directions to have a good and performant implementation for this feature.
Currently, the way most similar to the hypothetical implementation is to either use stringr::str_interp
explicigly or things like
a = 2;
## again, this is quite similar to assign("aa", a) in R, except the assignment happens in Julia
julia_assign("aa", a)
julia_command("sqrt(aa)")
The two "tricks" discussed above have been used a lot in diffeqr
, for example, see https://github.com/JuliaDiffEq/diffeqr/blob/master/R/diffeqr.R.
Does this answer the first part of the question?
Again, thank you very much for the suggestion!
And about the issue related to 10
, typeof(10)
in R returns "double", so 10
actually represents a float number in R. In fact, identical(10, 10.0)
returns TRUE
in R, which means that 10
and 10.0
are programmatically undistinguishable in R itself. The implication is that the two R commands abc(def, ..., 10)
and abc(def, ..., 10.0)
are the same.
In sum, JuliaCall
cannot distinguish between julia_call("repeat", xxx,10)
from julia_call("repeat", xxx,10.0)
. And I guess the most obvious and straightforward interpretation is repeat(xxx, 10.0)
in Julia.
Ah, thanks for that, it does seem that typeof
returns double for integers as well.
In terms of using a string interpolation approach, so would the examples I gave go from
julia_call("convert", julia_eval("Float32"),1/3)
julia_call("DNASequence",julia_eval('dna"ACGT"'),julia_eval('dna"NNNN"'),julia_eval('dna"TGCA"'))
to
julia_call("convert", ${"Float32"}, 1/3)
julia_call("DNASequence",${'dna"ACGT"'},${'dna"NNNN"'},${'dna"TGCA"'})
? Or would assignment need to happen first, i.e.
float <- "Float32"
x <- 'dna"ACGT"'
y <- 'dna"NNNN"'
z <- 'dna"TGCA"'
julia_call("convert", ${float}, 1/3)
julia_call("DNASequence", ${x}, ${y}, ${z})
Either way, it seems like a good idea in terms of implementation to me. I would be happy to test it when you implement it.
Oh, I forgot to mention that R's "real" integer looks like 10L
, but it is seldom used since most R functions just treat float numbers as integers.
The implementation currently in my mind would look like
julia_eval("convert(Float32}(${x})", x = 1/3)
julia_eval('DNASequence(dna"${x}", dna"${y}",dna"${z}"', x = "ACGT", y = "NNNN", z = "TGCA")
or assignment can happen in environment first.
The idea is that when julia_eval
, julia_command
(or some new function for this functionality) find ${something}
in the command string, the functions will first evaluate something
in R with the environment of the function, then interpolate the evaluation result into the command string, and finally evaluate the interpolated command string in Julia.