compound procedure의 피연산자는 지연하고 primitive 피연산자와 술어는 강제하며 첫 actual-value 요청 뒤 thunk를 evaluated-thunk로 바꿉니다.
생각해 볼 질문
평가기는 인자 작업을 미루면서도 같은 식을 여러 번 참조할 때 되풀이 계산하지 않도록 어떻게 만들까요?
피연산자 식과 호출 환경을 함께 지연하기
적용 방법을 결정하기 전에 연산자 강제하기
primitive 인자와 if 술어를 실제 값으로 강제하기
compound procedure에는 지연된 인자 전달하기
thunk 태그와 payload를 바꾸어 결과 메모이즈하기
사용하지 않은 식과 되풀이한 식과 선택되지 않은 식 구분하기
apply-lazy는 호스트 primitive와 표현된 compound procedure를 구분합니다. primitive는 실제 인자 값이 필요하므로 host apply 전에 피연산자를 강제합니다. compound procedure는 원래 피연산자 식과 호출 환경을 담은 thunk 레코드를 받습니다. 변수 조회는 그 레코드를 반환하고 actual-value가 강제 여부를 결정합니다.
force-it은 새 thunk를 한 번 평가한 뒤 같은 가변 레코드를 evaluated-thunk로 바꾸고 결과를 저장하며 식과 환경을 버립니다. 사용하지 않은 인자는 probe를 실행하지 않고 x를 두 번 써도 probe는 한 번뿐입니다. 선택되지 않은 if 갈래도 평가되지 않습니다. 이 수업의 call-by-need 모형은 나열한 형식의 지연 평가 과정을 직접 실행합니다.
SICP 코드UTF-8 7,892 / 1,048,576바이트
(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(definition?expression)(tagged-list?expression'define))(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(definition-variableexpression)(if(symbol?(cadrexpression))(cadrexpression)(car(cadrexpression))))(define(definition-valueexpression)(if(symbol?(cadrexpression))(caddrexpression)(cons'lambda(cons(cdr(cadrexpression))(cddrexpression)))))(define(operatorexpression)(carexpression))(define(operandsexpression)(cdrexpression))(define(pair-bindingsvariablesvalues)(cond((and(null?variables)(null?values))'())((null?variables)(error"too many guest arguments"))((null?values)(error"too few guest arguments"))(else(cons(cons(carvariables)(carvalues))(pair-bindings(cdrvariables)(cdrvalues))))))(define(make-framevariablesvalues)(cons'*frame*(pair-bindingsvariablesvalues)))(define(first-frameenvironment)(carenvironment))(define(frame-bindingsframe)(cdrframe))(define(extend-environmentvariablesvaluesbase)(cons(make-framevariablesvalues)base))(define(lookup-variable-valuevariableenvironment)(if(null?environment)(error"unbound lazy guest variable"variable)(let((record(assocvariable(frame-bindings(first-frameenvironment)))))(ifrecord(cdrrecord)(lookup-variable-valuevariable(cdrenvironment))))))(define(define-variable!variablevalueenvironment)(let*((frame(first-frameenvironment))(record(assocvariable(frame-bindingsframe))))(ifrecord(set-cdr!recordvalue)(set-cdr!frame(cons(consvariablevalue)(frame-bindingsframe)))))(list'definedvariable))(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(delay-itexpressionenvironment)(list'thunkexpressionenvironment))(define(thunk?object)(tagged-list?object'thunk))(define(evaluated-thunk?object)(tagged-list?object'evaluated-thunk))(define(force-itobject)(cond((thunk?object)(let((result(actual-value(cadrobject)(caddrobject))))(set-car!object'evaluated-thunk)(set-car!(cdrobject)result)(set-cdr!(cdrobject)'())result))((evaluated-thunk?object)(cadrobject))(elseobject)))(define(actual-valueexpressionenvironment)(force-it(lazy-evalexpressionenvironment)))(define(true?value)(not(eq?value#f)))(define(eval-ifexpressionenvironment)(if(true?(actual-value(if-predicateexpression)environment))(lazy-eval(if-consequentexpression)environment)(lazy-eval(if-alternativeexpression)environment)))(define(eval-sequenceexpressionsenvironment)(cond((null?expressions)'ok)((null?(cdrexpressions))(lazy-eval(carexpressions)environment))(else(actual-value(carexpressions)environment)(eval-sequence(cdrexpressions)environment))))(define(eval-definitionexpressionenvironment)(define-variable!(definition-variableexpression)(actual-value(definition-valueexpression)environment)environment))(define(list-of-arg-valuesexpressionsenvironment)(if(null?expressions)'()(cons(actual-value(carexpressions)environment)(list-of-arg-values(cdrexpressions)environment))))(define(list-of-delayed-argsexpressionsenvironment)(if(null?expressions)'()(cons(delay-it(carexpressions)environment)(list-of-delayed-args(cdrexpressions)environment))))(define(apply-lazyprocedureargument-expressionscalling-environment)(cond((procedure?procedure)(applyprocedure(list-of-arg-valuesargument-expressionscalling-environment)))((compound-procedure?procedure)(eval-sequence(procedure-bodyprocedure)(extend-environment(procedure-parametersprocedure)(list-of-delayed-argsargument-expressionscalling-environment)(procedure-environmentprocedure))))(else(error"not a lazy guest procedure"procedure))))(define(lazy-evalexpressionenvironment)(cond((self-evaluating?expression)expression)((symbol?expression)(lookup-variable-valueexpressionenvironment))((quoted?expression)(text-of-quotationexpression))((if?expression)(eval-ifexpressionenvironment))((lambda?expression)(make-procedure(lambda-parametersexpression)(lambda-bodyexpression)environment))((begin?expression)(eval-sequence(begin-actionsexpression)environment))((definition?expression)(eval-definitionexpressionenvironment))((pair?expression)(apply-lazy(actual-value(operatorexpression)environment)(operandsexpression)environment))(else(error"unknown lazy guest expression"expression))))(defineprobes0)(define(probevalue)(set!probes(+probes1))value)(defineprimitive-bindings(list(cons'++)(cons'--)(cons'**)(cons'//)(cons'==)(cons'<<)(cons'>>)(cons'listlist)(cons'conscons)(cons'carcar)(cons'cdrcdr)(cons'null?null?)(cons'pair?pair?)(cons'probeprobe)))(defineglobal-environment(list(cons'*frame*primitive-bindings)))(defineunused'((lambda(x)1)(probe10)))(defineduplicated'((lambda(x)(+xx))(probe10)))(defineselected-branch'(if#t(quotesafe)(probe99)))(set!probes0)(defineunused-value(actual-valueunusedglobal-environment))(defineunused-probesprobes)(set!probes0)(defineduplicated-value(actual-valueduplicatedglobal-environment))(defineduplicated-probesprobes)(set!probes0)(definebranch-value(actual-valueselected-branchglobal-environment))(list(list'unusedunused-valueunused-probes)(list'duplicatedduplicated-valueduplicated-probes)(list'branchbranch-valueprobes)))
compound 적용이 list-of-delayed-args로 들어가는 지점을 따라가고 첫 조회가 thunk를 force-it으로 보내는 곳을 찾으세요. 레코드가 evaluated-thunk로 바뀌고 둘째 조회가 저장한 값을 돌려주는지 확인하세요. if에서는 술어와 선택한 consequent만 평가되는지 보세요.
직접 해보기
프로그램을 수정하고 결과를 비교해 보세요.
((lambda (x) (+ x (+ x x))) (probe 4))를 추가하고 값과 probe 횟수를 예상하세요. 그 다음 force-it의 thunk 변경을 제거하고 비교하세요.
힌트 보기
메모이즈하면 x 조회 횟수와 관계없이 probe는 한 번입니다. 변경하지 않으면 매 조회가 저장한 식을 다시 평가합니다.