2017-02-21 3 views
1

Common Lisp는 비파괴적인 기능 (subst & 제거와 같은)과 파괴적인 기능을 제공하고 (& rotatef와 같은) 매크로를 수정하여 일반 용도로 사용하는 것처럼 보입니다. 이것은 아마도 프로그래밍의 기능적 스타일과 비 기능적 스타일을 효과적으로 지원하기위한 것입니다. 그러나 유비 쿼터스의 디자인에서 비 기능적 스타일을 향한 특별한 편견이있는 것 같다. setf. 일반화 된 참조를 통합하는 setf 매크로는 명백히 지정 가능한 위치를 수정하기에 충분히 유연합니다 (불변의 정수 및 문자는 제외). 이 힘은 비 기능적/파괴적 행동에도 불구하고 널리 퍼진 사용을 설명합니다. setf 후 패턴 대응 "기능적인 스타일"비파괴 연산자가없는 이유비파괴 세트?

문제는, (set 이미 가지고 있기 때문에, 그것을 넣어 전화) 기타 파괴/비파괴 혀짤배기 운영자 쌍과 유사하다. 그러한 연산자는 아마도 장소와 값이 같은 인수를 취할 것이지만 그 위치에 새로운 값 대신에 장소가 삽입 된 객체의 사본을 반환합니다. 또한 표준 복사를 사용하기 위해 간단하게 복사를 수정하는 표준 setf과 함께 일종의 범용 복사기가 필요할 것입니다. 그런 다음 비파괴 연산자를 setf 대신 사용할 수 있습니다. setf은 큰 개체에 예약되어 있습니다. 범용 복사기에 대한 (추정 된) 요구 사항과 그것에 내장 된 임의의 장소에서 물체를 복구해야하는 필요성을 감안할 때 그러한 운영자는 실현 가능합니까 (아니면 가능합니까?)?

답변

2

일반적인 설정 도구는 없지만 SETF를 사용하면 DEFINE-SETF-EXPANDER을 사용할 때 하나를 제공해야합니다. 나는 당신이 lenses/functional references의 등가물을 정의 할 수 있다고 가정합니다. 또 다른 접근 방식으로 Clojure는 update 함수를 정의합니다 (다른 언어에서 빌려 왔지만 Prolog에 있음을 알고 있습니다). FSET 라이브러리는 일부 기능적 구조, 특히 Clojure 에서처럼 작동하는 연관지도를 제공합니다.

잠시 동안 가정 당신은 구조에 자신을 제한 :

(defstruct foo a b c) 
(defstruct bar x y z) 

(defparameter *test* 
    (make-foo :a (make-bar :x 0 :y 0 :z 0) 
      :b 100 
      :c "string")) 

;; *test* is now 
;; #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string") 

다음 방법은 사본을 만들고,이 표준은 아니지만 well supported 인, copy-structureslot-value에 의존 :

(defun update (structure keys value) 
    (if (endp keys) 
     value 
     (destructuring-bind (key &rest keys) keys 
     (let ((copy (copy-structure structure))) 
      (setf (slot-value copy key) 
       (update (slot-value copy key) 
         keys 
         value)) 
      copy)))) 

*test*을 다음과 같이 업데이트 할 수 있습니다.

