Quantcast
Channel: vinxru
Viewing all articles
Browse latest Browse all 319

Lambda и Prolog

$
0
0
В прошлом я написал несколько компиляторов Пролога. И очень хотелось написать транслятор Пролога на Си, но это было невозможно, потому что в Прологе программа выполняется весьма необычным способом. Вызов функции может привести к многократному вызову функций по коду ниже. (Правильнее говорить предикат, а не функция). Например, в самом простом случае.

Совершеннолетние_клиенты(Имя) :-
  Клиенты(Имя, Возраст),
  Возраст > 18;


Первая строка выполнит многократный вызов второй строки. (Это зависит от компилятора, но предположим самый глупый компилятор). И на Си написать аналог этого примера было сложно. Конечно возможно, но машинный код был понятнее.

Вообще, в Прологе порядок строк может быть любой и на результат это не влияет. Каждая строка порождает множество вариантов и все они объединяются по И. Программа выше звучит как "Все что является клиентом"и "Всё что больше 18 лет".

Лямбда функции, появившиеся недавно в C++, позволяют красиво, то есть без хаков, представить на С++ программу на Прологе. Возьмем пример из Википеди (мне лень искать что то более интересное). Программа поиска общего делителя для массива чисел:

% Верно, что НОД (A, 0) = A
gcd2(A, 0, A).
 
% Верно, что НОД(A, B) = G, когда A>0, B>0 и НОД(B, A % B) = G (% - остаток от деления)
gcd2(A, B, G) :- A>0, B>0, N is mod(A, B), gcd2(B, N, G).
 
gcdn(A, [], A).
gcdn(A, [B|Bs], G) :- gcd2(A, B, N), gcdn(N, Bs, G).
gcdn([A|As], G) :- gcdn(A, As, G).


На C++ с Лямбдами это будет выглядеть так:

void gcd2(int a, int b, std::function<void(int)> result) {
  // Верно, что НОД (A, 0) = A
  if(b==0) result(a);
  //Верно, что НОД(A, B) = G, когда A>0, B>0 и НОД(B, A % B) = G (% - остаток от деления)
  if(a>0 && b>0) gcd2(b, a % b, result);
}
 
void gcdn(int a, int* bb, int* be, std::function<void(int)> result) {
  if(bb==be) result(a);
        else gcd2(a, *bb, [&](int n) { gcdn(n, bb+1, be, result); });
}

void gcdn(int* xb, int* xe, std::function<void(int)> result) {
  gcdn(*xb, xb+1, xe, result);
}

void test() {
  vector<int> numbers;
  numbers.push_back(36);
  numbers.push_back(6);
  numbers.push_back(10);  
  gcdn(numbers.begin(), numbers.end(), [&](int n) { 
    cout << n;
  });
}


В принципе, можно компилировать с Пролога на Си красиво

А почему бы сразу не писать так? Компилятор пролога строит план выполнения, переставляет строки, объединяет предикаты, выбрасывает лишнее. А еще определяет, какая переменная будет входящей, а какая исходящей. В предикате A=B*C, входящей может быть любая переменная и даже все сразу. Вот за этим он и нужен.

А пригодится всё это для построения оптимизаторов.

Viewing all articles
Browse latest Browse all 319

Trending Articles