인터페이스 (OOP)의 시맨틱 계약이 기능 서명 (FP)보다 유익한 정보입니까?


16

일부는 SOLID 원칙을 최대한 활용하면 기능적 프로그래밍을하게 된다고 말합니다 . 나는이 기사에 동의하지만 인터페이스 / 객체에서 함수 / 클로저로의 전환에서 일부 의미가 손실된다고 생각하며 함수 프로그래밍이 손실을 완화시키는 방법을 알고 싶습니다.

기사에서 :

또한 ISP (Interface Segregation Principle)를 엄격하게 적용하면 헤더 인터페이스보다 역할 인터페이스를 선호해야합니다.

더 작고 더 작은 인터페이스를 향한 설계를 계속 추진하면 궁극적으로 단일 방법의 인터페이스 인 궁극적 인 역할 인터페이스에 도달하게됩니다. 이것은 나에게 많이 일어난다. 예를 들면 다음과 같습니다.

public interface IMessageQuery
{
    string Read(int id);
}

에 의존하는 경우 암시 적 계약의IMessageQuery 일부는 호출 이 주어진 ID를 가진 메시지를 검색하고 반환 한다는 것입니다.Read(id)

이것을 동등한 기능적 서명에 의존하는 것과 비교하십시오 int -> string. 추가 신호가 없으면이 함수는 간단 할 수 있습니다 ToString(). 당신이 구현되면 IMessageQuery.Read(int id)ToString()나는 의도적으로 파괴되는 당신을 비난 할 수있다!

그렇다면 잘 알려진 인터페이스의 의미를 보존하기 위해 기능 프로그래머는 무엇을 할 수 있습니까? 예를 들어, 단일 멤버로 레코드 유형을 작성하는 것이 일반적입니까?

type MessageQuery = {
    Read: int -> string
}

5
OOP 인터페이스는 단일 함수가 아닌 FP typeclass 와 유사 합니다.
9000

2
인터페이스 당 하나의 방법으로 ISP를 '엄격하게'적용하면 너무 좋은 결과를 얻을 수 있습니다.
gbjbaanb

3
@gbjbaanb 실제로 대부분의 인터페이스에는 구현이 많은 하나의 메소드 만 있습니다. SOLID 원칙을 많이 적용할수록 이점을 더 많이 볼 수 있습니다. 그러나 그것은이 질문에 대한 주제가
아닙니다

1
@jk .: Haskell에서는 타입 클래스입니다. OCaml에서는 모듈이나 functor를 사용하고 Clojure에서는 프로토콜을 사용합니다. 어쨌든 일반적으로 인터페이스 비유를 단일 함수로 제한하지 않습니다.
9000

2
Without any additional clues... 문서가 계약의 일부인 이유 일 수 있습니다 .
SJuan76

답변:


8

Telastyn이 말했듯이 함수의 정적 정의를 비교하면 다음과 같습니다.

public string Read(int id) { /*...*/ }

let read (id:int) = //...

OOP에서 FP로가는 것을 잃어버린 것은 없습니다.

그러나 함수와 인터페이스는 정적 정의에서만 참조되지 않기 때문에 이것은 이야기의 일부일뿐입니다. 그들은 또한 전달됩니다 . 우리 MessageQuery가 다른 코드 조각에 의해 읽혀 졌다고 가정 해 봅시다 MessageProcessor. 그리고 우리는 :

public void ProcessMessage(int messageId, IMessageQuery messageReader) { /*...*/ }

이제 메서드 이름 이나 매개 변수를 직접 볼 수는 없지만 IDE를 통해 쉽게 얻을 수 있습니다. 보다 일반적으로, 우리는 int에서 string까지의 함수를 가진 메소드를 가진 인터페이스가 아니라이 함수와 관련된 매개 변수 이름 메타 데이터를 유지한다는 것을 의미 합니다.IMessageQuery.Readint idIMessageQueryid

반면에 기능 버전의 경우 다음이 있습니다.

let read (id:int) (messageReader : int -> string) = // ...

그래서 우리는 무엇을 유지하고 잃었습니까? 글쎄, 우리는 여전히 name 매개 변수를 가지고 messageReader있는데, 이것은 아마도 형식 이름 (과 동등한 IMessageQuery)을 불필요하게 만듭니다. 그러나 이제 id함수에서 매개 변수 이름 을 잃어 버렸습니다 .


