JuliaCall icon indicating copy to clipboard operation
JuliaCall copied to clipboard

'julia_call` appears to have trouble with typing

Open aguang opened this issue 5 years ago • 5 comments

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.

aguang avatar Mar 06 '19 19:03 aguang

Linking to openjournals/joss-reviews#1284

aguang avatar Mar 06 '19 19:03 aguang

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_callmentioned 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!

Non-Contradiction avatar Mar 07 '19 03:03 Non-Contradiction

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.

Non-Contradiction avatar Mar 07 '19 05:03 Non-Contradiction

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.

aguang avatar Mar 08 '19 17:03 aguang

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.

Non-Contradiction avatar Mar 08 '19 20:03 Non-Contradiction