Haskell의 고아 인스턴스


87

-Wall옵션을 사용하여 내 Haskell 애플리케이션을 컴파일 할 때 GHC는 고아 인스턴스에 대해 다음과 같이 불평합니다.

Publisher.hs:45:9:
    Warning: orphan instance: instance ToSElem Result

타입 클래스 ToSElem는 내 것이 아니며 HStringTemplate에 의해 정의됩니다 .

이제이 문제를 해결하는 방법 (인스턴스 선언을 Result가 선언 된 모듈로 이동)을 알고 있으며 GHC가 고아 인스턴스를 피하는 것을 선호하는 이유를 알고 있지만 여전히 내 방식이 더 낫다고 생각합니다. 나는 컴파일러가 불편한지 상관하지 않는다.

ToSElemPublisher 모듈에서 내 인스턴스 를 선언하려는 이유 는 다른 모듈이 아닌 HStringTemplate에 의존하는 Publisher 모듈이기 때문입니다. 나는 우려의 분리를 유지하고 모든 모듈이 HStringTemplate에 의존하지 않도록 노력하고 있습니다.

예를 들어 Java의 인터페이스와 비교할 때 Haskell의 유형 클래스의 장점 중 하나는 닫히지 않고 열려 있기 때문에 인스턴스가 데이터 유형과 같은 위치에서 선언 될 필요가 없다는 것입니다. GHC의 조언은 이것을 무시하는 것 같습니다.

그래서 제가 찾고있는 것은 제 생각이 건전하고이 경고를 무시 / 억제하는 것이 정당하다는 확인 또는 제 방식대로하는 것에 대해 더 설득력있는 주장입니다.


답변과 의견의 토론은 실행 파일 에서 고아 인스턴스를 정의하는 것과 다른 사람에게 노출 되는 라이브러리 에서 정의하는 것 사이에 큰 차이가 있음을 보여줍니다 . 이 엄청나게 인기있는 질문 은 고아 인스턴스를 정의하는 라이브러리의 최종 사용자에게 혼란스러운 고아 인스턴스가 될 수 있음을 보여줍니다.
Christian Conkle 2014.11.21

답변:


94

나는 당신이 이것을하고 싶어하는 이유를 이해합니다. 그러나 불행히도 당신이 말하는 방식으로 하스켈 클래스가 "개방적"인 것처럼 보이는 것은 단지 환상 일 수 있습니다. 많은 사람들이 이것을 할 가능성이 Haskell 사양의 버그라고 생각합니다. 이유는 아래에서 설명하겠습니다. 어쨌든 인스턴스에 실제로 적합하지 않은 경우 클래스가 선언 된 모듈 또는 유형이 선언 된 모듈에서 선언해야합니다. 이는 아마도 newtype다른 래퍼 를 사용해야한다는 신호일 것입니다. 당신의 유형 주위.

고아 인스턴스를 피해야하는 이유는 컴파일러의 편의성보다 훨씬 더 깊게 실행됩니다. 이 주제는 다른 답변에서 볼 수 있듯이 다소 논란의 여지가 있습니다. 토론의 균형을 맞추기 위해 저는 고아 인스턴스를 절대로 작성해서는 안된다는 관점을 설명하려고합니다. 이는 경험 많은 Haskeller 사이에서 대부분의 의견이라고 생각합니다. 내 의견은 중간 어딘가에 있으며, 마지막에 설명하겠습니다.

문제는 동일한 클래스 및 유형에 대해 둘 이상의 인스턴스 선언이 존재하는 경우 표준 Haskell에 사용할 항목을 지정하는 메커니즘이 없다는 사실에서 기인합니다. 오히려 프로그램은 컴파일러에 의해 거부됩니다.

그것의 가장 간단한 효과는 다른 누군가가 당신의 모듈에 대한 의존성에서 변경 한 변경 때문에 갑자기 컴파일을 멈출 수있는 완벽하게 작동하는 프로그램을 가질 수 있다는 것입니다.

