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)