Suppose I have an array like:
a = np.arange(0,10)
Why does a[-1:9] give an empty result? I expected it to give a result containing a[-1], a[0], a[1], ... a[8].
The slice is interpreted as starting at a[-1], which is the same as a[len(a)-1], so a[-1:9] is equivalent to a[9:9], which is an empty list. Your expected result isn't a contiguous range, which is what a slice must produce.
np.roll code is complex because it can work on any axis. But basically, for your shift it does res[0]=a[-1] and res[1:] = a[:-1]. You can't do the roll with one slicing expression. slice produces a view, and a roll cannot be a view.