더 나쁜 것은 먼 변경으로 인해 작동중인 프로그램이 런타임에 충돌 을 시작할 수 있다는 것입니다. 특정 인스턴스 선언에서 비롯된 것으로 가정하는 메서드를 사용할 수 있으며 프로그램이 설명 할 수없는 충돌을 일으킬 수있을만큼 다른 인스턴스로 자동으로 대체 될 수 있습니다.

이러한 문제가 발생하지 않는다는 보장을 원하는 사람들은 어느 곳에서나 특정 유형에 대해 특정 클래스의 인스턴스를 선언 한 적이 있다면 작성된 프로그램에서 다른 인스턴스를 다시 선언하지 않아야한다는 규칙을 따라야합니다. 누구든지. 물론 a newtype를 사용하여 새 인스턴스를 선언하는 해결 방법이 있지만 이는 항상 최소한 사소한 불편이며 때로는 큰 불편입니다. 따라서 이런 의미에서 고아 인스턴스를 의도적으로 작성하는 사람들은 다소 무례합니다.

그렇다면이 문제에 대해 어떻게해야합니까? 안티 고아 인스턴스 캠프는 GHC 경고가 버그이며 고아 인스턴스를 선언하려는 시도를 거부하는 오류 여야한다고 말합니다. 그 동안 우리는 자기 수양을 실천하고 어떤 대가를 치르더라도 피해야합니다.

보시다시피 잠재적 인 문제에 대해 그렇게 걱정하지 않는 사람들이 있습니다. 그들은 실제로 고아 인스턴스를 귀하가 제안한 것처럼 우려 사항 분리 도구로 사용하도록 권장하며 사례별로 문제가 없는지 확인해야한다고 말합니다. 나는 다른 사람들의 고아 사례로 인해 이러한 태도가 너무 무심하다는 것을 확신하기 위해 충분히 불편을 겪었습니다.

올바른 해결책은 인스턴스 가져 오기를 제어하는 ​​Haskell의 가져 오기 메커니즘에 확장을 추가하는 것입니다. 그것은 문제를 완전히 해결하지는 못하지만 이미 세계에 존재하는 고아 인스턴스의 손상으로부터 프로그램을 보호하는 데 도움이 될 것입니다. 그리고 시간이 지남에 따라 특정 제한된 경우에 고아 인스턴스가 그렇게 나쁘지 않을 수 있다고 확신하게 될 것입니다. (그리고 바로 그 유혹이 고아 방지 캠프의 일부가 내 제안에 반대하는 이유입니다.)

이 모든 것에서 내 결론은 적어도 당분간은 고아 인스턴스를 선언하지 말고 다른 이유가없는 경우 다른 사람을 배려하는 것이 좋습니다. 를 사용합니다 newtype.


4
특히 이것은 도서관의 성장과 함께 점점 더 문제가되고 있습니다. Haskell의 2200 개 이상의 라이브러리와 수만 개의 개별 모듈로 인해 인스턴스를 선택하는 위험이 극적으로 증가합니다.
Don Stewart

16
Re : "올바른 해결책은 인스턴스 가져 오기를 제어하는 ​​Haskell의 가져 오기 메커니즘에 확장을 추가하는 것입니다."이 아이디어가 누구에게나 관심이 있다면 예를 들어 Scala 언어를 살펴볼 가치가 있습니다. 타입 클래스 인스턴스와 매우 유사하게 사용할 수있는 '암시 적'의 범위를 제어하기 위해 이와 같은 기능이 있습니다.
Matt

5
내 소프트웨어는 라이브러리가 아니라 응용 프로그램이므로 다른 개발자에게 문제를 일으킬 가능성은 거의 없습니다. 게시자 모듈은 응용 프로그램이고 나머지 모듈은 라이브러리로 간주 할 수 있지만 라이브러리를 배포 할 경우 게시자가 없어서 고아 인스턴스가 발생하지 않습니다. 그러나 인스턴스를 다른 모듈로 이동하면 라이브러리가 HStringTemplate에 대한 불필요한 종속성과 함께 제공됩니다. 그래서이 경우 고아들은 괜찮다고 생각하지만, 다른 상황에서 같은 문제가 발생하면 당신의 조언에 귀를 기울 이겠습니다.
Dan Dyer

