How to make linspace work with one point?

Currently,

linspace(1,10,1)

returns

ERROR: ArgumentError: linspace(1.0, 10.0, 1): endpoints differ

What’s the way to use multiple dispatch to grab the value of 1 and make a method like:

linspace(a,b,1) = a:1.0:a

I remember reading about this somewhere

1 Like

I’d consider that error a feature, not a bug. In my mind, the raison d’etre of linspace is to hit the endpoints exactly — that’s how it differs from the other constructions.

Multiple dispatch only works with types, not values, so you can’t use it to do something special when one of the arguments is the value 1.

2 Likes

Range has the same behavior. It gives nonsensical enough results for length=0, but errors, rather than defaulting to the valid singleton range 0:0 that I expect.

julia> range(0, stop=2, length=0)
0.0:-2.0:2.0

julia> range(0, stop=2, length=1)
ERROR: ArgumentError: range(0.0, stop=2.0, length=1): endpoints differ
Stacktrace:
 [1] _linspace1(#unused#::Type{Float64}, start::Float64, stop::Float64, len::Int64)
   @ Base ./twiceprecision.jl:678
 [2] _linspace(#unused#::Type{Float64}, start_n::Int64, stop_n::Int64, len::Int64, den::Int64)
   @ Base ./twiceprecision.jl:662
 [3] _linspace
   @ ./twiceprecision.jl:660 [inlined]
 [4] _range
   @ ./range.jl:441 [inlined]
 [5] #range#57
   @ ./range.jl:91 [inlined]
 [6] top-level scope
   @ REPL[2]:1

julia> range(0, stop=2, length=2)
0.0:2.0:2.0

That still makes some sense to me: an empty range is definitely what you asked for and definitely what you got back. A 1-length range with different endpoints is impossible to satisfy. Should it give you back 0:0 or 2:2?

8 Likes

Should it give you back 0:0 or 2:2 ?

IMO, Yes!

What is most consistent with most user’s expectations?

range(2, stop=0, length=2) works, so why not choose the convention that range includes the mandatory first argument start, so:
range(2, stop=0, length=1)2:2
range(0, stop=2, length=1)0:0

The assumption that a user would want an empty range, but never a semi-empty one seems inconsistent to me.

Otherwise, one works around this with clunky code blocks.

Maybe I misunderstood the question, but I don’t think “yes” is an answer to it.

3 Likes

In my opinion it should give back either 0:0 or 2:2.

There might be no guarantee which of these is returned. However, I think returning a range containing start is preferable.

This has the advantage that it would interpolate from the predictable behaviors for length=0, 2, 3, ..., n

range(0, stop=2, length=0)0:-2:2 represents an empty range
range(0, stop=2, length=1)0:0 (proposed)
range(0, stop=2, length=2)0:2:2
range(0, stop=2, length=3)0:1:2
etc.

This would be convenient. It might be unpredictable in other contexts.

As soon as you’re guessing what the user meant and just arbitrarily picking one possibility, you’re in dangerous API design territory. Dynamic languages have traditionally loved doing this “for convenience” but the upshot is that most of them have a bad reputation for being impossible to write reliable code in. When in doubt about what the user meant, throw an error and ask them to be more specific.

10 Likes

Point taken.

range(0,step=2,stop=3)0:2:2
never returns 1:2:3. It silently assumes the range stops somewhere other than stop.

This seems very conventional. Using linspace to interpolate a specified number of points is too. I’ll back away from wanting a (tricky) mixture of the linspace and range behavior.

The OP could try something like this:

linearpermissiverange(start; stop, length) = length==1 ? (start:start) : range(start, stop=stop, length=length)

to handle ranges of length=1.