I have the following which tries to apply a relative unit range to a string whose length is unknown:
function testnums(data::String,numrange::String)
data[numrange]
end
foo = "asdfasdfasdf"
bar = "end-3:end"
testnums(foo,bar)
Here is the error
```
MethodError: no method matching getindex(::String, ::String)
Closest candidates are:
getindex(::String, !Matched::UnitRange{Int64}) at strings/string.jl:245
getindex(::String, !Matched::Int64) at strings/string.jl:210
getindex(::String, !Matched::UnitRange{#s69} where #s69<:Integer) at strings/string.jl:242
...
testnums(::String, ::String) at passargs.jl:2
top-level scope at passargs.jl:7
```
As suggested by the error, you can’t pass a string for the range. So how can I pass a relative range eg end-3:end?
What you should do, if you truly want to pass a relative range, is to pass a normal range, and the index it’s relative to, and add those up inside the function. For example, for end, it would be rel=length(data), and then you do rge=-3:0, and then in the function you take data[rel.+rge]. Otherwise, you need to pass the absolute range.
Please don’t do this. This is a pathological example of pretty much every “gotcha” in the julia language put together. It relies both on using strings as expressions (using parse) and on global variables and using eval. There are so many subtleties to just how scary this is that it would even be difficult adequately explaining it . Luckily, it throws a ParseError.
The short answer to this is that since end is a keyword (not a function or a symbol), you can’t pass it around. In particular notice what happens when the expression a[1:end] is “lowered” (translated by julia into what’s actually going to get compiled).
In particular, note that %1 is evaluating lastindex(a), %2 constructs the range 1:%1 (i.e. 1:lastindex(a)), and then that’s what’s used as the index in %3. The keyword end is just syntactic sugar for lastindex(self), so it doesn’t play a direct role in any of this and julia gets rid of it long before compile time. To do this you can:
pass in the index ranges you want directly,
or you can write a macro (I advise against, but it is technically the “best” solution)
or you create an api that doesn’t require it. If your example is the behavior that’s important to you, you could instead do
function testnums(data, back_i)
data[end-back_i:end]
end
foo = "123456789"
testnums(foo, 3) # "6789"
Ha yes, I just (tried) to answer his question. But it is true that this is not best practice. I just assumed that he has a good reason to pass strings to the function.
What would work though is
function testnums(data::String,numrange::String)
exp = reduce(*, ["data[",numrange,"]"])
eval(Meta.parse(exp))
end
foo = "asdfasdfasdf"
bar = "end-3:end"
testnums(foo,bar)
But it is certainly not the way to go. If you don’t have a specific reason to use a String argument for the range then dont.
I have faced the same issue of not being able to pass relative ranges to functions. Coming from the python world where we could write the same function as,
I understand why the code provided by the OP doesn’t work. But shouldn’t there be a counterpart to python’s slice in Julia which allows easily passing relative ranges to functions. Would something like RelativeRange(end-3,end) be hard to implement in Julia? Or is there something fundamentally flawed about passing relative ranges as arguments to functions?
It’s not possible to pass the keyword end around. I was going to say this can be done with a macro, but turns out it really can’t . You cannot represent a standalone end as an expression without jumping through hoops.
SliceLike(x -> lastindex(x)-3, lastindex)
# getindex with this converts to
# x[lastindex(x)-3:lastindex(x)],
# which is the same as with end.
or even simply to a “slicing” function like
# called like slicer(x)
slicer = x -> x[end-3:end]
But anyway it turns out you truly can’t do this with a macro, because end is even more of a reserved keyword than I thought! I’m not sure if this is necessarily the case for some intrinsic reason, or whether :(end-3) could technically be an allowed expression, but oh well.
In any case, a custom type like Slice is still very much on the table, as is using a slicer function without a macro.
Thanks for the thorough answer.
Lol, this is almost exactly how I did it while waiting on a more ‘elegant’ solution from Discourse.
I was hoping that a metaprogramming technique was possible so that I could write a function that ‘understands’ relative and explicit ranges, looks like the quick and dirty solution is the only one in this case.