(update *test* '(a z) 10) 

여기에 추적입니다 :

0: (UPDATE #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string") (A Z) 10) 
    1: (UPDATE #S(BAR :X 0 :Y 0 :Z 0) (Z) 10) 
    2: (UPDATE 0 NIL 10) 
    2: UPDATE returned 10 
    1: UPDATE returned #S(BAR :X 0 :Y 0 :Z 10) 
0: UPDATE returned #S(FOO :A #S(BAR :X 0 :Y 0 :Z 10) :B 100 :C "string") 

당신이 부분적으로 (#'1+와 결과 폐쇄를 업데이트 기능을 적용하여 incf에 해당 구현할 수 있도록, 당신이 대신 값의 함수를 받아 들일 수있는 일반화하기 위해, 의 목록을 수락합니다.

또한 일반 기능에서 가능한 복사본 작업을 일반화해야합니다. 마찬가지로 다른 종류의 접근자를 사용하고 slot-valueaccess 함수 ((setf access) 연산이 있음)로 바꿀 수 있습니다. 그러나 일부 데이터를 공유하려는 경우이 일반적인 복사/설정 방법은 낭비 적이 될 수 있습니다. 예를 들어, 데이터를 이끌어내는 목록의 일부만 복사하면됩니다. 단락 뒤의 단락 셀은 복사 할 필요가 없습니다.

당신은 사용자 정의 업데이터를 정의하는 몇 가지 기능을 정의 할 수 있습니다 : 당신이 상기 CLOS 객체 리더/라이터를 쓴 것처럼

(defmethod perform-update (new (list list) position) 
    (nconc (subseq list 0 position) 
     (list new) 
     (nthcdr (1+ position) list))) 
+0

그럼 CLAS 인스턴스를 제외하고 기본 lisp 객체로 복사하는 것을 제한한다고 가정하면 보편적 인 복사 아이디어의 핵심은 기본 객체의 각 유형에 대한 재귀 일반 메소드를 작성하는 것입니다. – davypough

+0

@davypough 예, 기본 범용 복사본은 데이터 구조의 모든 구성 요소의 복사본을 만들 수 있습니다 (단 하나의 복사본 수준 만 필요합니다. 위의 업데이트 함수는 필요한 경우 재귀 복사본을 수행함). 순환 참조에 문제가 있음을 유의하십시오. – coredump

6

Common Lisp에는 CLOS objects의 내장 인쇄 가능 표현이 없습니다 (예 : Saving CLOS objects 참조) : MOP의 출력으로 범용 복사기가 없습니다.

특히 개체 생성에는 복제가 보장하기 어려운 임의의 부작용이있을 수 있습니다. 예를 들어 유체에 따라 슬롯을 수정하려면 클래스에 initialize-instance을 정의하십시오 (예 : 조수 또는 random). 그러면과 같은 인수가 과 같이 호출 된 make-instance의 결과는 과 다른이 될 수 있습니다. 이것이 memcpy 스타일의 일반 복사기를 선호하는 주장이라고 주장 할 수 있습니다. 그러나 이제 인스턴스 계산을 수행하는 클래스 (인스턴스에 고유 ID를 매핑하는 해시 테이블을 포함하는 :allocation :class 슬롯)를 상상해보십시오. 이제 테이블을 통해 복사 된 객체에서 왕복 이동은 개체 (원본이 아닌 복사본)가됩니다. 이는 주요 계약 위반입니다. 그리고이 예제들은 빙산의 일각에 불과합니다.

그러나 Common Lisp는 기능 스타일보다는 명령형 스타일을 권장한다는 데 동의하지 않습니다. 단순히 설명하는 스타일을 낙담시킬뿐입니다 (복사 + setf). 이 방법으로 생각해보십시오. 기능적 POV에서 복사가 원본과 구별되지 않습니다 (모든 것이 변경 불가능하기 때문입니다).

기능 스타일에 대한 명확한 선호도를 보여주는 "작은 힌트"가 있습니다. "자연"이름이 (예 : append)은 은 비파괴 함수로 지정됩니다. 파괴적인 버전 (예 : nconc)에는 이라는 모호한 이름이 지정됩니다. (ratiocomplex과 같은 복합물을 포함)

또한, pathnames, charactersnumbers 는 불변, 즉 아르모든 경로명과 숫자 함수는 새로운 객체 (기능적 스타일)를 생성합니다.

따라서, IMO, 커먼 리스프는 "간단한 일이 쉬워야한다, 어려운 일이 가능해야한다"여전히 필수적 스타일이 가능하면서 슬로건에 준수하여, 기능적인 스타일을 사용하는 프로그래머를 권장합니다.

Kent Pitman's writeup도 참조하십시오.

+0

것 같습니다. (고맙습니다!) 특정 종류의 객체에 대한 제한의 좋은 예가 있습니까? – davypough

+0

달의 위상에 따라 오브젝트를 수정하는'make' 메소드를'make-instance'에 추가하십시오. 그러면 저장 한 객체는 읽은 객체와 다릅니다. – sds