development

클로저 프로토콜에 대한 간단한 설명

big-blog 2020. 7. 6. 07:05
반응형

클로저 프로토콜에 대한 간단한 설명


클로저 프로토콜과 그들이 해결해야 할 문제를 이해하려고합니다. 클로저 프로토콜의 내용과 이유에 대한 명확한 설명이 있습니까?


Clojure의 프로토콜의 목적은 효율적인 방식으로 표현 문제를 해결하는 것입니다.

식 문제는 무엇입니까? 확장 성의 기본 문제를 말합니다. 프로그램은 연산을 사용하여 데이터 유형을 조작합니다. 프로그램이 발전함에 따라 새로운 데이터 유형과 새로운 작업으로 프로그램을 확장해야합니다. 특히 기존 데이터 형식과 작동하는 새 작업을 추가하고 기존 작업과 작동하는 새 데이터 형식을 추가하려고합니다. 그리고 우리는 이것이 진정한 확장 이되기를 원합니다 . 즉, 우리는 기존 확장 을 수정하고 싶지 않습니다.프로그램은 기존 추상화를 존중하고 확장을 별도의 네임 스페이스에서 별도의 모듈로 만들고, 개별적으로 컴파일하고, 개별적으로 배포하고, 개별적으로 유형을 검사하기를 원합니다. 우리는 그것들이 타입 안전을 원합니다. [참고 :이 언어가 모두 언어에 맞는 것은 아닙니다. 그러나 예를 들어, 유형 안전을 보장하는 목표는 Clojure와 같은 언어에서도 의미가 있습니다. 형식 안전성을 정적으로 확인할 수 없다고 해서 코드가 무작위로 끊어지기를 원하는 것은 아닙니다.]

표현 문제는 실제로 어떻게 언어로 그러한 확장 성을 제공합니까?

절차 적 및 / 또는 기능적 프로그래밍의 전형적인 순진한 구현의 경우, 새로운 오퍼레이션 (프로 시저, 함수)을 추가하는 것이 매우 쉽지만, 새로운 오퍼레이션을 추가하는 것은 매우 어렵다는 것이 밝혀졌습니다. 대 / 소문자 구분 ( switch,, case패턴 일치)과 관련하여 새 대소 문자 를 추가해야합니다 (예 : 기존 코드 수정).

func print(node):
  case node of:
    AddOperator => print(node.left) + '+' + print(node.right)
    NotOperator => '!' + print(node)

func eval(node):
  case node of:
    AddOperator => eval(node.left) + eval(node.right)
    NotOperator => !eval(node)

이제 유형 검사와 같은 새 작업을 추가하려면 쉽지만 새 노드 유형을 추가하려면 모든 작업에서 기존의 모든 패턴 일치 표현식을 수정해야합니다.

그리고 전형적인 순진한 OO의 경우, 당신은 정반대의 문제가 있습니다 : 기존 작업에서 작동하는 새로운 데이터 유형을 쉽게 추가 할 수는 있지만 (기본적으로 수정하는 것을 의미하기 때문에) 새로운 작업을 추가하는 것은 어렵습니다 기존 클래스 / 객체.

class AddOperator(left: Node, right: Node) < Node:
  meth print:
    left.print + '+' + right.print

  meth eval
    left.eval + right.eval

class NotOperator(expr: Node) < Node:
  meth print:
    '!' + expr.print

  meth eval
    !expr.eval

여기서 필요한 모든 작업을 상속, 재정의 또는 구현하기 때문에 새 노드 유형을 추가하는 것이 쉽지만 모든 리프 클래스 또는 기본 클래스에 추가해야하므로 기존 작업을 수정해야하기 때문에 새 작업을 추가하기가 어렵습니다. 암호.

여러 언어에는 식 문제를 해결하기위한 여러 구문이 있습니다. Haskell에는 형식 클래스가 있고 스칼라는 암시 적 인수가 있으며 Racket에는 단위가 있으며 Go에는 인터페이스가 있으며 CLOS 및 Clojure에는 멀티 메소드가 있습니다. C #과 Java의 인터페이스 및 확장 메소드, Ruby의 Monkeypatching, Python, ECMAScript와 같은 방식으로 해결 하려고 시도 하는 "솔루션"도 있습니다 .

Clojure에는 실제로 Expression Problem : Multimethods를 해결하는 메커니즘 이미 있습니다 . 오픈 오피스가 EP와 함께 갖는 문제는 그들이 오퍼레이션과 타입을 함께 묶는다는 것입니다. 멀티 메소드는 서로 분리되어 있습니다. FP의 문제점은 작업과 사례 차별을 함께 묶는 것입니다. 다시 말하지만 멀티 메소드에서는 분리되어 있습니다.

So, let's compare Protocols with Multimethods, since both do the same thing. Or, to put it another way: Why Protocols if we already have Multimethods?

The main thing Protocols offer over Multimethods is Grouping: you can group multiple functions together and say "these 3 functions together form Protocol Foo". You cannot do that with Multimethods, they always stand on their own. For example, you could declare that a Stack Protocol consists of both a push and a pop function together.

So, why not just add the capability to group Multimethods together? There's a purely pragmatic reason, and it is why I used the word "efficient" in my introductory sentence: performance.

Clojure is a hosted language. I.e. it is specifically designed to be run on top of another language's platform. And it turns out that pretty much any platform that you would like Clojure to run on (JVM, CLI, ECMAScript, Objective-C) has specialized high-performance support for dispatching solely on the type of the first argument. Clojure Multimethods OTOH dispatch on arbitrary properties of all arguments.

So, Protocols restrict you to dispatch only on the first argument and only on its type (or as a special case on nil).

This is not a limitation on the idea of Protocols per se, it is a pragmatic choice to get access to the performance optimizations of the underlying platform. In particular, it means that Protocols have a trivial mapping to JVM/CLI Interfaces, which makes them very fast. Fast enough, in fact, to be able to rewrite those parts of Clojure which are currently written in Java or C# in Clojure itself.

Clojure has actually already had Protocols since version 1.0: Seq is a Protocol, for example. But until 1.2, you couldn't write Protocols in Clojure, you had to write them in the host language.


I find it most helpful to think of protocols as being conceptually similar to an "interface" in object oriented languages such as Java. A protocol defines an abstract set of functions that can be implemented in a concrete way for a given object.

An example:

(defprotocol my-protocol 
  (foo [x]))

Defines a protocol with one function called "foo" that acts on one parameter "x".

You can then create data structures that implements the protocol, e.g.

(defrecord constant-foo [value]  
  my-protocol
    (foo [x] value))

(def a (constant-foo. 7))

(foo a)
=> 7

Note that here the object implementing the protocol is passed as the first parameter x - somewhat like the implicit "this" parameter in object oriented languages.

One of the very powerful and useful features of protocols is that you can extend them to objects even if the object was not originally designed to support the protocol. e.g. you can extend the protocol above to the java.lang.String class if you like:

(extend-protocol my-protocol
  java.lang.String
    (foo [x] (.length x)))

(foo "Hello")
=> 5

참고URL : https://stackoverflow.com/questions/4509782/simple-explanation-of-clojure-protocols

반응형