이 문제를 해결하는 데는 두 가지 주요 방법이 있습니다.

  • 첫째, 그 서명을 읽음으로써 이미 무슨 일이 벌어 질지 추측 할 수 있습니다. 함수를 짧고 단순하며 응집력있게 유지하고 이름을 잘 지정하면이 정보를 쉽게 이해하고 찾을 수 있습니다. 실제 함수 자체를 읽은 후에는 훨씬 더 간단해질 것입니다.

  • 둘째, 기본형을 감싸는 작은 유형을 만들기 위해 많은 기능적 언어에서 관용적 디자인으로 간주됩니다 . 이 경우 반대의 상황이 발생합니다. 유형 이름을 매개 변수 이름 ( IMessageQueryto messageReader)으로 바꾸는 대신 매개 변수 이름을 유형 이름으로 바꿀 수 있습니다. 예를 들어, 다음 int과 같은 유형으로 랩핑 될 수 있습니다 Id.

    type Id = Id of int
    

    이제 우리의 read서명은 다음과 같습니다.

    let read (id:int) (messageReader : Id -> string) = // ...
    

    우리가 이전에했던 것만 큼 유익합니다.

    참고로, 이것은 우리가 OOP에서 가지고 있던 컴파일러 보호 기능을 제공합니다. OOP의 버전이 보장 반면 우리는 특별히했다 IMessageQuery단지 오래된보다는 int -> string기능, 여기에 우리는 우리가 복용하고 있다는 비슷한 (하지만 다른) 보호가 Id -> string단지 오래된보다는를 int -> string.


이러한 기술이 인터페이스에서 전체 정보를 사용할 수있는 것만큼이나 유익하고 유익 할 것이라고 100 % 확신하며 말하기를 꺼려하지만, 위의 예에서 대부분의 경우, 아마 좋은 일을 할 수 있습니다.


1
이것은 내가 가장 좋아하는 답변입니다
-FP

10

FP를 수행 할 때 더 구체적인 의미 유형을 사용하는 경향이 있습니다.

예를 들어, 나를위한 방법은 다음과 같습니다.

read: MessageId -> Message

객체 지향보다이 통신하는 꽤 많은 (/ 자바) 스타일 ThingDoer.doThing()스타일


2
+1. 또한 Haskell과 같은 유형이 지정된 FP 언어로 훨씬 더 많은 속성을 유형 시스템으로 인코딩 할 수 있습니다. 그것이 하스켈 유형이라면, IO가 전혀 수행하지 않는다는 것을 알 것입니다 (따라서 어딘가에서 메시지에 대한 id의 메모리 내 매핑을 참조 할 것입니다). OOP 언어에서는 그 정보가 없습니다.이 함수는 데이터베이스를 호출 할 때 데이터베이스를 호출 할 수 있습니다.
Jack

1
왜 이것이 Java 스타일보다 더 많은 의사 소통을하는지 명확하지 않습니다 . 무엇 않습니다 read: MessageId -> Message당신은 이야기 string MessageReader.GetMessage(int messageId)하지 않는 이유는 무엇입니까?
Ben Aaronson

@BenAaronson 신호 대 잡음비가 더 좋습니다. 그것이 OO 인스턴스 방법이라면 나는 그것이 무엇을하고 있는지 전혀 알지 못하며 표현되지 않은 모든 종류의 의존성을 가질 수 있습니다. Jack이 언급했듯이 더 강한 유형을 사용하면 더 많은 확신을 갖게됩니다.
Daenyth

9

그렇다면 잘 알려진 인터페이스의 의미를 보존하기 위해 기능 프로그래머는 무엇을 할 수 있습니까?

잘 명명 된 기능을 사용하십시오.

IMessageQuery::Read: int -> string단순히 ReadMessageQuery: int -> string또는 이와 비슷한 것이됩니다 .

주목할 점은 이름은 가장 느슨한 의미에서 계약일뿐입니다. 그들은 당신과 다른 프로그래머가 이름에서 같은 의미를 추론하고 순종 할 때만 작동합니다. 이 때문에 내재 된 동작을 나타내는 모든 이름을 사용할 수 있습니다 . OO와 함수형 프로그래밍은 이름이 약간 다른 위치와 형태가 약간 다르지만 기능은 동일합니다.

인터페이스 (OOP)의 시맨틱 계약이 기능 서명 (FP)보다 유익한 정보입니까?

이 예에는 없습니다. 위에서 설명한 것처럼 단일 함수를 가진 단일 클래스 / 인터페이스는 비슷한 이름의 독립형 함수보다 의미있는 정보가 아닙니다.

