파이썬과 같이 동적으로 유형이 지정된 언어에서만 가능한 디자인 패턴이 있습니까?


30

관련 질문을 읽었습니다. Python과 같은 동적 언어에서 불필요한 디자인 패턴이 있습니까? Wikiquote.org 에서이 인용구 기억했습니다

동적 타이핑의 멋진 점은 계산 가능한 모든 것을 표현할 수 있다는 것입니다. 유형 시스템은 그렇지 않습니다. 유형 시스템은 일반적으로 결정 가능하며 하위 집합으로 제한합니다. 정적 유형 시스템을 선호하는 사람들은“괜찮습니다. 충분합니다. 여러분이 작성하고 싶은 흥미로운 프로그램은 모두 유형으로 작동합니다.” 그러나 그것은 우스운 일입니다. 일단 유형 시스템을 갖추면 흥미로운 프로그램이 무엇인지조차 알 수 없습니다.

--- 소프트웨어 엔지니어링 라디오 에피소드 140 : Gilad Bracha를 사용한 뉴스 피크 및 플러그 가능 유형

따옴표 형식을 사용하여 "유형으로 작동하지 않는"유용한 디자인 패턴이나 전략이 있는지 궁금합니다.


3
이중 디스패치 및 방문자 패턴은 정적으로 유형이 지정된 언어에서는 달성하기가 매우 어렵지만 동적 언어에서는 쉽게 달성 할 수 있음을 발견했습니다. 이 답변 (및 질문)을 참조하십시오 : programmers.stackexchange.com/a/288153/122079
user3002473

7
당연하지. 예를 들어 런타임에 새 클래스를 만드는 모든 패턴. (이것은 Java에서도 가능하지만 C ++에서는 불가능합니다. 활력의 슬라이딩 스케일이 있습니다).
user253751

1
타입 시스템이 얼마나 정교한 지에 따라 크게 달라질 것입니다.
Bergi

1
모두 Haskell 또는 OCaml 대신 Java 및 C #과 같은 유형 시스템을 말하는 것 같습니다. 강력한 유형 시스템을 사용하는 언어는 동적 언어만큼 간결하지만 유형 안전을 유지할 수 있습니다.
앤드류는 0

@immibis 잘못되었습니다. 정적 타입 시스템은 런타임에 새로운 "동적"클래스를 생성 할 수 있습니다. 프로그래밍 언어를위한 실용적인 기초 33 장을 참조하십시오.
gardenhead

답변:


4

일류 타입

동적 타이핑은 일류 유형을 의미합니다. 언어 자체 유형을 포함하여 런타임에 유형을 검사, 작성 및 저장할 수 있습니다. 또한 변수 가 아니라 이 입력 됨을 의미 합니다 .

정적으로 유형이 지정된 언어는 메소드 디스패치, 유형 클래스 등과 같이 동적 유형에 의존하지만 일반적으로 런타임에 보이지 않는 방식으로 코드를 생성 할 수 있습니다. 기껏해야 그들은 검사를 수행 할 수있는 방법을 제공합니다. 또는 유형을 값으로 시뮬레이션 할 수 있지만 임시 동적 유형 시스템이 있습니다.

그러나 동적 유형 시스템에는 일류 유형 만있는 경우는 거의 없습니다 . 당신은 일류 심볼, 일류 패키지, 일류 .... 모든 것을 가질 수 있습니다. 이는 정적 언어의 컴파일러 언어와 런타임 언어 간의 엄격한 분리와 대조됩니다. 컴파일러 나 인터프리터가 런타임으로 할 수있는 일도 가능합니다.

자, 타입 추론이 좋은 것이고 코드를 실행하기 전에 검사하고 싶다는 것에 동의합시다. 그러나 나는 런타임에 코드를 생성하고 컴파일 할 수있는 것을 좋아합니다. 그리고 컴파일 타임에도 미리 계산하는 것을 좋아합니다. 동적으로 입력 된 언어에서는 동일한 언어로 수행됩니다. OCaml에는 기본 유형 시스템과 다른 모듈 / 기능 유형 시스템이 있으며 전 처리기 언어와 다릅니다. C ++에는 기본 언어와는 아무런 관련이없는 템플릿 언어가 있으며 일반적으로 실행 중 형식을 무시합니다. 그리고 더 많은 것을 제공하고 싶지 않기 때문에 그 언어로는 괜찮 습니다.

궁극적으로, 그것은 어떤 종류의 소프트웨어를 개발할 수 있는지를 바꾸지는 않지만 표현력은 소프트웨어를 개발 하는 방법 과 어려운지 여부를 변경 합니다 .

패턴

동적 유형에 의존하는 패턴은 개방형 클래스, 디스패치, 메모리의 객체 데이터베이스, 직렬화 등과 같은 동적 환경과 관련된 패턴입니다. 벡터가 런타임에 보유하는 객체의 유형에 대해 잊어 버리지 않기 때문에 일반 컨테이너와 같은 간단한 작업 (매개 변수 유형이 필요 없음).

가능한 정적 분석의 예뿐만 아니라 Common Lisp에서 코드가 평가되는 많은 방법을 소개하려고 시도했습니다 (이것은 SBCL입니다). 샌드 박스 예제 는 별도의 파일에서 가져온 Lisp 코드의 작은 하위 집합을 컴파일 합니다. 합리적으로 안전하기 위해 읽기 가능 테이블을 변경하고 표준 심볼의 하위 집합 만 허용하고 시간 초과로 사물을 래핑합니다.

