Проблема в том, что как только вы напишите matrix --> [A, B]
, это правило определенно сгенерирует двухэлементный список, независимо от того, что такое A
и B
.
Таким образом, вы хотите сгенерировать один из них -элементные списки [A]
или [B]
. Вы можете сделать это явно:
a --> [0, 0, 0, 0, 0].
b --> [1, 1, 1, 1, 1].
matrix -->
[A],
{ phrase(a, A) }.
matrix -->
[B],
{ phrase(b, B) }.
matrix -->
[A, B],
{ phrase(a, A) },
{ phrase(b, B) }.
Это работает:
?- phrase(matrix, Matrix).
Matrix = [[0, 0, 0, 0, 0]] ;
Matrix = [[1, 1, 1, 1, 1]] ;
Matrix = [[0, 0, 0, 0, 0], [1, 1, 1, 1, 1]].
Но это много печатает и не очень гибко, если вы хотите расширить его.
Итак, давайте попробуем обобщить фиксированный бит [A, B]
. В качестве первого шага мы можем использовать list//1
DCG, который просто описывает свой собственный список аргументов:
list([]) -->
[].
list([X|Xs]) -->
[X],
list(Xs).
Мы можем использовать это следующим образом:
?- phrase(list([a, b, c]), Xs).
Xs = [a, b, c].
И использовать его для определить матрицу:
matrix_with_list -->
list([A, B]),
{ phrase(a, A) },
{ phrase(b, B) }.
Похоже, что мы еще не достигли прогресса:
?- phrase(matrix_with_list, Matrix).
Matrix = [[0, 0, 0, 0, 0], [1, 1, 1, 1, 1]].
Но теперь мы можем немного изменить list//1
, чтобы описать только подсписок его списка аргументов:
optional_list([]) -->
[].
optional_list([_X|Xs]) -->
% skip this X!
optional_list(Xs).
optional_list([X|Xs]) -->
% keep this X
[X],
optional_list(Xs).
Это ведет себя следующим образом:
?- phrase(optional_list([a, b, c]), Xs).
Xs = [] ;
Xs = [c] ;
Xs = [b] ;
Xs = [b, c] ;
Xs = [a] ;
Xs = [a, c] ;
Xs = [a, b] ;
Xs = [a, b, c].
Теперь мы можем адаптировать предыдущее определение:
matrix_with_optional_list -->
optional_list([A, B]),
{ phrase(a, A) },
{ phrase(b, B) }.
И мы получаем:
?- phrase(matrix_with_optional_list, Matrix).
Matrix = [] ;
Matrix = [[1, 1, 1, 1, 1]] ;
Matrix = [[0, 0, 0, 0, 0]] ;
Matrix = [[0, 0, 0, 0, 0], [1, 1, 1, 1, 1]].
Довольно хорошо! Но нехорошо иметь все эти phrase/2
вызовы, даже если они относятся к элементам, которые не попадают в матрицу.
Итак, давайте обобщим еще немного, для DCG, аргументом которой является список DCG, и это описывает подсписок списка списков, описанных этими DCG:
optional_phrase([]) -->
[].
optional_phrase([_Rule|Rules]) -->
% skip this rule
optional_phrase(Rules).
optional_phrase([Rule|Rules]) -->
% apply this rule
[List],
{ phrase(Rule, List) },
optional_phrase(Rules).
Основное понимание здесь заключалось в том, что вы можете использовать phrase/2
в порядке «высшего порядка», где его первый аргумент равен не буквальный атом (или термин функтора), именующий DCG, но переменная , связанная с таким атомом или термином. Тем не менее, вы должны убедиться, что эти переменные действительно связаны, когда вы применяете это правило.
При этом окончательное определение матрицы будет просто:
matrix_with_optional_phrase -->
optional_phrase([a, b]).
Это теперь перечисляет матрицы, как и раньше, но он только когда-либо выполняет phrase/2
для элементов, которые фактически являются частью матрицы:
?- phrase(matrix_with_optional_phrase, Matrix).
Matrix = [] ;
Matrix = [[1, 1, 1, 1, 1]] ;
Matrix = [[0, 0, 0, 0, 0]] ;
Matrix = [[0, 0, 0, 0, 0], [1, 1, 1, 1, 1]].