1
합리적인 접근으로 들립니다. 이때주의해야 할 사항은 가져온 모듈의 작성자가이 인스턴스를 이후 버전에 추가하는 경우입니다. 해당 인스턴스가 자신의 인스턴스와 동일하면 고유 한 인스턴스 선언을 삭제해야합니다. 해당 인스턴스가 자신의 인스턴스와 다른 경우 유형 주위에 newtype 래퍼를 넣어야합니다. 이는 코드의 중요한 리팩토링이 될 수 있습니다.
Yitz 2010-06-21

@Matt : 사실, 놀랍게도 Scala는 Haskell이 그렇지 않은 곳에서 이것을 바로 얻습니다! (물론 Scala에는 유형 클래스 기계에 대한 일급 구문이없는 경우를 제외하고 더 나쁩니다 ...)
Erik Kaplun

44

이 경고를 억제하십시오!

당신은 좋은 회사에 있습니다. Conal은 "TypeCompose"에서 수행합니다. "chp-mtl"및 "chp-transformers"가이를 수행하고, "control-monad-exception-mtl"및 "control-monad-exception-monadsfd"가 수행합니다.

btw 당신은 아마 이것을 이미 알고있을 것입니다. 그러나 검색에서 당신의 질문을 우연히 발견하지 못하는 사람들을 위해 :

{-# OPTIONS_GHC -fno-warn-orphans #-}

편집하다:

나는 Yitz가 그의 대답에서 언급 한 문제를 실제 문제로 인정합니다. 하지만 고아 인스턴스를 사용하지 않는 것도 문제라고 생각하고 고아 인스턴스를 신중하게 사용하는 "모든 악의 최소"를 선택하려고합니다.

귀하의 질문은 이미 문제를 잘 알고 있음을 보여주기 때문에 내 짧은 대답에 느낌표 만 사용했습니다. 그렇지 않으면 나는 덜 열정적이었을 것입니다. :)

약간의 전환이지만 타협하지 않는 완벽한 세상에서 완벽한 솔루션이라고 생각합니다.

Yitz가 언급 한 문제 (어떤 인스턴스가 선택되었는지 알지 못함)는 다음과 같은 "전체 론적"프로그래밍 시스템에서 해결할 수 있다고 생각합니다.

  • 기본적으로 단순한 텍스트 파일을 편집하는 것이 아니라 환경의 도움을받습니다 (예 : 코드 완성은 관련 유형의 항목 만 제안하는 등).
  • "낮은 수준"언어는 유형 클래스에 대한 특별한 지원이 없으며 대신 함수 테이블이 명시 적으로 전달됩니다.
  • 그러나 "상위 수준"프로그래밍 환경은 Haskell이 현재 제공되는 방식과 유사한 방식으로 코드를 표시하고 (보통 전달 된 함수 테이블을 볼 수 없습니다) 명백한 경우 명시 적 유형 클래스를 선택합니다 (예 : 예를 들어 Functor의 모든 경우는 하나의 선택 만 가능) 여러 예제 (압축 목록 Applicative 또는 list-monad Applicative, First / Last / lift 어쩌면 Monoid)가있는 경우 사용할 인스턴스를 선택할 수 있습니다.
  • 어떤 경우에도 인스턴스가 자동으로 선택 되더라도 환경은 쉬운 인터페이스 (하이퍼 링크 또는 호버 인터페이스 등)를 통해 어떤 인스턴스가 사용되었는지 쉽게 확인할 수 있습니다.

판타지 세계 (또는 미래)에서 돌아온 지금 : "정말 필요"할 때 고아 인스턴스를 사용하면서 고아 인스턴스를 피하는 것이 좋습니다.


5
예, 그러나 틀림없이 이러한 각 발생은 어떤 순서의 실수입니다. control-monad-exception-mtl 및 monads-fd의 Either에 대한 잘못된 인스턴스가 떠 오릅니다. 각각의 모듈이 고유 한 유형을 정의하거나 새로운 유형의 래퍼를 제공하도록 강요 받으면 덜 눈에 띄게됩니다. 거의 모든 고아 인스턴스는 발생하기를 기다리는 골칫거리이며, 다른 어떤 것도 가져 오기가 적절하지 않은지 확인하기 위해 끊임없는 경계가 필요하지 않습니다.
Edward KMETT 2010 년

