NewLisp에서 익명 함수 반복

익명 함수는 많은 언어에서 다음과 같이 표현된다.
(lambda (n)
  (* (+ n 1) (- n 1)))

매개 변수 목록과 함수체만 있고 이름은 없습니다.대부분의 경우 문제가 없지만, 귀환이 필요하면, 귀환을 어떻게 하는지 모르는 익명 함수를 호출해야 하기 때문에 좀 번거롭다.
학술계에서 이 문제를 해결하는 방법이 있는데 그 중 하나가 Y조합자이다. 그러나 그것은 너무 번거롭고 매크로를 통해 하나의 lambda를 귀속 가능한 형식으로 바꾸기 어려워서 좋을 것이 없다.
역사적 경험에 따르면 현재 가장 좋은 방법은 하나의 조작부호를 실현하는 것이다. 익명 함수는 이 조작부호를 통해 자신을 호출한다.
(lambda (n) ... (this(-n 1)) 또는 (lambda(n)...(lambda (- n 1)))
첫 번째는this나 다른 것으로 현재 익명 함수 자체를 표시하고 직접 호출하면 귀환할 수 있다.두 번째는 유명한 함수와 마찬가지로 익명 함수를 정의하는 것과 같은 조작부호로 자신을 호출하는 것이다.
그러나 두 번째는 비현실적이다. 왜냐하면 이렇게 하면 혼란을 초래할 수 있다. 예를 들어 lambda를 끼워야 할 때 그 의미도 맞지 않는다.
그래서 이 글은 주로 첫 번째 방식을 중심으로 한다.this가 현재 익명 함수를 가리키도록 하고 자신을 호출할 수 있도록 한다.
New Lisp는 Lisp 언어의 구현이자 사투리라고도 할 수 있는데 Common Lisp에 비해 많은 것이 적지만 Common Lisp보다 훨씬 사용하기 쉽다.Lisp 시리즈의 언어는 문법이 없다는 특징이 있다.또는 극소 문법,Lisp 컴파일러로 문법 단계가 아예 없고 의미부터 시작하기 때문에 컴파일러에 매우 가깝다.Lisp는 다중 프로그래밍 언어로 명령식, 함수식, 대상 등을 지원한다. 물론 Lisp는 목록 처리 언어라고 불러야 한다.또는 추상적 문법 트리 처리 언어.원본 프로그램은 바로 AST입니다. AST를 디자인하여 소프트웨어를 구축합니다.
우선 우리는 가장 간단한 방법을 찾은 후에 점차적으로 개선할 것이다.
익명 함수 내부에서 국부 함수를 정의하고 이 함수를 호출하면 자신에게 귀속될 수 있다.예를 들어 정의할 귀속 익명 함수에서 하나의 함수를 주체 함수로 정의하고 이 함수를 호출하여 귀속 효과를 실현한다.
대략적인 형식은 다음과 같다.
(lambda (n)
  (define this (n) (if (< n 2) 1 (* n (this (- n 1)))))
  (this n))

익명 함수를 반환할 수 있는 매크로를 정의할 수 있습니다.
(define-macro
  (lambda* _args)
  (letex ((fargs _args) 
          (fbody (cons 'begin $args))
          (fcall (cons 'this (flat _args))))
    (lambda
      fargs
      (define (this fargs) fbody)
      fcall)))

이렇게 하면 lambda*만 사용하면 귀속 가능한 익명 함수를 정의할 수 있습니다.
;  this 
(lambda* (n) (if (< n 2) 1 (* n (this (- n 1)))))

그러나 이 정의의 함수는 이름 공간을 오염시키고 서로 다른 lambda*는this를 덮어씁니다. 왜냐하면 define는 전역에서 정의된 것이기 때문입니다.Common Lisp에서는 함수 내부에만 표시되는 네스트된 함수를 정의하여 해결할 수 있습니다.
(defmacro re-lambda (&rest body)
  `(lambda (&rest args)
     (labels (,(cons 'this body))
        (apply #'this args))))

그러나 NewLisp에서 함수 내부에서만 볼 수 있는 플러그인 함수를 어떻게 정의하는지 찾지 못했기 때문에 다른 방법을 통해 해결해야 한다.
그래서 나는 함수 이름을 자동으로 생성하는 방식을 생각했다. NewLisp에는 함수(time-of-day)가 하나 있고 비교적 정확한 시간 스탬프를 되돌려준다.
> (time-of-day)
66359119.140
> (time-of-day)
66359415.039

이렇게 하면 매크로를 확장할 때 함수 이름을 자동으로 생성하여 충돌을 방지할 수 있습니다.
 (define-macro
  (lambda* _args)
  (let ((f (string "$."(time-of-day)))) ; 
    (eval
      (list
        'define
        (cons (sym f) (flat _args))
        (list
          'let
          (list (list 'this (sym f))) ; this 
          (cons 'begin $args))))))

따라서 lambda*로 정의할 때, $.66789291.992, $.66922513.671 등 시간마다 다른 이름이 자동으로 생성되며, 거의 중복되지 않습니다.
그러나 이렇게 해서는 안 된다. 단지 피할 수 있는 명칭 충돌일 뿐이지만 명칭 공간을 오염시킬 수 있고, 어떤 상황에서는 여전히 예측하기 어려운 문제를 초래할 수 있기 때문이다.그래서 마지막으로 최종 버전의 lambda*를 설계했는데 아무런 부작용도 없었다.
최종 버전
(define-macro
  (lambda* _args)
  (letex ((fargs _args) 
          (fbody (cons 'begin $args))
          (fcall (cons 'this (flat _args))))
    (lambda
      fargs
      (let ((this (lambda fargs fbody)))
        fcall))))

매크로를 확장할 때 lambda 내부에서 하나의 lambda를 주체 함수로 정의하고let을 사용하여 국부 변수this를 이 주체 함수로 가리키기 때문에this를 통해 자신을 시뮬레이션할 수 있다. 함수는 이름이 필요 없고 지향 함수만 있는 국부 변수this만 있으면 효과가 매우 좋다.
4
;  
(lambda* (n)
  (if (< n 2)
    1
    (* n (this (- n 1)))))

;  =>
(lambda (n)
  (let ((this
        (lambda (n)
          (begin
            (if (< n 2)
              1
              (* n (this (- n 1))))))))
    (this n)))
테스트 결과:
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 1)
1
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 2)
2
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 3)
6
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 4)
24
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 5)
120
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 20)
2432902008176640000
> this
nil
> (setf this 100)
100
> ((lambda* (n) (if (< n 2) 1 (* n (this (- n 1))))) 10)
3628800
> this
100

* Lisp의 매크로를 쉽게 시도하지 마라. 뉴 Lisp의 매크로를 쉽게 시도하지 마라. 전자는 상처를 입고, 후자는 C++ 템플릿 오류를 조사하는 것과 같은 과정을 느낄 수 있으며, 심지어는 더욱 폭발할 수도 있다.
농업계의 한 용어로 말하자면 원자탄 같다.

좋은 웹페이지 즐겨찾기