API 및 기능 프로그래밍


15

Clojure와 같은 기능적 프로그래밍 언어에 대한 (제한적으로 제한적인) 노출에서 데이터 캡슐화는 덜 중요한 역할을하는 것으로 보입니다. 일반적으로 맵이나 세트와 같은 다양한 기본 유형이 객체보다 데이터를 나타내는 기본 통화입니다. 게다가, 그 데이터는 일반적으로 불변입니다.

예를 들어, Clojure 명성의 Rich Hickey가 그 문제 에 대한 인터뷰 에서 가장 유명한 인용문 중 하나입니다 .

포 거스 : 그 아이디어에 따라 일부 사람들은 Clojure가 유형에 대한 데이터 숨김 캡슐화에 관여하지 않는다는 사실에 놀랐습니다. 데이터 숨기기를 포기하기로 결정한 이유는 무엇입니까?

Hickey : Clojure는 추상화에 대한 프로그래밍을 강력하게 강조합니다. 그러나 어느 시점에서 누군가는 데이터에 액세스 할 수 있어야합니다. 그리고 "개인"이라는 개념이 있다면, 특권과 신뢰에 상응하는 개념이 필요합니다. 그리고 그것은 복잡성과 가치가 거의 없어 시스템에 강성을 만들고 종종 원하지 않는 곳에 살도록 강요합니다. 이것은 간단한 정보를 클래스에 넣을 때 발생하는 다른 손실에 추가됩니다. 데이터가 변경 불가능한 범위 내에서, 누군가가 변경 될 수있는 것에 의존 할 수있는 것 외에는 액세스를 제공 할 때 발생할 수있는 피해는 거의 없습니다. 글쎄, 사람들은 항상 현실에서 그렇게하고, 상황이 바뀌면 적응합니다. 그리고 그들이 합리적이라면 그들은 미래에 적응해야 할 변화가있을 수있는 것을 바탕으로 결정을 내릴 때를 알고 있습니다. 그래서 그것은 위험 관리 결정입니다. 프로그래머가 자유롭게 만들어야한다고 생각합니다. 사람들이 추상화로 프로그래밍하기를 원하고 구현 세부 사항과의 결혼을 원치 않는 감각이 없다면 결코 훌륭한 프로그래머가 될 수 없습니다.

OO 세계에서 왔을 때, 이것은 지난 몇 년 동안 배운 비밀스러운 원칙 중 일부를 복잡하게하는 것 같습니다. 여기에는 정보 숨기기, 데메테르의 법칙 및 통일 접근 원칙이 포함됩니다. 캡슐화의 일반적인 스레드는 다른 사람들이 자신이 만져야 할 것과 만져서는 안되는 것을 알 수 있도록 API를 정의 할 수있게합니다. 본질적으로 일부 코드의 관리자가 소비자 코드에 버그를 도입하는 방법에 대한 걱정없이 자유롭게 코드를 변경하고 리팩토링 할 수있는 계약을 작성합니다 (개방 / 폐쇄 원칙). 또한 다른 프로그래머가 해당 데이터를 얻거나 사용할 수있는 도구를 알 수 있도록 깔끔하고 선별 된 인터페이스를 제공합니다.

데이터에 직접 액세스 할 수있게되면 해당 API 계약이 중단되고 모든 캡슐화 이점이 사라지는 것 같습니다. 또한 엄격히 불변의 데이터는 상태 및 해당 상태에서 수행 할 수있는 일련의 작업을 나타내는 의미에서 도메인 별 구조 (객체, 구조체, 레코드)를 전달하는 것이 훨씬 덜 유용한 것으로 보입니다.

기능적 코드베이스는 코드베이스의 크기가 엄청나게 증가하여 API를 정의해야하고 많은 개발자가 시스템의 특정 부분을 다루는 데 관여 할 때 나타나는 이러한 문제를 어떻게 해결합니까? 이러한 유형의 코드베이스에서 이러한 상황을 처리하는 방법을 보여주는이 상황에 대한 예가 있습니까?


2
객체 개념없이 공식적인 인터페이스를 정의 할 수 있습니다. 인터페이스를 문서화하는 기능을 작성하십시오. 구현 세부 사항에 대한 문서를 제공하지 마십시오. 인터페이스를 만들었습니다.
Scara95

@ Scara95 그렇다고해서 인터페이스 코드를 구현하고 소비자에게해야 할 것과하지 말아야 할 것을 경고하기 위해 인터페이스에 대한 충분한 문서를 작성해야한다는 의미는 아닙니까? 코드가 변경되고 설명서가 오래되면 어떻게됩니까? 나는 일반적으로 이런 이유로 자체 문서화 코드를 선호합니다.
jameslk

