함수 인수의 지연 평가

버그 보고

이 페이지에 문제가 있는 경우 여기를 클릭하여 버그질라 문제를 만듭니다.
이 페이지 개선

이 페이지에 대한 끌어오기 요청을 빠르게 포크하고 온라인으로 편집하고 제출합니다.로그인한 깃허브 계정이 필요합니다. 이 작은 변화를 위해 잘 작동합니다.더 크게 변경하려는 경우 로컬 복제본 사용을 고려할 수 있습니다.

월터 브라이트, http://www.digitalmars.com/d

지연 평가는 표현식을 평가하지 않고 표현식의 결과가 필요할 때까지 평가하지 않는 기술입니다.&&,|/및?:연산자는 기존의 방법 할일 게으른 평가입니다:

void test(int* p){ if (p && p) ...}

두 번째 식은 계산되지 않습니다.두 번째 표현식이 지연 평가되지 않은 경우 런타임 오류를 생성합니다.

매우 중요하지만 게으른 평가 연산자는 중요한 제한이 있습니다. 메시지를 기록하고 전역 값에 따라 런타임에 설정 및 해제할 수 있는 로깅 함수를 고려하십시오:

void log(const(char) message){ if (logging) fwritefln(logfile, message);}

종종 메시지 문자열은 런타임에 생성됩니다:

void foo(int i){ log("Entering foo() with i set to " ~ toString(i));}

이 기능이 작동하는 동안 문제는 로깅 사용 여부에 관계없이 메시지 스트링의 빌드가 발생한다는 것입니다.로깅을 많이 사용하는 응용 프로그램을 사용하면 성능이 크게 저하 될 수 있습니다.

이 문제를 해결하는 한 가지 방법은 게으른 평가를 사용하는 것입니다:

void foo(int i){ if (logging) log("Entering foo() with i set to " ~ toString(i));}

그러나 이는 로깅 세부 사항을 사용자에게 노출시켜 캡슐화 원칙을 위반합니다. 에 씨,이 문제는 종종 매크로를 사용하여 작동합니다:

#define LOG(string) (logging && log(string))

그러나 그 문제에 대한 단지 논문. 전처리기 매크로에는잘 알려진 단점:

  • 로깅 변수는 사용자의 네임스페이스에 노출됩니다.
  • 매크로는 기호 디버거에 보이지 않습니다.
  • 매크로는 전역 전용이며 범위를 지정할 수 없습니다.
  • 매크로는 클래스 멤버가 될 수 없습니다.
  • 매크로는 주소를 사용할 수 없으므로 함수와 같이 간접적으로 전달할 수 없습니다.

강력한 솔루션은 함수 매개 변수의 게으른 평가를 수행하는 방법입니다. 이 방법은 대리자 매개 변수를 사용하여 프로그래밍 언어에서 가능합니다:

void log(const(char) delegate() dg){ if (logging) fwritefln(logfile, dg());}void foo(int i){ log( { return "Entering foo() with i set to " ~ toString(i); });}

이제 문자열 작성 식은 로그가 참이고 캡슐화가 유지되는 경우에만 평가됩니다. 유일한 문제는 소수의 표현식을{반환 특급;}으로 래핑하고 싶다는 것입니다.

그래서 디는 작지만 중요한 한 단계 더 나아 간다(안드레이 알렉산드 레스 쿠 제안).모든 표현식은 무효 또는 식의 형식을 반환하는 대리자로 암시적으로 변환될 수 있습니다.대리자 선언은 지연 저장소 클래스로 대체됩니다.다음 기능은 다음과 같습니다:

void log(lazy const(char) dg){ if (logging) fwritefln(logfile, dg());}void foo(int i){ log("Entering foo() with i set to " ~ toString(i));}

로깅이 설정되어 있지 않으면 문자열이 구성되지 않는다는 점을 제외하면 원래 버전입니다.

코드에서 반복되는 패턴이 보일 때마다 그 패턴을 추론하고 캡슐화할 수 있다는 것은 코드의 복잡성과 버그를 줄일 수 있다는 것을 의미합니다. 가장 일반적인 예가 함수입니다.그 자체.게으른 평가는 다른 패턴의 호스트의 캡슐화를 가능하게합니다.

간단한 예제의 경우 식을 계산한 횟수라고 가정합니다. 패턴은:

for (int i = 0; i < count; i++) exp;

이 패턴은 지연 평가를 사용하여 함수에 캡슐화 할 수 있습니다:

void dotimes(int count, lazy void exp){ for (int i = 0; i < count; i++) exp();}

그것은 같이 사용될 수 있습니다:

void foo(){ int x = 0; dotimes(10, write(x++));}

어느 것이 인쇄 할 것인가:

0123456789

보다 복잡한 사용자 정의 제어 구조가 가능합니다.다음은 구조와 같은 스위치를 만드는 방법입니다:

bool scase(bool b, lazy void dg){ if (b) dg(); return b;}/* Here the variadic arguments are converted to delegates in this special case. */void cond(bool delegate() cases ...){ foreach (c; cases) { if (c()) break; }}

같이 사용될 수 있는지 어느 것이:

void foo(){ int v = 2; cond ( scase(v == 1, writeln("it is 1")), scase(v == 2, writeln("it is 2")), scase(v == 3, writeln("it is 3")), scase(true, writeln("it is the default")) );}

어느 것이 인쇄 할 것인가:

it is 2

리스프 프로그래밍 언어에 익숙한 사람들은 리스프 매크로와 몇 가지 흥미로운 유사점을 발견하게 될 것이다.

마지막 예제는 공통 패턴이 있습니다:

Abc p;p = foo();if (!p) throw new Exception("foo() failed");p.bar(); // now use p

던지기는 표현식이 아닌 문이기 때문에,이 작업을 수행해야하는 표현식은 여러 문으로 나눌 필요가 있으며 추가 변수가 도입됩니다.(이 문제에 대한 철저한 치료를 위해 안드레이 알렉산드 레스 쿠 및 페트 루 마네 아안의 논문 병력).게으른 평가를 사용하면 모두 단일 함수로 캡슐화 할 수 있습니다:

Abc Enforce(Abc p, lazy const(char) msg){ if (!p) throw new Exception(msg()); return p;}

위의 첫 번째 예는 다음과 같습니다:

Enforce(foo(), "foo() failed").bar();

그리고 5 줄의 코드가 하나가됩니다. 적용은 템플릿 기능을 사용하여 개선 할 수 있습니다:

T Enforce(T)(T p, lazy const(char) msg){ if (!p) throw new Exception(msg()); return p;}

결론

함수 인수의 게으른 평가는 함수의 표현력을 극적으로 확장합니다. 이것은 수학적으로 정확한 유형 계층구조인,강력한 타입을 정의합니다.

감사의 글

나는 안드레이 알렉산드레 쿠,바르토스 밀레프스키,데이비드의 영감과 도움을 감사하게 생각한다. 이 커뮤니티는 많은 건설적인 비판과 함께 많은 도움을 주었다.

답글 남기기

이메일 주소는 공개되지 않습니다.