;;
;; Fetching systems, installing them, etc. 
;; ASDF and QL provide provide resp. a Make-like facility 
;; and system management inside the runtime: those are
;; not distinct programs.
;; Reflexivity allows to develop dedicated tools: for example,
;; being able to find the transitive reduction of dependencies
;; to parallelize builds. 
;; https://gitlab.common-lisp.net/xcvb/asdf-dependency-grovel
;;
(ql:quickload 'trivial-timeout)

;;
;; Readtables are part of the runtime.
;; See also NAMED-READTABLES.
;;
(defparameter *safe-readtable* (copy-readtable *readtable*))
(set-macro-character #\# nil t *safe-readtable*)
(set-macro-character #\: (lambda (&rest args)
                           (declare (ignore args))
                           (error "Colon character disabled."))
                     nil
                     *safe-readtable*)

;; eval-when is necessary when compiling the whole file.
;; This makes the result of the form available in the compile-time
;; environment. 
(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar +WHITELISTED-LISP-SYMBOLS+ 
    '(+ - * / lambda labels mod rem expt round 
      truncate floor ceiling values multiple-value-bind)))

;;
;; Read-time evaluation #.+WHITELISTED-LISP-SYMBOLS+
;; The same language is used to control the reader.
;;
(defpackage :sandbox
  (:import-from
   :common-lisp . #.+WHITELISTED-LISP-SYMBOLS+)
  (:export . #.+WHITELISTED-LISP-SYMBOLS+))

(declaim (inline read-sandbox))

(defun read-sandbox (stream &key (timeout 3))
  (declare (type (integer 0 10) timeout))
  (trivial-timeout:with-timeout (timeout)
    (let ((*read-eval* nil)
          (*readtable* *safe-readtable*)
          ;;
          ;; Packages are first-class: no possible name collision.
          ;;
          (package (make-package (gensym "SANDBOX") :use '(:sandbox))))
      (unwind-protect
           (let ((*package* package))
             (loop
                with stop = (gensym)
                for read = (read stream nil stop)
                until (eq read stop)
                ;;
                ;; Eval at runtime
                ;;
                for value = (eval read)
                ;;
                ;; Type checking
                ;;
                unless (functionp value)
                do (error "Not a function")
                ;; 
                ;; Compile at run-time
                ;;
                collect (compile nil value)))
        (delete-package package)))))

;;
;; Static type checking.
;; warning: Constant 50 conflicts with its asserted type (MOD 11)
;;
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in :timeout 50)))

;; get it right, this time
(defun read-sandbox-file (file)
  (with-open-file (in file)
    (read-sandbox in)))

#| /tmp/plugin.lisp
(lambda (x) (+ (* 3 x) 100))
(lambda (a b c) (* a b))
|#

(read-sandbox-file #P"/tmp/plugin.lisp")

;; 
;; caught COMMON-LISP:STYLE-WARNING:
;;   The variable C is defined but never used.
;;

(#<FUNCTION (LAMBDA (#:X)) {10068B008B}>
 #<FUNCTION (LAMBDA (#:A #:B #:C)) {10068D484B}>)

다른 언어와 관련하여 "불가능"한 것은 없습니다. 블렌더의 플러그인 접근 방식, 즉석에서 재 컴파일 등을 수행하는 정적으로 컴파일 된 언어의 음악 소프트웨어 또는 IDE의 플러그인 방식. 외부 도구 대신 동적 언어는 이미 존재하는 정보를 사용하는 도구를 선호합니다. FOO의 모든 알려진 발신자? BAR의 모든 하위 클래스? ZOT 클래스에 특화된 모든 메소드? 이것은 내재화 된 데이터입니다. 유형은 이것의 또 다른 측면 일뿐입니다.


( CFFI 참조 )


39

짧은 대답 : 아니오, 튜링 동등성 때문입니다.

긴 대답 :이 녀석은 트롤입니다. 유형 시스템이 "하위 집합으로 제한"하는 것이 사실이지만, 그 부분 집합 밖의 물건은 정의 상으로는 작동하지 않는 물건입니다.

Turing-complete 프로그래밍 언어 (범용 프로그래밍을 위해 설계된 언어와 그렇지 않은 많은 언어)로 할 수있는 모든 것; 정리하기에는 상당히 낮은 수준이며 Turing이되는 시스템의 몇 가지 예가 있습니다. 실수로 완료하십시오) 다른 Turing-complete 프로그래밍 언어로 할 수 있습니다. 이것을 "동등한 투어링"이라고하며 정확히 말한 것을 의미합니다. 중요한 것은 다른 언어로 쉽게 다른 일을 할 수 있다는 것을 의미하지는 않습니다. 어떤 사람들은 처음에는 새로운 프로그래밍 언어를 만드는 것이 전체 요점이라고 주장합니다. 기존 언어가 빨아 먹는 것들.

예를 들어, 모든 변수, 매개 변수 및 반환 값을 기본 Object유형 으로 선언 한 다음 리플렉션을 사용하여 특정 데이터에 액세스하여 정적 OO 유형 시스템에서 동적 유형 시스템을 에뮬레이션 할 수 있습니다. 정적 언어로는 할 수없는 동적 인 언어로는 할 수있는 것이 아무것도 없다는 것을 알 수 있습니다. 그러나 그렇게하는 것은 물론 큰 혼란입니다.

인용문의 사람은 정적 유형이 수행 할 수있는 작업을 제한한다는 것이 정확하지만 문제가 아닌 중요한 기능입니다. 도로의 줄은 차에서 할 수있는 일을 제한하지만 제한적이거나 도움이되는 것을 찾으십니까? (차가 반대 방향으로 가고 있고 운전하는 곳을 오지 않기 위해 반대 방향으로 가고 있다는 말이없는 바쁜 복잡한 도로에서 운전하고 싶지 않다는 것을 알고 있습니다!) 유효하지 않은 동작을 고려하여 발생하지 않도록하려면 심한 충돌이 발생할 가능성을 크게 줄입니다.

또한 그는 다른 쪽의 특성을 잘못보고 있습니다. "작성하려는 모든 흥미로운 프로그램이 유형으로 작동하는 것"이 ​​아니라 "작성하려는 모든 흥미로운 프로그램이 유형 을 필요로 합니다." 특정 수준의 복잡성을 넘어 서면 두 가지 이유로 인해 유형 시스템없이 코드베이스를 유지 관리하기가 매우 어려워집니다.

첫째, 타입 주석이없는 코드는 읽기 어렵 기 때문입니다. 다음 Python을 고려하십시오.

def sendData(self, value):
   self.connection.send(serialize(value.someProperty))

연결의 다른 쪽 끝에있는 시스템이받는 것처럼 보이는 데이터는 무엇입니까? 그리고 그것이 완전히 잘못 보이는 것을 받고 있다면, 무슨 일이 일어나고 있는지 어떻게 알 수 있습니까?

그것은 모두의 구조에 달려 value.someProperty있습니다. 그러나 그것은 어떻게 생겼습니까? 좋은 질문! 무슨 소리 야 sendData()? 통과하는 것이 무엇입니까? 그 변수는 어떻게 생겼습니까? 어디에서 왔습니까? 로컬이 아닌 경우 전체 기록 value을 추적하여 진행 상황을 추적해야합니다. 어쩌면 당신은 someProperty재산 이있는 다른 것을 전달하고 있지만 생각하는 것을하지 않습니다?

Boo 언어에서 볼 수 있듯이 매우 유사한 구문을 사용하지만 정적으로 유형이 지정된 유형 주석으로 살펴 보겠습니다.

def SendData(value as MyDataType):
   self.Connection.Send(Serialize(value.SomeProperty))

문제가 발생하면 갑자기 디버깅 작업이 훨씬 쉬워집니다. 정의를 찾아보십시오 MyDataType! 또한 같은 이름을 가진 속성을 가진 호환되지 않는 형식을 전달했기 때문에 나쁜 동작 을 일으킬 가능성이 있습니다. 유형 시스템은 실수를하지 않기 때문에 갑자기 0이됩니다 .

두 번째 이유는 첫 번째 이유입니다. 크고 복잡한 프로젝트에는 여러 기고자가 있습니다. (그렇지 않으면 오랜 시간에 걸쳐 직접 빌드하는 것입니다. 본질적으로 동일한 것입니다. 3 년 전에 작성한 코드를 믿지 않으면 읽어보십시오!) 코드를 작성할 당시에 코드의 거의 일부를 작성한 사람의 머리를 통하는 것입니다. 왜냐하면 당신이 없었거나 오래 전에 자신의 코드인지 기억하지 못하기 때문입니다. 형식 선언을 사용하면 코드의 의도가 무엇인지 이해하는 데 실제로 도움이됩니다!

인용문에 나오는 사람과 같은 사람들은 정적 입력의 이점을 거의 무제한의 하드웨어 리소스로 인해 매년마다 컴파일러의 도움 또는 효율성에 관한 모든 것으로 잘못 잘못 인식합니다. 그러나 내가 보았 듯이, 이러한 이점은 확실히 존재하지만 주요 이점은 인적 요소, 특히 코드 가독성 및 유지 관리 가능성에 있습니다. (추가 된 효율성은 확실히 좋은 보너스입니다!)


24
"이 녀석은 트롤입니다." – Ad Hominem 공격이 귀하의 잘 알려진 사례를 도울 것이라고 확신하지 않습니다. 저는 권위의 주장이 가정과 마찬가지로 나쁜 잘못이라는 것을 잘 알고 있지만 Gilad Bracha는 아마도 대부분의 언어보다 더 많은 언어와 (이 토론에 가장 관련성이 높은) 시스템을 설계했음을 지적하고 싶습니다. 단지 작은 발췌문 : 그는 Java 언어 사양 및 Java 가상 머신 사양의 공동 저자 인 Dart의 공동 설계자 인 Newspeak의 유일한 디자이너이며, Java 및 JVM의 설계에 대해 작업했습니다.
Jörg W Mittag

10
Strongtalk (Smalltalk의 정적 타입 시스템), Dart 타입 시스템, Newspeak 타입 시스템, 모듈화에 대한 그의 박사 학위 논문은 거의 모든 현대적인 모듈 시스템의 기초입니다 (예 : Java 9, ECMAScript 2015, Scala, Dart, Newspeak, Ioke , Seph), mixins에 관한 그의 논문은 우리가 생각하는 방식에 혁명을 일으켰습니다. 이제 그 않습니다 하지 그가 바로 것을 의미하지만, 내가 여러 정적 타입 시스템은 "트롤"보다 조금 더 그를하게 설계하는 데 있다고 생각합니다.
Jörg W Mittag

17
"유형 시스템이"집합을 서브 세트로 제한 "하는 것이 사실이지만, 그 서브 세트 밖의 항목은 정의 상으로는 작동하지 않는 것입니다." – 이것은 잘못이다. 우리는 정지 문제의 결정 불가능 성, 라이스 정리 및 기타 결정 불가능 및 계산 불가 결과를 통해 정적 유형 검사기가 모든 프로그램에 대해 유형 안전 또는 유형 안전하지 않은 프로그램인지 결정할 수 없음을 알고 있습니다. 해당 프로그램 (일부는 유형이 안전하지 않음)을 수락 할 수 없으므로 유일한 안전한 선택은 거부 하는 것입니다 (그러나 일부는 유형 안전합니다). 또는 언어는 다음과 같이 설계되어야합니다.
Jörg W Mittag

9
… 프로그래머가 그러한 결정 불가능한 프로그램을 작성하는 것을 불가능하게하는 방식이지만, 다시 말하지만 그 중 일부는 실제로 유형 안전합니다. 따라서 슬라이스 방식에 관계없이 프로그래머는 형식이 안전한 프로그램을 작성할 수 없습니다. 그리고 실제로 그들 중 많은 수가 (보통) 존재하기 때문에, 적어도 그들 중 일부 작동하는 것뿐만 아니라 유용하다는 것을 거의 확신 할 수 있습니다 .
Jörg W Mittag

8
@MasonWheeler : 정적 유형 검사와 관련하여 항상 정지 문제가 발생합니다. 프로그래머가 특정 종류의 프로그램을 작성하지 못하도록 언어를 신중하게 설계하지 않은 경우 정적 유형 검사는 중단 문제를 해결하는 것과 동일하게됩니다. 어느 쪽이든 당신은 유형 검사를 혼동 수도 있고, 당신은 당신이 프로그램을 끝낼 때문에 당신이 쓰기에 허용되지 않는 프로그램을 끝낼 되어 쓸 수 있지만, 유형 검사 시간의 무한한 시간이 걸릴.
Jörg W Mittag

27

나는 '패턴'부분을 회피 할 것이다. 왜냐하면 그것이 패턴이 무엇인지 아닌지에 대한 정의로 이어지고 그 토론에 대한 관심을 오랫동안 잃어 버렸다고 생각하기 때문이다. 내가 말할 것은 당신이 다른 언어로는 할 수없는 어떤 언어로 할 수있는 것이 있다는 것입니다. 나는 야, 내가 분명히하자 하지 가 말하는 당신이 해결할 수있는 문제가 다른에 해결할 수있는 하나 개의 언어에가. 메이슨은 이미 튜링 완전성을 지적했다.

예를 들어, XML DOM 요소를 감싸고 첫 번째 클래스 객체로 만드는 클래스를 파이썬으로 작성했습니다. 즉, 코드를 작성할 수 있습니다.

doc.header.status.text()

파싱 ​​된 XML 객체에서 해당 경로의 내용이 있습니다. 깔끔하고 깔끔한 IMO. 헤드 노드가 없으면 더미 객체 만 포함하는 더미 객체 만 반환합니다 (거북이를 거꾸로합니다). Java와 같은 실제 방법은 없습니다. XML 구조에 대한 지식을 바탕으로 클래스를 미리 컴파일해야합니다. 이것이 좋은 아이디어인지를 제외하고, 이런 종류의 일은 동적 언어로 문제를 해결하는 방법을 실제로 변화시킵니다. 그러나 그것이 항상 항상 더 좋은 방식으로 변한다고 말하지는 않습니다. 역동적 인 접근법에는 일정한 비용이 있으며 Mason의 대답은 적절한 개요를 제공합니다. 그들이 좋은 선택인지 여부는 많은 요소에 달려 있습니다.

보조 노트에, 당신은 할 수 있습니다 당신이 만들 수 있기 때문에 자바에서 이렇게 자바에서 파이썬 인터프리터 . 특정 언어로 특정 문제를 해결한다는 것은 사람들이 튜링 완전성에 대해 이야기 할 때 통역사 또는 이와 유사한 것을 만드는 것을 간과 할 수 있다는 사실입니다.


4
Java는 제대로 설계되지 않았기 때문에 Java에서이 작업을 수행 할 수 없습니다. C #에서는을 사용하는 것이 어렵지 않으며 IDynamicMetaObjectProviderBoo에서는 간단 하지 않습니다 . ( GitHub의 표준 소스 트리의 일부로 포함 된 100 줄 미만의 구현은 쉬운 일이기 때문입니다!)
Mason Wheeler

6
@MasonWheeler "IDynamicMetaObjectProvider"? 이것이 C #의 dynamic키워드 와 관련이 있습니까? ... 효과적으로 C #에 대한 동적 타이핑을 효과적으로 수행합니까? 내가 옳다면 당신의 주장이 유효한지 확실하지 않습니다.
jpmc26

9
@MasonWheeler 시맨틱에 빠지고 있습니다. (우리는 여기서 SE에 대한 수학적 형식을 개발하고 있지 않습니다.) 축소에 대한 토론에 참여하지 않고, 동적 타이핑은 유형에 대한 전술 한 컴파일 시간 결정, 특히 각 유형에 프로그램이 액세스하는 특정 멤버가 있는지 확인하는 관행입니다. 이것이 dynamicC #에서 달성 하는 목표입니다 . "반사 및 사전 조회"는 컴파일 타임이 아니라 런타임에 발생합니다. 언어에 동적 입력을 추가 하지 않는 경우를 어떻게 만들 수 있는지 잘 모르겠습니다 . 내 요점은 지미의 마지막 단락이 그것을 다룬다는 것입니다.
jpmc26

44
자바의 열렬한 팬이없는에도 불구하고, 나는 또한 호출하는 자바 "잘못 설계"라고 감히 구체적으로 는 동적 타이핑은 ... 지나치게 추가하지 않았기 때문에.
jpmc26

5
약간 더 편리한 구문 외에도 사전과 다른 점은 무엇입니까?
Theodoros Chatzigiannakis

10

인용문은 정확하지만 실제로는 불분명합니다. 이유를 알기 위해 세분화 해 봅시다.

동적 타이핑의 멋진 점은 계산 가능한 모든 것을 표현할 수 있다는 것입니다.

글쎄요 다이나믹 한 타이핑이 있는 언어 를 사용하면 튜링이 완료 되는 한 대부분 을 표현할 수 있습니다 . 타입 시스템 자체는 모든 것을 표현할 수 없습니다. 그래도 그에게 의심의 이익을 주자.

유형 시스템은 그렇지 않습니다. 유형 시스템은 일반적으로 결정 가능하며 하위 집합으로 제한합니다.

이것은 사실이지만, 우리는 타입 시스템 을 사용하는 언어 가 허용 하는 것이 아니라 타입 시스템이 허용 하는 것에 대해 확실히 이야기하고 있습니다. 컴파일 할 때 유형 시스템을 사용하여 물건을 계산하는 것이 가능하지만 일반적으로 튜링 완료 (타입 시스템이 일반적으로 결정 가능하므로)에서는 아니지만 거의 모든 정적으로 유형이 지정된 언어는 런타임에 튜링 완료입니다 (종속적으로 유형이 지정된 언어는 아닙니다, 그러나 우리가 여기에 대해 이야기하고 있다고 생각하지 않습니다).

정적 유형 시스템을 선호하는 사람들은“괜찮습니다. 충분합니다. 여러분이 작성하고 싶은 흥미로운 프로그램은 모두 유형으로 작동합니다.” 그러나 그것은 우스운 일입니다. 일단 유형 시스템을 갖추면 흥미로운 프로그램이 무엇인지조차 알 수 없습니다.

문제는 동적 유형 언어에 정적 유형이 있다는 것입니다. 때로는 모든 것이 문자열이며, 더 일반적으로 모든 것이 속성의 가방이거나 int 또는 double과 같은 값인 태그가 지정된 유니온이 있습니다. 문제는 정적 언어 가이 작업을 수행 할 수 있다는 것입니다. 역사적으로는이 작업을 수행하는 것이 약간 어색했지만 현대 정적 형식 언어는 동적 형식 언어를 사용하는 것처럼 쉽게 수행 할 수 있으므로 어떻게 다른 점이 있습니까? 프로그래머가 흥미로운 프로그램으로 볼 수있는 것은 무엇입니까? 정적 언어는 다른 유형뿐만 아니라 정확히 동일한 태그 조합을 갖습니다.

제목에있는 질문에 대답하려면 : 아니요. 정적으로 유형이 지정된 언어로 구현할 수없는 디자인 패턴은 없습니다. 항상 동적 시스템을 충분히 구현하여 얻을 수 있기 때문입니다. 다이나믹 한 언어로 '무료'로 얻을 수있는 패턴이있을 수 있습니다. 이것은 YMMV에 대한 언어의 단점을 따라 잡을 가치가 있거나 없을 수도 있습니다 .


2
네가 방금 예라고 대답했는지 확실하지 않습니다. 나에게 더없는 것 같습니다.
user7610

1
@TheodorosChatzigiannakis 예, 동적 언어는 어떻게 구현됩니까? 먼저, 동적 클래스 시스템 또는 기타 관련 요소를 구현하려는 경우 우주 비행사 건축가에게 전달됩니다. 둘째, 디버깅 가능하고 완벽하게 조사 가능하며 성능을 향상시킬 수있는 리소스가 없을 것입니다 ( "사전 사용"은 언어 구현 속도가 느립니다). 뿐만 아니라 라이브러리로, 전체 언어에 통합 될 때 셋째는, 일부 동적 기능을 가장 잘 사용됩니다 예를 들어, 가비지 컬렉션을 생각하는 (거기에 있는 라이브러리와 같은 GC를,하지만 그들은 일반적으로 사용되지 않음).
coredump

1
@Theodoros 내가 이미 한 번 링크 한 논문에 따르면, 2.5 %의 구조 (파이썬 모듈에서 연구 결과)는 유형화 된 언어로 쉽게 표현할 수 있습니다. 2.5 %가 동적 타이핑 비용을 지불 할 수도 있습니다. 그것은 본질적으로 내 질문에 관한 것입니다. neverworkintheory.org/2016/06/13/polymorphism-in-python.html
user7610

3
@JiriDanek 내가 알 수있는 한, 정적으로 입력 된 언어가 다형성 콜 스팟을 갖고 프로세스에서 정적 타이핑을 유지하는 것을 막는 것은 없습니다. 다중 방법의 정적 유형 확인을 참조하십시오 . 어쩌면 나는 당신의 링크를 오해하고 있습니다.
Theodoros Chatzigiannakis

1
"그것은 대부분이있는, 완전한 튜링으로 동적 입력을 가진 언어는 긴으로 아무것도 표현할 수 있습니다."이 과정 진정한 문입니다 있지만, 그것은하지 않습니다 정말 때문에 "현실 세계"에서 개최 양의 텍스트 하나의가있다 쓰기는 매우 클 수 있습니다.
Daniel Jour

4

동적으로 유형이 지정된 언어로만 수행 할 수있는 작업이 있습니다. 그러나 반드시 좋은 디자인 은 아닙니다 .

먼저 정수 5를 할당 한 다음 string 'five'또는 Cat객체를 동일한 변수에 할당 할 수 있습니다 . 그러나 코드 리더가 진행중인 작업, 모든 변수의 목적이 무엇인지 파악하기가 더 어려워지고 있습니다.

라이브러리 Ruby 클래스에 새 메소드를 추가하고 전용 필드에 액세스 할 수 있습니다. 이러한 해킹이 유용 할 수 있지만 캡슐화를 위반하는 경우가있을 수 있습니다. (공개 인터페이스에만 의존하는 메서드를 추가하는 것은 마음에 들지 않지만 정적으로 형식이 지정된 C # 확장 메서드는 할 수없는 것은 아닙니다.)

다른 클래스의 객체에 새 필드를 추가하여 추가 데이터를 전달할 수 있습니다. 그러나 새로운 구조를 만들거나 원래 유형을 확장하는 것이 더 좋습니다.

일반적으로 코드를 더 체계적으로 유지하려는 경우 유형 정의를 동적으로 변경하거나 다른 유형의 값을 동일한 변수에 할당 할 수 있다는 이점이 줄어 듭니다. 그러나 코드는 정적으로 유형이 지정된 언어로 달성 할 수있는 것과 다르지 않습니다.

동적 언어가 좋은 것은 구문 설탕입니다. 예를 들어, 역 직렬화 된 JSON 객체를 읽을 때 단순히 중첩 된 값을 obj.data.article[0].content말보다 훨씬 더 깔끔하게 참조 할 수 있습니다 obj.getJSONObject("data").getJSONArray("article").getJSONObject(0).getString("content").

루비 개발자는 특히을 구현하여 달성 할 수있는 마법에 대해 오랫동안 이야기 할 수있었습니다 method_missing. 예를 들어, ActiveRecord ORM은 메소드를 User.find_by_email('joe@example.com')선언하지 않고 전화를 걸 수 있도록이를 사용합니다 find_by_email. 물론 UserRepository.FindBy("email", "joe@example.com")정적으로 유형이 지정된 언어에서 와 같이 달성 할 수 없었던 것은 아니지만 그 깔끔함을 부정 할 수는 없습니다.


4
정적으로 유형이 지정된 언어로만 수행 할 수있는 작업이 있습니다. 그러나 반드시 좋은 디자인은 아닙니다.
coredump

2
구문 설탕에 대한 요점은 동적 타이핑 및 구문과 관련된 모든 것과 거의 관련이 없습니다.
leftaroundabout

@leftaroundabout 패턴은 구문과 관련이 있습니다. 타입 시스템도 이와 관련이 있습니다.
user253751

4

동적 프록시 패턴은 프록시해야하는 유형 당 하나의 클래스가 없어도 프록시 객체를 구현하기위한 바로 가기입니다.

class Proxy(object):
    def __init__(self, obj):
        self.__target = obj

    def __getattr__(self, attr):
        return getattr(self.__target, attr)

이를 사용 Proxy(someObject)하여과 동일하게 동작하는 새 객체를 만듭니다 someObject. 분명히 당신은 어떻게 든 추가 기능을 추가하고 싶겠지 만, 이것은 처음부터 유용한 기초입니다. 완전한 정적 언어에서는 프록시하려는 유형별로 하나의 프록시 클래스를 작성하거나 동적 코드 생성을 사용해야합니다 (확실히 많은 정적 언어의 표준 라이브러리에 포함되어 있음). 이 원인을 해결할 수없는 문제).

동적 언어의 또 다른 사용 사례는 소위 "원숭이 패치"입니다. 여러 가지면에서 이것은 패턴이 아니라 안티 패턴이지만 주의해서 수행하면 유용한 방법으로 사용될 있습니다. 그리고 원숭이 패치가 정적 언어로 구현 될 수 없는 이론적 인 이유는 없지만 실제로는 그것을 가지고있는 것을 본 적이 없습니다.


Go에서 이것을 모방 할 수 있다고 생각합니다. 모든 프록시 객체에 있어야하는 방법이 있습니다 (그렇지 않으면 오리가 흔들리지 않고 모두 떨어져 나갈 수 있음). 이 방법으로 Go 인터페이스를 만들 수 있습니다. 나는 그것에 대해 더 많이 생각해야하지만, 내가 생각한 것이 효과가 있다고 생각합니다.
user7610

RealProxy 및 제네릭을 사용하여 모든 .NET 언어에서 비슷한 것을 할 수 있습니다.
LittleEwok

@LittleEwok-RealProxy는 런타임 코드 생성을 사용합니다. 제가 말했듯이 많은 현대 정적 언어에는 이와 같은 해결 방법이 있지만 동적 언어에서는 여전히 쉽습니다.
Jules

C # 확장 방법은 안전한 원숭이 패치와 비슷합니다. 기존 방법은 변경할 수 없지만 새로운 방법을 추가 할 수 있습니다.
앤드류는

3

, 동적으로 유형이 지정된 언어에서만 가능한 많은 패턴과 기술이 있습니다.

원숭이 패치 는 런타임에 속성이나 메서드가 객체 나 클래스에 추가되는 기술입니다. 이 기술은 정적 형식 언어에서는 불가능합니다. 이는 컴파일 타임에 형식과 작업을 확인할 수 없기 때문입니다. 언어는 그것이 원숭이 패치를 지원하는 경우 또는, 다른 방법을 넣어 정의 동적 언어.

언어가 원숭이 패치 (또는 런타임에 유형을 수정하는 유사한 기술)를 지원하는 경우 정적으로 유형을 확인할 수 없음을 증명할 수 있습니다. 따라서 현재 기존 언어의 제한 사항이 아니라 정적 입력의 기본 제한 사항입니다.

따라서 인용문은 정확합니다. 정적으로 입력 된 언어보다 동적 언어에서 더 많은 것이 가능합니다. 반면에 특정 유형의 분석 은 정적으로 유형이 지정된 언어로만 가능합니다. 예를 들어 주어진 유형에서 어떤 작업이 허용되는지 항상 알고 있으므로 컴파일 유형에서 잘못된 작업을 감지 할 수 있습니다. 런타임에 작업을 추가하거나 제거 할 수있는 경우 동적 언어로는 이러한 확인이 불가능합니다.

이것이 정적 언어와 동적 언어 사이의 충돌에서 명백한 "최상의"가없는 이유입니다. 정적 언어는 컴파일 타임에 다른 종류의 힘을 대신하여 런타임에 특정 힘을 포기하여 버그 수를 줄이고 개발을 더 쉽게 만듭니다. 어떤 사람들은 그 절충이 그만한 가치가 있다고 생각하지만 다른 사람들은 그렇지 않습니다.

다른 답변은 Turing-equivalence는 한 언어로 가능한 것은 모든 언어에서 가능하다는 것을 의미한다고 주장했습니다. 그러나 이것은 따르지 않습니다. 정적 언어에서 원숭이 패치와 같은 것을 지원하려면 기본적으로 정적 언어 내에 동적 하위 언어를 구현해야합니다. 이것은 물론 가능하지만 호스트 언어에 존재하는 정적 유형 검사를 잃기 때문에 임베디드 동적 언어로 프로그래밍하고 있다고 주장합니다.

버전 4부터 C #은 동적으로 형식화 된 개체를 지원했습니다. 분명히 언어 디자이너는 두 종류의 타이핑을 모두 사용할 수 있다는 이점이 있습니다. 그러나 그것은 또한 케이크를 가지고 나도 먹을 수 없다는 것을 보여줍니다 : C #에서 동적 객체를 사용하면 원숭이 패치와 같은 기능을 수행 할 수 있지만 이러한 객체와의 상호 작용에 대한 정적 유형 검증은 손실됩니다.


마지막 단락부터 두 번째 단락까지 +1 결정적인 주장이라고 생각합니다. 나는 아직도 당신이 어디에 당신이 원숭이 패치 할 수있는 모든 권한이 정적 유형으로 비록 차이가 있다고 주장 것
JK합니다.

2

따옴표 형식을 사용하여 "유형으로 작동하지 않는"유용한 디자인 패턴이나 전략이 있는지 궁금합니다.

예, 아니오

프로그래머가 컴파일러보다 더 정밀한 변수 유형을 알고있는 상황이 있습니다. 컴파일러는 무언가가 객체라는 것을 알 수 있지만 프로그래머는 프로그램의 변형으로 인해 실제로는 문자열임을 알 수 있습니다.

이에 대한 몇 가지 예를 보여 드리겠습니다.

Map<Class<?>, Function<?, String>> someMap;
someMap.get(object.getClass()).apply(object);

someMap을 구성한 방식 때문에을 someMap.get(T.class)반환 한다는 것을 알고 Function<T, String>있습니다. 그러나 Java는 내가 기능을 가지고 있다고 확신합니다.

또 다른 예:

data = parseJSON(someJson)
validate(data, someJsonSchema);
print(data.properties.rowCount);

data.properties.rowCount는 스키마에 대해 데이터의 유효성을 검사했기 때문에 유효한 참조 및 정수가 될 것입니다. 해당 필드가 없으면 예외가 발생했을 것입니다. 그러나 컴파일러는 예외를 던지거나 일종의 일반 JSONValue를 반환한다는 것을 알고 있습니다.

또 다른 예:

x, y, z = struct.unpack("II6s", data)

"II6s"는 데이터가 세 가지 변수를 인코딩하는 방식을 정의합니다. 형식을 지정 했으므로 어떤 유형이 반환되는지 알고 있습니다. 컴파일러는 터플을 반환한다는 것만 알고 있습니다.

이 모든 예제의 통일 된 주제는 프로그래머가 유형을 알고 있지만 Java 레벨 유형 시스템에서는이를 반영 할 수 없다는 것입니다. 컴파일러는 유형을 알지 못하므로 정적으로 유형이 지정된 언어는 호출하지 않지만 동적으로 유형이 지정된 언어는 호출합니다.

그것이 원래 인용 한 것입니다.

동적 타이핑의 멋진 점은 계산 가능한 모든 것을 표현할 수 있다는 것입니다. 유형 시스템은 그렇지 않습니다. 유형 시스템은 일반적으로 결정 가능하며 하위 집합으로 제한합니다.

동적 타이핑을 사용할 때 언어의 유형 시스템이 알고있는 가장 파생 된 유형이 아니라 내가 아는 가장 파생 된 유형을 사용할 수 있습니다. 위의 모든 경우에 의미 적으로 올바른 코드가 있지만 정적 입력 시스템에 의해 거부됩니다.

그러나 질문으로 돌아가려면

따옴표 형식을 사용하여 "유형으로 작동하지 않는"유용한 디자인 패턴이나 전략이 있는지 궁금합니다.

위의 예 중 하나와 실제로 동적 타이핑의 예는 적절한 캐스트를 추가하여 정적 타이핑에서 유효하게 만들 수 있습니다. 컴파일러에서 지원하지 않는 형식을 알고 있으면 값을 캐스팅하여 컴파일러에 알리십시오. 따라서 어떤 수준에서는 동적 입력을 사용하여 추가 패턴을 얻지 못할 수 있습니다. 정적으로 유형이 지정된 코드를 사용하려면 더 많이 캐스팅해야 할 수도 있습니다.

다이내믹 타이핑의 장점은 타입 시스템의 유효성을 확신하기가 까다 롭다는 사실을 염두에 두지 않고 단순히 이러한 패턴을 사용할 수 있다는 것입니다. 사용 가능한 패턴을 변경하지 않고 유형 시스템이 패턴을 인식하거나 유형 시스템을 파괴하기 위해 캐스트를 추가하는 방법을 알 필요가 없기 때문에 구현하기가 더 쉬워집니다.


1
왜 자바가 '고급 / 복잡한 타입 시스템'으로 가지 말아야하는 차단 점입니까?
jk.

2
@ jk, 내가 무슨 말인지 생각하게 만드는 이유는 무엇입니까? 나는 더 진보 된 / 복잡한 타입 시스템이 가치가 있는지 아닌지에 대한 측면을 분명히 피했다.
Winston Ewert

2
이들 중 일부는 끔찍한 예이며, 다른 일부는 유형이 아닌 유형보다 언어 결정이 더 많은 것 같습니다. 사람들이 직렬화 해제가 유형이 지정된 언어에서 그렇게 복잡하다고 생각하는 이유에 대해 특히 혼란 스럽습니다. 형식화 된 결과는 data = parseJSON<SomeSchema>(someJson); print(data.properties.rowCount); 직렬화 해제 할 클래스가없는 경우 다시 data = parseJSON(someJson); print(data["properties.rowCount"]);입력 할 수 있습니다.이 유형은 여전히 ​​유형화되어 동일한 의도를 나타냅니다.
NPSF3000

2
@ NPSF3000, parseJSON 기능은 어떻게 작동합니까? 반사 또는 매크로를 사용하는 것 같습니다. 정적 언어로 data [ "properties.rowCount"]를 어떻게 입력 할 수 있습니까? 결과 값이 정수임을 어떻게 알 수 있습니까?
Winston Ewert

2
@ NPSF3000, 정수를 모른다면 어떻게 사용할 계획입니까? 배열인지 알지 못하고 JSON의 목록에서 요소를 반복하는 방법은 무엇입니까? 내 예제의 요점은 그것이 data.properties객체라는 것을 알았고 그것이 data.properties.rowCount정수라는 것을 알고 있었고 단순히 그것들을 사용하는 코드를 작성할 수 있다는 것입니다. 당신의 제안 data["properties.rowCount"]은 같은 것을 제공하지 않습니다.
Winston Ewert

1

다음은 C ++ (정적 유형)에서는 불가능한 Objective-C (동적 유형)의 몇 가지 예입니다.

  • 여러 개의 고유 클래스의 객체를 동일한 컨테이너에 넣습니다.
    물론, 컨테이너의 내용을 해석하기 위해 런타임 유형 검사가 필요하며, 정적 타이핑의 대부분의 친구는 처음에는 이것을해서는 안된다고 반대합니다. 그러나 나는 종교적 논쟁을 넘어서서 이것이 유용 할 수 있음을 발견했다.

  • 서브 클래 싱없이 클래스 확장
    Objective-C에서와 같은 언어 정의 함수를 포함하여 기존 클래스에 대한 새 멤버 함수를 정의 할 수 있습니다 NSString. 예를 들어, 메소드를 추가하여 stripPrefixIfPresent:말할 수 있습니다 [@"foo/bar/baz" stripPrefixIfPresent:@"foo/"]( NSSring리터럴 사용에 유의하십시오 @"").

  • 객체 지향 콜백 사용.
    Java 및 C ++와 같이 정적으로 유형이 지정된 언어에서는 라이브러리가 사용자 제공 오브젝트의 임의 멤버를 호출 할 수 있도록 상당한 길이로 이동해야합니다. Java에서 해결 방법은 인터페이스 / 어댑터 쌍에 익명 클래스이며 C ++에서는 해결 방법이 일반적으로 템플릿 기반이므로 라이브러리 코드가 사용자 코드에 노출되어야합니다. Objective-C에서는 객체 참조와 메소드 선택기를 라이브러리에 전달하면 라이브러리가 간단하고 직접 콜백을 호출 할 수 있습니다.


C ++에서 void *로 캐스팅하여 첫 번째 작업을 수행 할 수 있지만 유형 시스템을 우회하므로 계산하지 않습니다. 타입 시스템 내에서 확장 메소드를 사용하여 C #에서 두 번째 작업을 수행 할 수 있습니다. 세 번째로, 나는 "메소드에 대한 선택기"가 람다가 될 수 있다고 생각합니다. 따라서 올바르게 이해하면 람다가있는 정적으로 유형이 지정된 언어도 똑같이 할 수 있습니다. ObjC에 익숙하지 않습니다.
user7610

1
@JiriDanek "C ++에서 void *로 캐스팅하여 첫 번째 작업을 수행 할 수 있습니다."정확하게 요소를 읽는 코드는 실제 유형을 자체적으로 검색 할 방법이 없습니다. 타입 태그가 필요합니다. 게다가 "<언어>에서이 작업을 수행 할 수 있습니다"라고 말하는 것이 적절하고 생산적인 방법이라고 생각하지 않습니다. 항상 에뮬레이션 할 수 있기 때문입니다. 중요한 것은 표현력의 향상 대 구현의 복잡성입니다. 또한 언어에 정적 및 동적 기능 (Java, C #)이 모두있는 경우 "정적"언어 제품군에만 속한다고 생각하는 것 같습니다.
coredump

1
@JiriDanek void*만으로는 동적 타이핑이 아니며 타이핑이 부족합니다. 그러나 예, dynamic_cast, 가상 테이블 등으로 인해 C ++은 정적으로 유형이 정해지지 않습니다. 그게 나쁜가요?
coredump

1
필요할 때 타입 시스템을 파괴하는 옵션이 유용하다는 것을 제안합니다. 필요할 때 탈출구를 확보하십시오. 또는 누군가 유용하다고 생각했습니다. 그렇지 않으면 언어에 넣지 않을 것입니다.
user7610

2
@ JiriDanek 내 생각에, 당신은 당신의 마지막 코멘트로 거의 그것을 못 박았습니다. 이러한 탈출구는주의해서 사용할 경우 매우 유용 할 수 있습니다. 그럼에도 불구하고, 큰 힘을 가지고 큰 책임을지고 그것을 남용하는 사람들이 많이 있습니다 ... 따라서, 다른 모든 클래스가 정의에 의해 파생 된 일반 기본 클래스에 대한 포인터를 사용하는 것이 훨씬 좋습니다. Objective-C와 Java 모두에서), RTTI를 사용하여 void*특정 객체 유형 으로 캐스팅하는 것보다 사례를 구별 합니다. 전자는 엉망인 경우 런타임 오류를 생성하며, 나중에 정의되지 않은 동작이 발생합니다.
cmaster
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.