클래스에서 둘 이상의 함수 / 필드 / 프로퍼티를 얻으면 관계를 볼 수 있으므로 더 많은 정보를 유추 할 수 있습니다. 네임 스페이스 또는 모듈로 구성된 동일하거나 유사한 매개 변수 또는 독립형 함수를 취하는 독립형 함수보다 유익한 정보인지는 논쟁의 여지가 있습니다.

개인적으로, 나는 좀 더 복잡한 예에서도 OO가 훨씬 더 유익하다고 생각하지 않습니다.


2
매개 변수 이름은 어떻습니까?
벤 Aaronson

@ BenAaronson-그들 어때? OO와 기능 언어 모두 매개 변수 이름을 지정할 수있게하므로 푸시입니다. 나는 여기서 질문과 일치시키기 위해 형식 서명 속기를 사용하고 있습니다. 실제 언어에서는 다음과 같이 보일 것입니다ReadMessageQuery id = <code to go fetch based on id>
Telastyn

1
함수가 인터페이스를 매개 변수로 사용하고 해당 인터페이스에서 메서드를 호출하면 인터페이스 메서드의 매개 변수 이름을 쉽게 사용할 수 있습니다. 함수가 다른 함수를 매개 변수로 사용하는 경우 함수에 전달 된 매개 변수 이름을 쉽게 지정할 수 없습니다.
Ben Aaronson

1
예, @BenAaronson 이것은 함수 서명에서 손실되는 정보 중 하나입니다. 예를 들어,에 대한 let consumer (messageQuery : int -> string) = messageQuery 5id매개 변수는 단지입니다 int. 하나의 주장은 당신이 Id아닌을 전달해야한다는 것 int입니다. 실제로 그것은 그 자체로 적절한 대답을 할 것입니다.
AlexFoxGill 2016 년

@AlexFoxGill 실제로 나는 바로 그 라인을 따라 무언가를 쓰는 과정에있었습니다!
Ben Aaronson

0

하나의 함수가 '의미 적 계약'을 가질 수 없다는 데 동의하지 않습니다. 다음에 대한 법률을 고려하십시오 foldr.

foldr f z nil = z
foldr f z (singleton x) = f x z
foldr f z (xn <> ys) = foldr f (foldr f z ys) xn

그 의미가 의미가 아니거나 계약이 아닌 것은 무엇입니까? '폴더'에 대한 유형을 정의 할 필요는 없습니다. 특히 foldr해당 법률에 따라 고유하게 결정 되기 때문 입니다. 당신은 그것이 무엇을할지 정확히 알고 있습니다.

어떤 유형의 기능을 사용하려면 동일한 작업을 수행 할 수 있습니다.

-- The first argument `f` must satisfy for all x, y, z
-- > f x x = true
-- > f x y = true and f y x = true implies x = y
-- > f x y = true and f y z = true implies f x z = true
sort :: forall 'a. (a -> a -> bool.t) -> list.t a -> list.t a;

동일한 계약이 여러 번 필요한 경우에만 해당 유형의 이름을 지정하고 캡처하면됩니다.

-- Type of functions `f` satisfying, for all x, y, z
-- > f x x = true
-- > f x y = true and f y x = true implies x = y
-- > f x y = true and f y z = true implies f x z = true
type comparison.t 'a = a -> a -> bool.t;

타입 체커는 타입에 할당 된 의미를 강제하지 않을 것이므로 모든 계약에 대해 새로운 타입을 만드는 것은 단순한 보일러 플레이트입니다.


1
첫 번째 요점은 질문을 다루지 않는다고 생각합니다. 서명이 아닌 함수 정의입니다. 물론 클래스 또는 함수의 구현을 살펴보면 그것이 무엇을하는지 알 수 있습니다. 질문은 여전히 ​​추상화 수준 (인터페이스 또는 함수 서명)에서 의미를 보존 할 수 있는지 묻습니다.
AlexFoxGill

@AlexFoxGill- 고유하게 결정하지만 함수 정의는 아닙니다foldr . 힌트 :의 정의 foldr에는 두 개의 방정식이 있는데 (왜?) 위에 주어진 사양에는 세 개의 방정식이 있습니다.
Jonathan Cast

내 말은 폴더는 함수 서명이 아닌 함수라는 것입니다. 법률 또는 정의는 서명의 일부가 아닙니다
AlexFoxGill

-1

거의 모든 정적으로 유형화 된 기능 언어는 의미 적 의도를 명시 적으로 선언해야하는 방식으로 기본 유형의 별명을 지정할 수 있습니다. 다른 답변 중 일부는 예를 제공했습니다. 실제로, 숙련 된 기능적 프로그래머는 이러한 랩퍼 유형을 사용해야하는 이유 가 매우 작습니다 . 컴포저 빌 러티와 재사용 성이 손상되기 때문입니다.

