Another day, another predicate, another choicepoint!

Can squeeze out more performance, at the cost of readability:

nth1_elem(I, L, E) :-
    (   integer(I)
    ->  I @>= 1
    ;   var(I)
    ),
    nth1_elem_(L, 1, I, E).

nth1_elem_([H|T], U, I, E) :-
    (   (   U == I
        ->  ! 
        ;   U = I
        ),
        % After cut
        H = E,
        (   T == []
            % No more elements to check
        ->  !
        ;   true
        )
    ;   U1 is U + 1,
        nth1_elem_(T, U1, I, E)
    ).

Performance:

?- garbage_collect, numlist(1, 20_000_000, NL),
time((nth1_elem(I, NL, E), false)).
% 60,000,001 inferences, 1.300 CPU in 1.301 seconds (100% CPU, 46167444 Lips)

?- garbage_collect, time((between(1, 10_000, Len),
numlist(1, Len, NL), nth1_elem(I, NL, 1_000), false)).
% 100,118,001 inferences, 5.001 CPU in 5.006 seconds (100% CPU, 20020685 Lips)

?- garbage_collect, time((between(1, 10_000, Len),
length(L, Len), nth1_elem(I, L, E), false)).
% 150,055,001 inferences, 3.349 CPU in 3.351 seconds (100% CPU, 44811446 Lips)