2
감사. 이 특별한 상황에서 사용할 것 같지만 Yitz 덕분에 이제 어떤 문제가 발생할 수 있는지 더 잘 이해할 수 있습니다.
Dan Dyer

37

고아 인스턴스는 성가신 일이지만 제 생각에는 때때로 필요합니다. 유형은 한 라이브러리에서 제공되고 클래스는 다른 라이브러리에서 제공되는 라이브러리를 종종 결합합니다. 물론 이러한 라이브러리의 작성자는 유형과 클래스의 가능한 모든 조합에 대한 인스턴스를 제공 할 것으로 기대할 수 없습니다. 그래서 나는 그들을 제공해야합니다. 그래서 그들은 고아입니다.

인스턴스를 제공해야 할 때 유형을 새 유형으로 래핑해야한다는 아이디어는 이론적 장점이있는 아이디어이지만 많은 상황에서 너무 지루합니다. 생계를 위해 Haskell 코드를 작성하지 않는 사람들이 내놓은 아이디어입니다. :)

그러니 계속해서 고아 인스턴스를 제공하십시오. 그들은 무해합니다.
고아 인스턴스로 ghc를 크래시 할 수 있다면 이는 버그이므로보고해야합니다. (여러 인스턴스를 감지하지 못하는 ghc 버그는 수정하기가 그리 어렵지 않습니다.)

그러나 언젠가 다른 누군가가 이미 가지고있는 것처럼 인스턴스를 추가 할 수 있으며 (컴파일 시간) 오류가 발생할 수 있습니다.


2
좋은 예는 (Ord k, Arbitrary k, Arbitrary v) ⇒ Arbitrary (Map k v)QuickCheck를 사용할 때입니다.
Erik Kaplun

17

이 경우에는 고아 인스턴스를 사용하는 것이 좋습니다. 저에게 일반적인 경험 법칙은-타입 클래스를 "소유"하거나 데이터 유형 (또는 그 일부 구성 요소)을 "소유"하는 경우 인스턴스를 정의 할 수 있다는 것입니다. 즉, Maybe MyData의 인스턴스도 괜찮습니다. 적어도 때때로). 이러한 제약 내에서 인스턴스를 배치하기로 결정한 곳은 자신의 비즈니스입니다.

한 가지 더 예외가 있습니다. 유형 클래스 나 데이터 유형을 소유하지 않고 라이브러리가 아닌 바이너리를 생성하는 경우에도 괜찮습니다.


5

(나는 내가 파티에 늦었다는 것을 알고 있지만 이것은 여전히 ​​다른 사람들에게 유용 할 수 있습니다)

고아 인스턴스를 자체 모듈에 보관할 수 있으며, 누군가 해당 모듈을 가져 오는 경우 특별히 필요하기 때문이며 문제가 발생하면 가져 오는 것을 피할 수 있습니다.


3

이 줄을 따라 안티 고아 인스턴스 캠프의 위치 WRT 라이브러리를 이해하지만 실행 가능한 대상의 경우 고아 인스턴스가 좋지 않아야합니까?


3
다른 사람에게 무례하다는 점에서 당신이 옳습니다. 그러나 미래에 동일한 인스턴스가 종속성 체인 어딘가에 정의되면 잠재적 인 미래 문제에 직면하게됩니다. 따라서이 경우 위험을 감수 할 가치가 있는지 여부를 결정하는 것은 귀하에게 달려 있습니다.
Yitz 2010-06-27

5
실행에 고아 인스턴스를 구현하는 거의 모든 경우에, 당신이 간격 채우기 위해의 소원은 이미 당신을 위해 정의 하였다. 따라서 인스턴스가 업스트림에 나타나면 결과 컴파일 오류는 인스턴스 선언을 제거 할 수 있음을 알려주는 유용한 신호입니다.
Ben
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.