Вычислитель передает продолжения успеха и неудачи. Amb хранит оставшиеся варианты в пути неудачи, а require вызывает его при нарушении ограничения.
Вопрос для размышления
Как вычислитель может превратить неудачу из фатальной ошибки в запрос на возобновление работы с предыдущей точки выбора?
Вычисление выражений guest с явными продолжениями успеха и неудачи
Представление составных процедур guest с лексическими окружениями
Передача альтернативных продолжений через вычисление оператора и операндов
Реализация amb путем проверки каждого варианта с путем неудачи к остальным
Реализация require путем вызова текущей альтернативы, когда ее предикат ложен
Сбор каждого конечного решения или остановка на явном пределе решений
Сохранение в поле зрения пропущенного отката присваиваний и политики справедливости
Процедура ambeval принимает expression, environment, succeed и fail. Детерминированное выражение вызывает succeed со своим значением и с продолжением неудачи, которое следует использовать, если последующая работа отклонит это значение. Форма amb вычисляет свой первый вариант и заменяет неудачу процедурой, проверяющей оставшиеся варианты. Вычисление операндов передает эти продолжения слева направо, поэтому неудача внутри тела процедуры может вернуться к более раннему недетерминированному аргументу.
Форма require вычисляет свой предикат в той же системе продолжений. Истинный предикат завершается успехом со значением ok, а ложный предикат вызывает следующую альтернативу предиката, которая в итоге возвращается к самому последнему выбору amb. Процедура all-values повторно вызывает следующую альтернативу, предоставляемую каждым успехом. Первый запуск исчерпывает выбранный конечный поиск и сообщает complete. Второй запуск намеренно останавливается после четырех решений и сообщает truncated. Учебный вычислитель охватывает упорядоченные варианты amb, форму require, применение процедур и передачу продолжений успеха или неудачи.
Код SICP7,667 из 1,048,576 байт UTF-8
(begin(define(tagged-list?expressiontag)(and(pair?expression)(eq?(carexpression)tag)))(define(self-evaluating?expression)(or(number?expression)(string?expression)(boolean?expression)))(define(quoted?expression)(tagged-list?expression'quote))(define(if?expression)(tagged-list?expression'if))(define(lambda?expression)(tagged-list?expression'lambda))(define(begin?expression)(tagged-list?expression'begin))(define(amb?expression)(tagged-list?expression'amb))(define(require?expression)(tagged-list?expression'require))(define(text-of-quotationexpression)(cadrexpression))(define(if-predicateexpression)(cadrexpression))(define(if-consequentexpression)(caddrexpression))(define(if-alternativeexpression)(cadddrexpression))(define(lambda-parametersexpression)(cadrexpression))(define(lambda-bodyexpression)(cddrexpression))(define(begin-actionsexpression)(cdrexpression))(define(amb-choicesexpression)(cdrexpression))(define(require-predicateexpression)(cadrexpression))(define(operatorexpression)(carexpression))(define(operandsexpression)(cdrexpression))(define(pair-bindingsvariablesvalues)(cond((and(null?variables)(null?values))'())((null?variables)(error"too many arguments"))((null?values)(error"too few arguments"))(else(cons(cons(carvariables)(carvalues))(pair-bindings(cdrvariables)(cdrvalues))))))(define(extend-environmentvariablesvaluesenvironment)(append(pair-bindingsvariablesvalues)environment))(define(lookup-variable-valuevariableenvironment)(let((binding(assocvariableenvironment)))(ifbinding(cdrbinding)(error"unbound guest variable"variable))))(define(make-procedureparametersbodyenvironment)(list'compoundparametersbodyenvironment))(define(compound-procedure?procedure)(tagged-list?procedure'compound))(define(procedure-parametersprocedure)(cadrprocedure))(define(procedure-bodyprocedure)(caddrprocedure))(define(procedure-environmentprocedure)(cadddrprocedure))(define(eval-sequenceexpressionsenvironmentsucceedfail)(cond((null?expressions)(succeed'okfail))((null?(cdrexpressions))(ambeval(carexpressions)environmentsucceedfail))(else(ambeval(carexpressions)environment(lambda(ignorednext-alternative)(eval-sequence(cdrexpressions)environmentsucceednext-alternative))fail))))(define(get-argumentsexpressionsenvironmentsucceedfail)(if(null?expressions)(succeed'()fail)(ambeval(carexpressions)environment(lambda(argumentnext-argument)(get-arguments(cdrexpressions)environment(lambda(remainingnext-remaining)(succeed(consargumentremaining)next-remaining))next-argument))fail)))(define(apply-procedureprocedureargumentssucceedfail)(cond((procedure?procedure)(succeed(applyprocedurearguments)fail))((compound-procedure?procedure)(eval-sequence(procedure-bodyprocedure)(extend-environment(procedure-parametersprocedure)arguments(procedure-environmentprocedure))succeedfail))(else(error"not a guest procedure"procedure))))(define(eval-ifexpressionenvironmentsucceedfail)(ambeval(if-predicateexpression)environment(lambda(predicate-valuenext-predicate)(ambeval(ifpredicate-value(if-consequentexpression)(if-alternativeexpression))environmentsucceednext-predicate))fail))(define(eval-requireexpressionenvironmentsucceedfail)(ambeval(require-predicateexpression)environment(lambda(predicate-valuenext-predicate)(ifpredicate-value(succeed'oknext-predicate)(next-predicate)))fail))(define(eval-ambchoicesenvironmentsucceedfail)(define(try-nextremaining)(if(null?remaining)(fail)(ambeval(carremaining)environmentsucceed(lambda()(try-next(cdrremaining))))))(try-nextchoices))(define(eval-applicationexpressionenvironmentsucceedfail)(ambeval(operatorexpression)environment(lambda(procedurenext-operator)(get-arguments(operandsexpression)environment(lambda(argumentsnext-arguments)(apply-procedureprocedureargumentssucceednext-arguments))next-operator))fail))(define(ambevalexpressionenvironmentsucceedfail)(cond((self-evaluating?expression)(succeedexpressionfail))((symbol?expression)(succeed(lookup-variable-valueexpressionenvironment)fail))((quoted?expression)(succeed(text-of-quotationexpression)fail))((if?expression)(eval-ifexpressionenvironmentsucceedfail))((lambda?expression)(succeed(make-procedure(lambda-parametersexpression)(lambda-bodyexpression)environment)fail))((begin?expression)(eval-sequence(begin-actionsexpression)environmentsucceedfail))((amb?expression)(eval-amb(amb-choicesexpression)environmentsucceedfail))((require?expression)(eval-requireexpressionenvironmentsucceedfail))((pair?expression)(eval-applicationexpressionenvironmentsucceedfail))(else(error"unknown guest expression"expression))))(defineprimitive-environment(list(cons'++)(cons'--)(cons'**)(cons'//)(cons'==)(cons'<<)(cons'>>)(cons'<=<=)(cons'>=>=)(cons'listlist)(cons'conscons)(cons'carcar)(cons'cdrcdr)(cons'null?null?)(cons'pair?pair?)(cons'notnot)(cons'absabs)))(define(all-valuesexpressionsolution-limit)(if(=solution-limit0)(list'truncated'())(let((answers'())(count0))(ambevalexpressionprimitive-environment(lambda(valuenext-alternative)(set!answers(consvalueanswers))(set!count(+count1))(if(=countsolution-limit)(list'truncated(reverseanswers))(next-alternative)))(lambda()(list'complete(reverseanswers)))))))(definepair-program'((lambda(leftright)(begin(require(<leftright))(require(=(+leftright)5))(listleftright)))(amb1234)(amb1234)))(all-valuespair-program20))
Проследите за тем, как каждый выбор amb устанавливает продолжение неудачи для оставшихся вариантов. Затем проследите за get-arguments, когда отклоненный require в теле поочередно возобновляет следующий третий аргумент, следующий второй аргумент или следующий первый аргумент. Поиск останавливается, когда исчерпывает свои варианты или достигает установленного предела решений.
Попробуйте сами
Измените программу и сравните результат.
Измените программу для пар так, чтобы переменные left и right выбирались от 1 до 6, потребуйте через require равенства их произведения 12 и соберите все решения, в которых left меньше. Спрогнозируйте порядок решений перед запуском.
Показать подсказку
Порядок amb слева направо проверяет все варианты right для left 1 перед переходом к left 2. Только упорядоченные пары множителей проходят обе формы require.