어쨌든 인터페이스를 문서화해야합니다.
Scara95

3
Also, strictly immutable data seems to make passing around domain-specific structures (objects, structs, records) much less useful in the sense of representing a state and the set of actions that can be performed on that state.실제로는 아닙니다. 변경되는 유일한 것은 변경 사항이 새 오브젝트에서 종료된다는 것입니다. 이것은 코드에 대한 추론에서 큰 승리입니다. 변경 가능한 객체를 전달하는 것은 누가 그것을 변형시킬 수 있는지 추적해야한다는 것을 의미하며, 코드 크기에 따라 확장되는 문제입니다.
Doval

답변:


10

우선, 기능적인 것이 무엇이고, 동적 타이핑이 무엇인지에 관한 Sebastian의 의견을 두 번째로 살펴 보겠습니다. 더 일반적으로 Clojure는 기능적 언어 및 커뮤니티의 풍미 중 하나 이므로이를 너무 많이 일반화해서는 안됩니다. 더 많은 ML / Haskell 관점에서 언급 할 것입니다.

Basile이 언급했듯이 액세스 제어 개념은 ML / Haskell에 존재하며 종종 사용됩니다. "인수 분해"는 기존의 OOP 언어와 약간 다릅니다. OOP에서 클래스 의 개념은 유형모듈 의 역할을 동시에 수행하는 반면 기능적 (및 전통적인 절차 적) 언어는 이들을 직교 적으로 처리합니다.

또 다른 요점은 ML / Haskell은 유형 삭제가있는 제네릭에서 매우 무겁고 OOP 캡슐화와는 다른 "정보 숨기기"의 풍미를 제공하는 데 사용될 수 있다는 것입니다. 구성 요소가 데이터 항목의 유형을 유형 매개 변수로만 알고있는 경우 해당 구성 요소는 해당 유형의 값을 안전하게 전달할 수 있지만 구체적인 유형을 모르거나 알 수 없기 때문에 많은 구성 요소가 수행되지 않습니다. ( instanceof이 언어 에는 범용 또는 런타임 캐스팅 이 없습니다 ). 이 블로그 항목 은 이러한 기술에 대해 제가 가장 좋아하는 입문서 중 하나입니다.

다음 : FP 세계에서는 투명한 데이터 구조를 불투명 / 캡슐화 된 구성 요소에 대한 인터페이스로 사용하는 것이 매우 일반적입니다. 예를 들어, 인터프리터 패턴은 FP에서 매우 일반적이며, 데이터 구조는 로직을 설명하는 구문 트리로 사용되고이를 "실행하는"코드로 제공됩니다. 데이터 구조를 소비하는 인터프리터가 실행될 때 상태가 올바로 말했다. 또한 인터프리터의 구현은 동일한 데이터 유형의 관점에서 클라이언트와 계속 통신하는 한 변경 될 수 있습니다.

가장 길고 길다 : 캡슐화 / 정보 숨기기는 끝이 아닌 기술 입니다. 그것이 제공하는 것에 대해 조금 생각해 봅시다. 캡슐화는 소프트웨어 단위 의 계약구현 을 조정하는 기술입니다 . 일반적인 상황은 다음과 같습니다. 시스템의 구현은 계약에 따라 존재하지 않아야하는 가치 또는 상태를 인정합니다.

이런 식으로 살펴보면 캡슐화 외에도 FP가 같은 목적으로 사용할 수있는 여러 가지 추가 도구를 제공한다는 것을 알 수 있습니다.

  1. 퍼베이시브 기본값으로서의 불변성. 투명한 데이터 값을 타사 코드로 전달할 수 있습니다. 그것들을 수정하여 유효하지 않은 상태로 만들 수는 없습니다. (Karl의 대답이 이것을 지적합니다.)
  2. 많은 코드를 작성하지 않고도 유형의 구조를 정교하게 제어 할 수있는 대수 데이터 유형이있는 정교한 유형 시스템. 이러한 시설을 신중하게 사용함으로써 종종 "나쁜 상태"가 불가능한 유형을 설계 할 수 있습니다. (슬로건 : "불법 상태를 표현할 수 없음" ) 캡슐화를 사용하여 클래스의 허용 가능한 상태 세트를 간접적으로 제어하는 ​​대신 컴파일러에게 그 상태를 알려주고 보증합니다.
  3. 이미 언급 한대로 해석기 패턴. 좋은 추상 구문 트리 유형을 디자인하기위한 한 가지 핵심은 다음과 같습니다.
    • 모든 값이 "유효"하도록 추상 구문 트리 데이터 유형을 시도하고 설계하십시오.
    • 실패하면 인터프리터가 유효하지 않은 조합을 명시 적으로 감지하여 완전히 거부하십시오.

