Улучшение генерации списков в диапазоне в Prolog

Я новичок в Prolog и влюбляюсь в него все больше и больше. Мне интересно, может ли эта реализация быть далее обобщена или улучшена, и является ли это идиоматическим кодом Пролога?

%% range/2
range(End, List) :-
    End > 0, !,
    range_ascend(0, End, 1, List).

range(End, List) :-
    End < 0,
    range_descend(0, End, 1, List).

%% range/3
range(Start, End, List) :-
    ((Start =< End) ->
        (range_ascend(Start, End, 1, List))
    ;
        (range_descend(Start, End, 1, List))).

%% range/4 (+Start, +End, +Step, -List)
range(Start, End, Step, List) :-
    ((Start =< End) ->
        (range_ascend(Start, End, Step, List))
    ;
        (range_descend(Start, End, Step, List))).

range_descend(Start, End, _, []) :-
    End >= Start, !.
range_descend(Start, End, Step, [Start|Rest]) :-
    Next is Start - Step,
    range_descend(Next, End, Step, Rest).

range_ascend(Start, End, _, []) :-
    Start >= End, !.
range_ascend(Start, End, Step, [Start|Rest]) :-
    Next is Start + Step,
    range_ascend(Next, End, Step, Rest).

Ответ 1

Одна из основных проблем вашей реализации заключается в том, что она работает только "одним способом", то есть ваш код работает хорошо, когда End устанавливается на определенное значение, но не работает, когда это переменная:

?- range(X,[0,1,2,3]).
ERROR: >/2: Arguments are not sufficiently instantiated

Возможно, вам не нужно такое поведение для работы, но в Prolog обычно желательно и элегантно, что ваш предикат действует как истинное отношение, которое работает по-разному.

Однако реализация предикатов как таковых зачастую сложнее, чем их функциональное функционирование, особенно если вы новичок в Prolog.

Я не буду подробно разбираться в том, как улучшить свой код (я думаю, что это скорее вопрос для сайта Code Review SE). Однако я представляю ниже предикат диапазона с лучшим поведением, чем ваш:

range(I, S, [I|R]) :-
    I #=< S,
    if_(I = S,
        R = [],
        (   J #= I + 1,
            range(J, S, R)
        )
    ).

Этот предикат требует if_/3 и (=)/3 из library(reif), а также library(clpfd) (который вы можете включить в свой программа с :- use_module(library(clpfd)).).

Как вы можете видеть, он намного короче, чем ваша реализация, но также хорошо работает в нескольких разных сценариях:

?- range(0,5,L).             % Same behavior as your predicate
L = [0, 1, 2, 3, 4, 5].

?- range(-5,0,L).            % Different behavior from your predicate, but logically sounder
L = [-5, -4, -3, -2, -1, 0]

?- range(1,S,[1,2,3,4,5]).   % Finding the max of the range
S = 5 ;
false.

?- range(I,S,[1,2,3,4,5]).   % Finding both the min and max of the range
I = 1,
S = 5 ;
false.

?- range(I,S,[1,2,X,Y,5]).   % With variables in the range
I = 1,
S = 5,
X = 3,
Y = 4 ;
false.

?- range(1,S,L).             % Generating ranges
S = 1,
L = [1] ;
S = 2,
L = [1, 2] ;
S = 3,
L = [1, 2, 3] ;
S = 4,
L = [1, 2, 3, 4] ;
…

?- range(I,1,L).             % Generating ranges
I = 1,
L = [1] ;
I = 0,
L = [0, 1] ;
I = -1,
L = [-1, 0, 1] ;
I = -2,
L = [-2, -1, 0, 1] ;
…

?- range(I,S,L).             % Generating all possible ranges
I = S,
L = [S],
S in inf..sup ;
L = [I, S],
I+1#=S,
S#>=I,
dif(I, S) ;
L = [I, _G6396, S],
I+1#=_G6396,
S#>=I,
dif(I, S),
_G6396+1#=S,
S#>=_G6396,
dif(_G6396, S) ;
…

Я думаю, вы можете увидеть, как много поведения отображается здесь, и насколько полезно иметь доступ ко всем из них только с одним предикатом.

Этот предикат использует Программирование логики Constraint (библиотека clpfd). CLP позволяет писать отношения между целыми числами (которые являются #=< и #=, которые вы видите в коде, в отличие от классических =< и is, которые вы используете в низкоуровневой арифметике). Это то, что делает большую часть тяжелой атлетики для нас и позволяет писать короткий, декларативный код о целых числах (с которыми вы не можете легко справиться с is).

Я рекомендую вам прочитать Арифметическую главу Power of Prolog от Markus Triska, чтобы узнать о арифметике CLP в Prolog, которая определенно является предметом вас нужно научиться, если вы намерены серьезно использовать Prolog (как я надеюсь, я проиллюстрировал в этом ответе).