예를 들어, 클라이언트가 목록에 의해 지원되는 메시지 쿼리의 구현을 원한다고 가정합니다. Haskell에서 구현은 다음과 같이 간단 할 수 있습니다.

messages = ["Message 0", "Message 1", "Message 2"]
messageQuery = (messages !!)

newtype Message = Message String이것을 사용 하는 것은 훨씬 간단하지 않으며이 구현은 다음과 같이 보입니다.

messages = map Message ["Message 0", "Message 1", "Message 2"]
messageQuery (Id index) = messages !! index

그것은 큰 일처럼 보이지 않을 수도 있지만, 어디서나 유형 변환을 수행 하거나 위의 모든 것이있는 코드에서 경계 레이어를 설정 Int -> String한 다음 Id -> Message아래 레이어로 전달하도록 변환해야 합니다. 국제화를 추가하거나 모든 대문자로 형식화하거나 로깅 컨텍스트 또는 기타를 추가하고 싶다고 가정 해보십시오. 이러한 작업은 모두로 구성하기가 간단 Int -> String하고으로 성가시다 Id -> Message. 증가 된 유형 제한이 바람직한 경우는 없지만 성가심이 더 가치가 있습니다.

래퍼 대신 유형 동의어 를 사용할 수 있습니다 ( ) type대신 Haskell에서 사용 newtype하는 것이 훨씬 일반적이며 변환이 필요하지 않지만 OOP 버전과 같은 정적 유형 보장은 제공하지 않습니다. 약간의 캡슐화. 타입 래퍼 는 주로 클라이언트가 값을 조작하지 않고 저장하고 다시 전달할 것으로 예상되는 곳에서 주로 사용됩니다. 예를 들어 파일 핸들입니다.

클라이언트가 "복잡"하는 것을 막을 수있는 것은 없습니다. 당신은 모든 단일 클라이언트에 대해 뛰어 넘기 위해 농구대를 만들고 있습니다. 공통 단위 테스트를위한 간단한 모의는 종종 생산에 적합하지 않은 이상한 행동을 요구합니다 . 인터페이스는 가능한 한 신경 쓰지 않도록 작성해야합니다.


1
이것은 Ben / Daenyth의 답변에 대한 의견과 비슷합니다. 시맨틱 유형을 사용할 수 있지만 불편 때문에 그렇지 않습니까? 난 당신을 downvote하지 않았지만이 질문에 대한 답변이 아닙니다.
AlexFoxGill 2016 년

-1 구성 성이나 재사용 성을 전혀 해치지 않습니다. 경우 IdMessage에 대한 간단한 래퍼 IntString, 그 것이다 사소한 그들 사이의 변환합니다.
Michael Shaw

그렇습니다. 사소한 일이지만 여전히 모든 곳에서 수행하는 것이 짜증납니다. 나는 타입 동의어와 래퍼의 사용법의 차이점을 설명하는 예제와 약간을 추가했습니다.
Karl Bielefeldt

이것은 크기의 것 같습니다. 소규모 프로젝트에서 이러한 종류의 래퍼 유형은 어쨌든 그렇게 유용하지 않을 수 있습니다. 대규모 프로젝트에서는 경계가 전체 코드의 작은 비율이되는 것이 당연합니다. 따라서 경계에서 이러한 유형의 변환을 수행 한 다음 다른 곳에서 랩핑 된 형식을 전달하는 것은 그리 어렵지 않습니다. 또한 Alex가 말했듯이 이것이 어떻게 질문에 대한 답인지 알 수 없습니다.
Ben Aaronson

어떻게해야하는지, 왜 함수형 프로그래머가 그렇게하지 않았는지 설명했습니다. 그것은 사람들이 원하는 사람이 아니더라도 대답입니다. 크기와 관련이 없습니다. 의도적으로 코드를 재사용하기 어렵게 만드는 것이 좋은 아이디어라는 것은 OOP 개념입니다. 가치를 더하는 제품 유형을 만드시겠습니까? 항상. 하나의 라이브러리에서만 사용할 수 있다는 점을 제외하고 널리 사용되는 유형과 정확히 같은 유형을 작성 하시겠습니까? OOP 코드와 많이 상호 작용하거나 OOP 관용구에 주로 익숙한 FP 코드를 제외하고는 실제로 들어 본 적이 없습니다.
Karl Bielefeldt
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.