이 F # "Designing with types"시리즈 는 이러한 주제 중 일부, 특히 # 2를 상당히 잘 읽습니다. (위의 "불법 상태를 표현할 수 없음"링크가 나오는 곳입니다.) 자세히 살펴보면 캡슐화를 사용하여 생성자를 숨기고 클라이언트가 잘못된 인스턴스를 구성하지 못하게하는 방법을 보여줍니다. 제가 위에서 말했듯이, 그것은 이다 툴킷의 일부!


9

나는 돌연변이가 소프트웨어에서 문제를 일으키는 정도를 과장되게 말할 수 없다. 우리의 머리에 박힌 많은 관행은 돌연변이로 인한 문제에 대한 보상입니다. 변경 가능성을 없애면 그러한 관행이 많이 필요하지 않습니다.

불변성이 있으면 런타임 중에 데이터 구조가 예기치 않게 변경되지 않으므로 프로그램에 기능을 추가 할 때 자신 만의 파생 데이터 구조를 만들 수 있습니다. 원래 데이터 구조는 이러한 파생 데이터 구조에 대해 아무것도 알 필요가 없습니다.

이는 기본 데이터 구조가 매우 안정적인 경향이 있음을 의미합니다 . 새로운 데이터 구조는 필요에 따라 가장자리에서 파생됩니다. 중요한 기능적 프로그램을 완료하기 전에는 설명하기가 정말 어렵습니다. 개인 정보 보호에 대한 관심이 점점 줄어들고 내구성있는 일반 공개 데이터 구조를 점점 더 많이 생성하려고합니다.


내가 추가하고 싶은 한 가지는 불변 변수로 인해 구조가있는 경우 프로그래머가 분산 및 분산 데이터 구조를 고수한다는 것입니다. 모든 데이터는 운송이 아니라 쉽게 발견하고 탐색 할 수 있도록 논리 그룹을 작성하도록 구성되어 있습니다. 이것은 충분한 기능적 프로그래밍을 수행 한 후에 수행 할 논리 진행입니다.
Xephon

8

내 의견으로는 해시와 프리미티브를 사용하는 Clojure의 경향은 기능적 유산의 일부가 아니라 역동적 인 유산의 일부라고 생각합니다. 나는 파이썬과 루비 (객체 지향, 명령 및 동적 모두에서 고차 함수를 꽤 잘 지원하더라도)와 비슷한 경향을 보았지만 Haskell (정적으로 유형화되었지만 순수하게 기능적임) 불변성을 피하기 위해 특별한 구조가 필요함).

따라서 질문해야 할 것은 기능적 언어가 어떻게 큰 API를 처리하는지가 아니라 동적 언어가 어떻게 처리하는지입니다. 답은 좋은 문서와 많은 단위 테스트입니다. 운 좋게도 현대의 동적 언어는 일반적으로이 두 가지를 모두 매우 잘 지원합니다. 예를 들어 Python과 Clojure는 주석뿐만 아니라 코드 자체에 문서를 포함시키는 방법을 가지고 있습니다.


정적으로 형식화 된 (순수한) 기능적 언어에 대해서는 OO 프로그래밍에서와 같이 데이터 형식으로 함수를 수행하는 간단한 방법이 없습니다. 따라서 문서는 중요합니다. 요점은 인터페이스를 정의하기 위해 언어 지원이 필요하지 않다는 것입니다.
Scara95

5
@ Scara95 "데이터 유형으로 함수를 수행"한다는 것이 무슨 뜻인지 자세히 설명 할 수 있습니까?
Sebastian Redl

6

일부 기능 언어는 추상 데이터 유형모듈 에서 구현 세부 사항을 캡슐화하거나 숨길 수있는 기능을 제공합니다 .

예를 들어, OCaml 에는 명명 된 추상 유형 및 값 (특히 이러한 추상 유형에서 작동하는 기능)의 모음으로 정의 된 모듈이 있습니다. 어떤 의미에서 Ocaml의 모듈은 API를 리프팅하고 있습니다. 또한 Ocaml에는 일부 모듈을 다른 모듈로 변환하여 일반 프로그래밍을 제공하는 기능이 있습니다. 따라서 모듈은 구성 적입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.