하스켈 타입 시스템을 정확히 존경하는 이유는 무엇입니까?


204

하스켈 을 배우기 시작했습니다 . 나는 그것을 처음 접했고, 기본 구성을 둘러보기 위해 두 가지 온라인 책을 읽었습니다.

익숙한 사람들이 자주 이야기하는 'memes'중 하나는 "컴파일하면 작동합니다"라는 것입니다.

나는 이유를 이해하기 위해 노력하고있어 정확히 하스켈은이 점에서 다른 정적으로 입력 된 언어보다 낫다.

다른 방법으로 말하면, Java로 가정하면 ArrayList<String>()실제로해야 할 것을 포함하기 위해 묻어 버리는 것과 같은 무서운 일을 할 수 있다고 가정 합니다 ArrayList<Animal>(). 여기서 가장 중요한 것은 stringcontains elephant, giraffe등을 포함 하고 누군가가 넣은 경우 Mercedes컴파일러가 도움이되지 않는다는 것입니다.

내가하면 ArrayList<Animal>()내 프로그램이 차량에 관하여, 동물을 정말없는 결정하는 경우에, 시간에 어떤 나중에에서, 다음, 그때 나는, 말, 생성하는 기능을 변경할 수 있습니다 ArrayList<Animal>생산을 ArrayList<Vehicle>내 IDE가 사방 말해한다 컴파일 시간입니다.

내 가정은 이것이 사람들이 강력한 유형 시스템에 의해 의미하는 것이라고 가정 하지만 Haskell이 더 나은 이유는 분명하지 않습니다. 다른 방법으로, 당신은 좋은 또는 나쁜 자바를 작성할 수 있습니다, 나는 당신이 Haskell에서 똑같이 할 수 있다고 가정합니다 (즉, 실제로 일류 데이터 유형이어야하는 문자열 / 정수에 물건을 넣습니다).

중요한 / 기본적인 것이 빠진 것 같습니다.
나는 나의 길의 잘못을 보게되어 매우 기쁩니다!


31
실제 답변을 작성하는 것보다 사람들이 더 많은 지식을 갖도록하겠습니다. 그러나 그 핵심은 다음과 같습니다. C #과 같이 정적으로 형식이 지정된 언어에는 방어 가능한 코드 를 작성하는 데 도움이되는 형식 시스템이 있습니다. Haskell의 올바른 시스템 (예 : 입증 가능한) 코드 작성을 돕는 시도와 같은 유형 시스템 . 작업의 기본 원칙은 컴파일 단계로 확인할 수있는 것들을 옮기는 것입니다. Haskell 은 컴파일 타임에 더 많은 것을 확인합니다 .
Robert Harvey

8
하스켈에 대해서는 잘 모르지만 Java에 대해서는 말할 수 있습니다. 이 동안 나타납니다 강력한 형식, 그것은 여전히 당신이 말한대로 "극악 무도 한"일을 할 수 있습니다. Java가 유형 시스템과 관련하여 거의 모든 보증을 할 수있는 방법이 있습니다.

12
나는 왜 모든 대답 Maybe이 끝까지 언급되는지 모르겠다 . 더 인기있는 언어가 Haskell에서 빌려야 할 것 중 하나만 선택해야한다면 이것이 될 것입니다. 그것은 매우 간단한 아이디어이기 때문에 (이론적 관점에서 그리 흥미롭지는 않지만) 이것만으로도 우리의 일이 훨씬 쉬워집니다.
Paul

1
여기에는 큰 대답이 있지만 도움을 얻으려면 유형 서명을 연구하십시오. 이를 통해 인간과 프로그램 은 자바가 어떻게 복잡한 중간 유형에 있는지 설명하는 방식으로 프로그램에 대해 추론 할 수 있습니다.
Michael Easter

6
공정성을 위해 "컴파일하면 전체가 작동 할 것"은 문자 그대로의 사실이 아니라는 구호 입니다. 예, Haskell 프로그래머는 형식 검사기를 통과하면 정확성에 대한 제한적인 개념에 대해 올바른 가능성을 제공하지만 문자 그대로 보편적으로 "진정한"진술은 아닙니다!
Tom Ellis

답변:


230

다음은 Haskell에서 사용할 수 있고 Java에서 사용할 수 없거나 덜 유용한 유형 시스템 기능의 순서가없는 목록입니다 (내 지식으로는 wrt Java는 약합니다)

  • 안전 . 하스켈 타입은 "타입 안전성"속성이 매우 우수합니다. 이것은 매우 구체적이지만 본질적으로 특정 유형의 값이 다른 유형으로 변환하기만을 원할 수 없음을 의미합니다. 이것은 때때로 가변성과 상충됩니다 (OCaml의 값 제한 참조 )
  • 대수 데이터 타입 . Haskell의 유형은 기본적으로 고등학교 수학과 구조가 동일합니다. 이것은 터무니없이 간단 하고 일관성이 있지만 원하는대로 강력합니다. 타입 시스템을위한 훌륭한 토대입니다.
    • 데이터 유형 일반 프로그래밍 . 이것은 제네릭 형식과 동일하지 않습니다 ( 일반화 참조 ). 대신, 앞서 언급 한 것처럼 유형 구조가 단순하기 때문에 해당 구조에서 일반적으로 작동하는 코드를 작성하는 것이 상대적으로 쉽습니다. 나중에 EqHaskell 컴파일러가 uality 와 같은 것을 사용자 정의 유형에 대해 자동 파생시키는 방법에 대해 이야기합니다 . 기본적으로이 작업을 수행하는 방법은 모든 사용자 정의 유형의 기본이되는 공통의 간단한 구조를 살펴보고 값 사이에서 일치시킵니다 (매우 자연스러운 형태의 구조적 평등).
  • 상호 재귀 유형 . 이것은 사소한 유형을 작성하는 데 필수적인 구성 요소입니다.
    • 중첩 유형 . 이를 통해 다른 유형에서 재귀하는 변수에 대해 재귀 유형을 정의 할 수 있습니다. 예를 들어, 한 가지 유형의 균형 트리는입니다 data Bt a = Here a | There (Bt (a, a)). 유효한 값에 대해 신중하게 생각 Bt a하고 해당 유형이 어떻게 작동하는지 확인하십시오. 까다 롭습니다!
  • 일반화 . 이것은 유형 시스템 (아헴, 당신을보고, 이동)에없는 거의 바보입니다. 유형 변수에 대한 개념과 해당 변수의 선택과 무관 한 코드에 대한 대화 능력을 갖는 것이 중요합니다. Hindley Milner는 시스템 F에서 파생 된 유형 시스템입니다. Haskell의 유형 시스템은 HM 타이핑의 정교화이며 시스템 F는 본질적 으로 일반화 의 난로 입니다. 내가 말하고자하는 것은 Haskell이 매우 좋은 일반화 이야기를 가지고 있다는 것입니다.
  • 추상 유형 . 하스켈의 이야기는 훌륭하지 않지만 존재하지 않습니다. 공용 인터페이스는 있지만 개인 구현을 갖는 유형을 작성할 수 있습니다. 이를 통해 나중에 구현 코드에 대한 변경 사항을 인정할 수 있으며, Haskell의 모든 작업의 ​​기초이기 때문에와 같이 잘 정의 된 인터페이스를 가진 "마법"유형을 작성해야합니다 IO. Java는 실제로 더 나은 추상 유형 스토리를 가지고 있지만 정직하게 말하지만 인터페이스가 더 대중화 될 때까지 진정으로 사실이라고 생각하지 않습니다.
  • Parametricity . 하스켈 값이없는 어떤 보편적 인 작업을. Java는 참조 평등 및 해싱과 같은 것, 더욱 강압적으로이를 위반합니다. 이것이 의미하는 바는 유형에 대한 연산 또는 값의 의미를 완전히 유형에서 완전히 알 수있게 해주는 유형에 대한 자유 이론 을 얻는 것 입니다. 특정 유형은 매우 적은 수의 주민 만있을 수 있습니다.
  • 까다로운 것을 인코딩 할 때 더 높은 종류 의 유형이 모든 유형을 표시합니다. Functor / Applicative / Monad, Foldable / Traversable, 전체 mtl효과 입력 시스템, 일반화 된 Functor 수정 점. 목록은 계속 이어집니다. 더 높은 종류에서 가장 잘 표현되는 것들이 많으며 상대적으로 적은 유형의 시스템으로도 사용자가 이러한 것들에 대해 이야기 할 수 있습니다.
  • 클래스를 입력하십시오 . 유형 시스템을 논리로 생각하면 (유용한 경우) 종종 증명해야합니다. 대부분의 경우 이것은 본질적으로 라인 노이즈입니다. 정답은 하나만있을 수 있으며 프로그래머가이를 설명하는 데 많은 시간과 노력이 낭비됩니다. 타입 클래스는 Haskell이 증명을 생성하는 방법입니다. 좀 더 구체적으로 말하면, "우리는 어떤 타입으로 (+)함께 하려고 합니까? 오 Integer, 알았어! 이제 올바른 코드를 인라인하자! "와 같은 간단한 "타입 방정식 시스템"을 해결할 수 있습니다 . 더 복잡한 시스템에서는 더 흥미로운 제약 조건을 설정할 수 있습니다.
    • 제약 미적분 . 타입 클래스 프롤로그 시스템에 도달하기위한 메커니즘 인 Haskell의 제약 조건은 구조적으로 유형화됩니다. 이것은 간단한 형태의 복잡한 구속 조건을 조립할 수 있는 매우 간단한 형태의 하위 유형 관계를 제공합니다. 전체 mtl라이브러리는이 아이디어를 기반으로합니다.
    • 파생 . 타입 클래스 시스템 의 정통성추구 하기 위해서는 사용자 정의 타입이 인스턴스화해야하는 제약 조건을 설명하기 위해 종종 간단한 코드를 많이 작성해야합니다. Haskell 유형의 일반적인 구조를 수행하는 경우 종종 컴파일러 가이 상용구를 요청하도록 할 수 있습니다.
    • class prolog를 입력하십시오 . Haskell 타입 클래스 솔버 (앞서 언급 한 "증거"를 생성하는 시스템)는 본질적으로 더 좋은 의미 론적 특성을 가진 주름진 형태의 프롤로그입니다. 이것은 실제로 털이 많은 것들을 프롤로그 유형으로 인코딩 할 수 있으며 컴파일 타임에 모두 처리 될 것으로 기대합니다. 좋은 예는 순서를 잊었을 때 두 개의 이종 목록이 동일하다는 증거를 해결하는 것일 수 있습니다. 즉, 이질적인 "집합"입니다.
    • 다중 매개 변수 유형 클래스 및 기능 종속성 . 이것들은 기본 타입 클래스 프롤로그에 매우 유용한 개선 사항입니다. 프롤로그를 알고 있다면 둘 이상의 변수의 술어를 쓸 수있을 때 표현력이 얼마나 증가하는지 상상할 수 있습니다.
  • 꽤 좋은 추론 . Hindley Milner 유형 시스템을 기반으로하는 언어는 상당히 유추 할 수 있습니다. HM 자체에는 완전한 추론이 있으므로 유형 변수를 작성할 필요가 없습니다. Haskell의 가장 간단한 형태 인 Haskell 98은 이미 매우 드문 상황에서이를 폐기합니다. 일반적으로 현대 Haskell은 HM에 더 많은 전력을 추가하고 사용자가 불만을 제기하는 것을 보면서 완전한 추론 공간을 천천히 줄이는 실험을 해왔습니다. 사람들은 거의 불평하지 않습니다. 하스켈의 추론은 꽤 좋습니다.
  • 아주 약한 하위 유형 만 . 타입 클래스 프롤로그의 제약 시스템에는 구조적 서브 타이핑이라는 개념이있다. 이것이 하스켈에서 유일한 형태의 하위 유형입니다 . 서브 타이핑은 추론과 추론에 끔찍합니다. 그것은 각각의 문제를 상당히 어렵게 만듭니다 (평등 시스템 대신 불평등 시스템). 오해하기도 정말 쉽습니다 (하위 분류와 같은 하위 분류입니까? 물론 아닙니다! 사람들은 종종 혼동을 일으키고 많은 언어가 그 혼란을 돕습니다! 우리는 어떻게 여기에 왔습니까?
    • 최근 참고 (2017 년 초) Steven Dolan 은 ML 및 Hindley-Milner 유형 추론의 변형 인 MLsub 에 대한 논문 을 발표 했는데 , 이는 매우 좋은 하위 분류 사례를 가지고 있습니다 ( 참고 자료 참조 ). 이것은 내가 위에서 작성한 것을 피하지는 않는다. 대부분의 서브 타이핑 시스템이 고장 나고 유추가 잘못되었다. 이제 완전히 명확하게 설명하자면, Java의 서브 타이핑 개념은 Dolan의 알고리즘과 시스템을 활용할 수 없습니다. 하위 유형이 무엇을 의미하는지 다시 생각해야합니다.
  • 상위 등급 유형 . 나는 일반화에 대해 일찍 이야기했지만, 단순한 일반화 그 이상으로 변수를 일반화 한 유형에 대해 이야기하는 것이 유용 합니다 . 예를 들어, 고차 구조 ( 모수 참조 )와 그 구조가 "포함하는"구조 사이의 매핑 은 다음과 같은 유형을 갖습니다 (forall a. f a -> g a). 스트레이트 HM에서는이 유형에서 함수를 작성할 수 있지만 순위가 높은 유형에서는 다음과 같은 인수 와 같은 함수가 필요합니다 mapFree :: (forall a . f a -> g a) -> Free f -> Free g. 것을 알 a변수 만 인수 내에서 바인딩됩니다. 이것은 함수 의 정의자가 의 사용자가 아니라 함수 를 사용할 때 인스턴스화되는 mapFree것을 결정 한다는 것을 의미합니다 .amapFree
  • 기존 유형 . 더 높은 순위의 유형은 보편적 정량에 대해 이야기 할 수 있지만, 실재적 유형은 우리가 실존 적 정량화에 대해 이야기 할 수있게 합니다 . 이것은 유용하고 결국 더 오래 걸리는 데 오랜 시간이 걸릴 것입니다.
  • 패밀리를 입력하십시오 . 우리가 항상 Prolog에서 생각하지는 않기 때문에 때로는 typeclass 메커니즘이 불편합니다. 타입 패밀리를 사용하면 타입 사이의 기능적 관계를 직접 작성할 수 있습니다.
    • 폐쇄 형 패밀리 . 유형 패밀리는 기본적으로 개방되어있어 성가 시므로 언제든지 확장 할 수 있지만 성공의 희망으로 "반전"할 수 없기 때문입니다. 이것은 주사를 증명할 수 없기 때문에 폐쇄 형 가족을 사용하면 가능합니다.
  • 종류 색인 유형 및 유형 승격 . 나는이 시점에서 정말 이국적이지만, 때때로 실용적으로 사용된다. 열려 있거나 닫힌 핸들 유형을 작성하려면 매우 훌륭하게 수행 할 수 있습니다. 다음 스 니펫에서 State값이 유형 수준으로 승격 된 매우 간단한 대수 유형입니다. 그런 다음, 그 후, 우리는 이야기 할 수 타입 생성자 와 같은 Handle특정에 인수를 복용 같은 종류의 같은 State. 모든 세부 사항을 이해하는 것은 혼란 스럽지만 매우 옳습니다.

    data State = Open | Closed
    
    data Handle :: State -> * -> * where
      OpenHandle :: {- something -} -> Handle Open a
      ClosedHandle :: {- something -} -> Handle Closed a
    
  • 작동하는 런타임 유형 표현 . Java는 유형을 지우고 일부 사람들의 퍼레이드에 비가 오는 것으로 유명합니다. 유형 삭제 올바른 방법이지만, 기능이있는 경우에는 getRepr :: a -> TypeRepr최소한 매개 변수를 위반하는 것입니다. 더 나쁜 것은 런타임에 안전하지 않은 강제 변환을 트리거하는 데 사용되는 사용자 생성 함수 인 경우 엄청난 안전 문제가 있다는 것 입니다. Haskell의 Typeable시스템은 안전한 생성을 허용합니다 coerce :: (Typeable a, Typeable b) => a -> Maybe b. 이 시스템은 Typeable컴파일러 (사용자 영역이 아닌)에서 구현되며 Haskell의 typeclass 메커니즘과 준수해야 할 법칙 없이는 훌륭한 의미를 부여 할 수 없습니다.

그러나 이것 외에도 Haskell의 유형 시스템의 가치는 유형이 언어를 설명하는 방법과 관련이 있습니다. 다음은 유형 시스템을 통해 가치를 창출하는 Haskell의 몇 가지 기능입니다.

  • 순도 . Haskell은 "부작용"을 매우 광범위하게 정의 할 때 부작용을 허용하지 않습니다. 타입은 입력과 출력을 제어하고 부작용없이 입력과 출력에서 모든 것을 고려해야하기 때문에 타입에 더 많은 정보를 입력 해야합니다.
    • IO . 결과적으로 Haskell은 부작용에 대해 이야기 할 수있는 방법이 필요했습니다. 실제 프로그램에는 일부가 포함되어 있어야하므로 유형 클래스, 상위 유형 유형 및 추상 유형의 조합은 IO a대표하는 특수한 특수 유형을 사용한다는 개념을 일으켰습니다. type의 값을 초래하는 부작용 계산 a. 이것은 순수한 언어에 포함 된 아주 좋은 효과 시스템 의 기초입니다 .
  • 부족null . null현대 프로그래밍 언어에서 수십억 달러의 실수 라는 것은 누구나 알고 있습니다. 대수 유형, 특히 유형을 유형 A으로 변환하여 "존재하지 않음"상태를 유형에 추가하는 기능 Maybe A은의 문제를 완전히 완화합니다 null.
  • 다형성 재귀 . 이를 통해 고유 한 일반화에서 각 재귀 호출에서 서로 다른 유형을 사용하더라도 유형 변수를 일반화하는 재귀 함수를 정의 할 수 있습니다. 이것은 말하기 어렵지만 특히 중첩 유형에 대해 이야기 할 때 유용합니다. Bt a이전부터 유형을 되돌아보고 크기를 계산하는 함수를 작성하십시오 size :: Bt a -> Int. 이 같은 비트를 살펴 보겠습니다 size (Here a) = 1하고 size (There bt) = 2 * size bt. 작동 방식은 너무 복잡하지 않지만 size마지막 방정식에서 재귀 호출 은 다른 유형으로 발생 하지만 전반적인 정의에는 일반화 된 유형이 size :: Bt a -> Int있습니다. 이것은 전체 추론을 깨뜨리는 기능이지만, 유형 서명을 제공하면 Haskell이 허용합니다.

계속 갈 수는 있지만,이 목록은 당신을 시작하고 그 다음에해야합니다.


7
널은 10 억 달러짜리 "실수"가 아니었다. 할 수없는 경우가 있습니다 정적으로 어떤 전에 포인터 역 참조되지 않습니다 확인 의미는 아마도 수있는 존재는 ; 이러한 경우 역 참조 트랩을 시도하면 포인터가 처음에 의미없는 객체를 식별하도록 요구하는 것보다 낫습니다. 나는 가장 큰 널 관련 실수하는, 주어진 구현을 가졌다 생각 char *p = NULL;에, 의지 트랩 *p=1234하지만 함정하지 않습니다 char *q = p+5678;*q = 1234;
supercat

37
Tony Hoare에서 인용 한 내용은 en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions 입니다. null포인터 산술에 필요할 때가 있다고 확신하지만 대신 포인터 산술은 null이 여전히 실수가 아니라는 것이 아니라 언어의 의미를 호스팅하기에 나쁜 장소라고 해석합니다.
J. Abrahamson

18
@ supercat, 실제로 null없이 언어를 작성할 수 있습니다. 허용 여부는 선택입니다.
Paul Draper

6
@supercat-이 문제는 Haskell에도 존재하지만 다른 형태로 존재합니다. 하스켈은 일반적으로 게으르고 불변이며, 따라서 당신이 쓸 수있는 p = undefined너무 오래로 p평가되지 않습니다. 더 유용하게 undefined는 평가하지 않는 한 일종의 변경 가능한 참조를 넣을 수 있습니다 . 더 심각한 문제는 종료되지 않을 수있는 게으른 계산으로, 물론 결정하기 어려운 것입니다. 가장 큰 차이점은 이것들이 모두 명백한 프로그래밍 결함 이며 일반적인 논리를 표현하는 데 사용되지 않는다는 것입니다.
Christian Conkle

6
@supercat Haskell은 참조 의미론이 완전히 부족합니다 (참조 참조를 참조 로 대체하여 모든 것이 보존되도록하는 참조 투명성 의 개념입니다 ). 따라서 귀하의 질문이 잘못 제기 된 것 같습니다.
J. Abrahamson

78
  • 풀 타입 추론. "거짓 쓰레기, 내가 쓰는 모든 일은 형식 서명을 작성하는 것"과 같은 느낌없이 복잡한 유형을 어디에서나 사용할 수 있습니다 .
  • 유형은 완전히 대수적 이므로 복잡한 아이디어를 표현하기가 매우 쉽습니다.
  • Haskell에는 유형 클래스가 있습니다. 인터페이스는 일종의 인터페이스와 비슷하지만 한 유형에 대한 모든 구현을 동일한 장소에 배치 할 필요는 없습니다. 소스에 액세스 할 필요없이 기존 써드 파티 유형에 대해 고유 한 유형 클래스 구현을 작성할 수 있습니다.
  • 고차 및 재귀 함수는 유형 검사기의 범위에 더 많은 기능을 넣는 경향이 있습니다. 예를 들어 필터를 사용 하십시오 . 명령형 언어에서는 for동일한 기능을 구현하기 위해 루프를 작성할 수 있지만 for루프에는 리턴 유형의 개념이 없으므로 동일한 정적 유형을 보장하지는 않습니다.
  • 하위 유형이 없으면 파라 메트릭 다형성이 크게 단순화됩니다.
  • Haskell에서는 더 높은 종류의 유형 (유형 유형)을 지정하고 사용하기가 비교적 쉽습니다.이를 통해 Java에서는 전혀 이해할 수없는 유형에 대한 추상화를 만들 수 있습니다.

7
좋은 대답-더 높은 종류의 간단한 예를 들어 주시면 Java에서 왜 불가능한지를 이해하는 데 도움이 될 것이라고 생각하십시오.
phatmanace

3
여기 좋은 예가 있습니다 .
Karl Bielefeldt

3
패턴 일치도 매우 중요합니다. 즉, 객체 유형을 사용하여 쉽게 의사 결정을 내릴 수 있습니다.
Benjamin Gruenbaum

2
@ BenjaminGruenbaum 타입 시스템 기능이라고 생각하지 않습니다.
Doval

3
ADT를하고 HKTs가의 definetly 나는이 질문을 누군가가 유용한 이유를 알고 것입니다 의심 해답의 일부입니다 동안, 나는 두 부분이 설명을 확장 할 필요가 있다고 제안
JK합니다.

62
a :: Integer
b :: Maybe Integer
c :: IO Integer
d :: Either String Integer

Haskell에서 : 정수, null 일 수있는 정수, 값이 외부 세계에서 온 정수 및 대신 문자열 일 수있는 정수는 모두 다른 유형이며 컴파일러는 이것을 강제합니다 . 이러한 차이점을 존중하지 않는 Haskell 프로그램을 컴파일 할 수 없습니다.

그러나 형식 선언을 생략 할 수 있습니다. 대부분의 경우 컴파일러는 변수에 대한 가장 일반적인 형식을 결정하여 성공적인 컴파일을 수행 할 수 있습니다. 그렇지 않습니까?


11
이 답변이 완료되지 않은 동안 +1 나는 질문의 수준에서 훨씬 더 나은 것으로 생각합니다
jk.

1
+1 다른 언어 Maybe(예 : Java Optional및 Scala Option) 도 설명하는 데 도움이 되지만, 해당 언어에서는 항상 null해당 유형의 변수에 할당 할 수 있고 프로그램 실행시 폭발 할 수 있으므로 절반에 달하는 해결책입니다. 시각. 이는 null 값이 없기 때문에 Haskell [1]에서는 발생할 수 없으므로 간단히 속일 수는 없습니다. ([1] : 실제로,가 fromJust있을 때 와 같은 부분 함수를 사용하여 NullPointerException과 유사한 오류를 생성 할 수 Nothing있지만 이러한 함수는 찌그러 질 수 있습니다 ).
Andres F.

2
"값이 외부 세계에서 온 정수"- IO Integer'실행시 정수를 제공하는 서브 프로그램'에 더 가깝지 않습니까? 에서)와 같은 main = c >> c제에 의해 리턴 된 값은 c초 단위로 다음 상이해도 c잠시 ab) 그 sanatisation을 적용 외부로부터 값을 나타내고 유형이있다) 등 우리가 하나의 범위에있는 바와 같이 (배치 위치에 관계없이 동일한 값을 갖 (즉, 직접 입력하지 말고 먼저 사용자의 입력이 올바른지 악성인지 확인하십시오).
Maciej Piechotka

4
Maciej, 더 정확할 것입니다. 나는 단순성을 위해 노력했다.
WolfeFan

30

많은 사람들이 Haskell에 대해 좋은 것을 나열했습니다. 그러나 "유형 시스템이 왜 프로그램을 더 정확하게 만드는가?"라는 특정 질문에 대한 답으로 "파라 메트릭 다형성"이라고 생각합니다.

다음 Haskell 기능을 고려하십시오.

foobar :: x -> y -> y

문자 그대로하나의 가능한 방법 이 기능을 구현하는가. 타입 시그니처 만으로도 가능한 한 가지 기능이 있기 때문에이 함수의 기능을 정확하게수 있습니다 . [괜찮아요, 거의!]

잠시 멈추고 그것에 대해 생각하십시오. 실제로 큰 문제입니다! 내가이 서명으로 함수를 작성하는 경우, 실제로 뜻 불가능 함수가 내가 의도 한 것보다 다른 아무것도 할 수 있도록. (물론 서명 자체는 여전히 틀릴 수 있습니다. 프로그래밍 언어가 모든 버그를 막을 수는 없습니다 .)

이 기능을 고려하십시오.

fubar :: Int -> (x -> y) -> y

이 기능은 불가능 합니다. 말 그대로이 기능을 구현할 수 없습니다. 타입 시그니처에서 알 수 있습니다.

보시다시피, Haskell 유형 서명은 많은 지옥을 알려줍니다!


C #과 비교하십시오. (죄송합니다. 제 Java는 조금 녹슬 었습니다.)

public static TY foobar<TX, TY>(TX in1, TY in2)

이 방법으로 수행 할 수있는 몇 가지 작업이 있습니다.

  • in2결과로 반환 합니다.
  • 영원히 반복하고 아무것도 반환하지 마십시오.
  • 예외를 던지고 아무것도 반환하지 마십시오.

실제로 Haskell에는이 세 가지 옵션도 있습니다. 그러나 C #은 추가 옵션도 제공합니다.

  • null을 돌려줍니다. (Haskell에는 null이 없습니다.)
  • in2반품하기 전에 수정 하십시오. (Haskell은 전체 수정이 없습니다.)
  • 반사를 사용하십시오. (Haskell에는 반사가 없습니다.)
  • 결과를 반환하기 전에 여러 I / O 작업을 수행하십시오. (여기에서 I / O 수행을 선언하지 않으면 Haskell에서 I / O를 수행 할 수 없습니다.)

반사는 특히 큰 망치입니다. 반사를 사용하여 TY얇은 공기 로 새로운 물체를 만들어 그것을 되돌릴 수 있습니다! 두 개체를 모두 검사하고 찾은 내용에 따라 다른 작업을 수행 할 수 있습니다. 전달 된 두 객체를 임의로 수정할 수 있습니다.

I / O도 마찬가지로 큰 망치입니다. 이 코드는 사용자에게 메시지를 표시하거나 데이터베이스 연결을 열거 나 하드 디스크 등을 다시 포맷하는 것일 수 있습니다.


foobar대조적으로 Haskell 함수 는 일부 데이터 가져 와서 해당 데이터를 변경없이 반환 할 수 있습니다 . 컴파일 타임에 해당 유형을 알 수 없으므로 데이터를 "볼 수 없습니다". 새로운 데이터를 만들 수는 없습니다. 왜냐하면 ... 가능한 모든 유형의 데이터를 어떻게 구성합니까? 그것에 대해 반성이 필요합니다. 형식 서명이 I / O가 수행되고 있음을 선언하지 않기 때문에 I / O를 수행 할 수 없습니다. 따라서 파일 시스템이나 네트워크와 상호 작용하거나 심지어 동일한 프로그램에서 스레드를 실행할 수 없습니다! (즉, 스레드 안전 100 % 보장입니다.)

보시다시피 , Haskell은 많은 일을 하지 않도록 함으로써 코드가 실제로하는 일에 대해 매우 강력하게 보장 할 수 있습니다. 사실, (실제 다형성 코드의 경우) 조각이 서로 맞을 수있는 방법은 한 가지뿐입니다.

(명확하게 : 타입 시그니처가 많은 것을 말하지 않는 Haskell 함수를 작성하는 것은 여전히 ​​가능합니다 Int -> Int. 그러나 아무 것도 할 수는 있지만, 동일한 입력이 항상 100 % 확실하게 동일한 출력을 생성한다는 것을 알고 있습니다. Java는 심지어 그것을 보장하지 않습니다!)


4
+1 좋은 답변입니다! 이것은 매우 강력하며 Haskell을 처음 접하는 사람들은 종종 저평가합니다. 그건 그렇고, 더 단순한 "불가능한"기능 fubar :: a -> b은 그렇지 않습니까? (예, 알고 unsafeCoerce있습니다. 이름에 "안전하지 않은"항목에 대해서는 이야기하고 있지 않으며, 초보자도 걱정하지 않아도됩니다! : D)
Andres F.

작성할 수없는 더 간단한 유형 서명이 많이 있습니다. 예. 예를 들어, foobar :: x구현이 불가능합니다 ...
MathematicalOrchid

사실, 당신은 할 수없는 순수한 코드가 스레드 안전하게,하지만 당신은 여전히 멀티 스레드 수 있습니다. "평가하기 전에 평가", "평가할 때 별도의 스레드에서도이를 평가할 수 있습니다"및 "평가할 때이를 평가할 수도 있습니다. 별도의 스레드에 ". 기본값은 "원하는대로 수행"이며 기본적으로 "가능한 한 늦게 평가"를 의미합니다.
John Dvorak

보다 유망하게 부작용이있는 in1 또는 in2에서 인스턴스 메소드를 호출 할 수 있습니다. 또는 전역 상태를 수정할 수 있습니다 (허가 된 경우 Haskell에서 IO 작업으로 모델링되었지만 대부분의 사람들이 IO로 생각하지 않을 수 있음).
Doug McClean

2
@isomorphismes 형식 x -> y -> y은 완벽하게 구현할 수 있습니다. 유형 (x -> y) -> y이 아닙니다. 형식 x -> y -> y은 두 개의 입력을 취하고 두 번째 입력을 반환합니다. 유형 (x -> y) -> y은 작동 하는 기능 을 수행하고 x어떻게 든 y그것을 밖으로 만들어야 합니다 ...
MathematicalOrchid

17

관련 SO 질문 .

나는 당신이 haskell에서 똑같이 할 수 있다고 가정합니다 (즉, 일류 데이터 유형이어야하는 문자열 / int에 물건을 넣습니다)

아니요, 적어도 Java와 같은 방식으로는 할 수 없습니다. Java에서는 이런 종류의 일이 발생합니다.

String x = (String)someNonString;

Java는 행복하게 String이 아닌 String을 시도하고 캐스팅합니다. Haskell은 이러한 종류의 작업을 허용하지 않으므로 전체 클래스의 런타임 오류가 제거됩니다.

null는 형식 시스템의 일부 Nothing이므로 ( ) 명시 적으로 요청하고 처리해야하므로 다른 모든 클래스의 런타임 오류가 제거됩니다.

의사 소통 할만큼 잘 알고있는 전문 지식이없는 재사용 및 타입 클래스와 관련된 다른 미묘한 이점도 많이 있습니다.

그러나 대부분 Haskell의 타입 시스템은 많은 표현력을 허용하기 때문입니다. 몇 가지 규칙만으로도 많은 일을 할 수 있습니다. 항상 존재하는 Haskell 트리를 고려하십시오.

data Tree a = Leaf a | Branch (Tree a) (Tree a) 

상당히 일반적인 한 줄의 코드로 전체 이진 트리 (및 두 개의 데이터 생성자)를 정의했습니다. 모두 몇 가지 규칙 만 사용합니다 ( 합계 유형 및 제품 유형 사용 ). 그것은 Java의 3-4 코드 파일과 클래스입니다.

특히 유형 시스템을 개척하기 쉬운 사람들 중에서 이러한 종류의 간결함 / 우아함이 매우 중요합니다.


귀하의 답변에서 NullPointerExceptions 만 이해했습니다. 더 많은 예제를 포함시킬 수 있습니까?
Jesvin Jose

2
반드시 사실 일 필요 없습니다 . JLS §5.5.1 : T가 클래스 유형이면 | S | <: | T | 또는 | T | <: | S |. 그렇지 않으면 컴파일 타임 오류가 발생합니다. 따라서 컴파일러는 변환 할 수없는 유형을 캐스팅 할 수 없습니다. 확실히 해결할 수있는 방법이 있습니다.
거미 보리스

제 생각에는 형식 클래스의 장점을 넣는 가장 간단한 방법은 interface실제로 추가 할 수있는 형식과 같 으며 구현하는 형식을 "잊어 버리지 않는다"는 것입니다. 즉, interfaceList<String>구현이 다른 구현 과 달리 함수에 대한 두 개의 인수가 동일한 유형을 갖도록 할 수 있습니다. 모든 인터페이스에 유형 매개 변수를 추가하여 Java에서 기술적으로 매우 유사한 작업을 수행 할 수 있지만 기존 인터페이스의 99 %가이를 수행하지 않으므로 동료와 혼동을 일으킬 수 있습니다.
Doval

2
@BoristheSpider True이지만 예외를 캐스팅하는 것은 거의 항상 수퍼 클래스에서 서브 클래스로 또는 인터페이스에서 클래스로 다운 캐스팅하는 것과 관련이 있으며, 수퍼 클래스가 드문 일이 아닙니다 Object.
Doval

2
문자열에 대한 질문의 요점은 캐스팅 및 런타임 유형 오류와 관련이 없지만 유형 을 사용 하지 않으려는 경우 Java는 데이터를 직렬화 된 상태로 실제로 저장 하지 않습니다. 임시 any형식 으로 문자열을 남용하는 형식입니다. Haskell은이 작업을 중단하지 않습니다. 왜냐하면 문자열이 있기 때문입니다. Haskell은 도구를 제공 할 수 있습니다 . 중첩 된 컨텍스트에서 다시 해석하기에 충분한 인터프리터가 Greenspunning을 고집한다면 어리석은 일을 강제로 막을 수 없습니다 null. 언어는 할 수 없습니다.
Leushenko

0

익숙한 사람들이 자주 이야기하는 'memes'중 하나는 "컴파일하면 작동합니다"라는 것입니다.

이것은 작은 프로그램에서 대부분 사실입니다. Haskell은 다른 언어로 쉽게 실수를하는 것을 방지합니다 (예 : Int32과 a Word32와 무언가를 비교하는 것). 그러나 모든 실수를 막는 것은 아닙니다.

Haskell은 실제로 리팩토링을 훨씬 쉽게 만듭니다. 프로그램이 이전에 정확하고 타입 검사를 한 경우, 사소한 발표 후에도 여전히 정확할 것입니다.

이 점에서 Haskell이 다른 정적 유형의 언어보다 더 나은 이유를 이해하려고합니다.

Haskell의 유형은 새 유형을 쉽게 선언 할 수 있다는 점에서 매우 가볍습니다. 이것은 Rust와 같은 언어와 대조적이며 모든 것이 조금 더 성가시다.

내 가정은 이것이 사람들이 강력한 유형 시스템에 의해 의미하는 것이라고 가정하지만 하스켈이 더 나은 이유는 분명하지 않습니다.

Haskell은 단순한 합계 및 제품 유형 이외의 많은 기능을 가지고 있습니다. 그것은 보편적으로 정량화 된 유형 (예 :) id :: a -> a도 가지고있다. Java 또는 Rust와 같은 언어와는 다른 함수를 포함하는 레코드 유형을 작성할 수도 있습니다.

GHC는 유형 만 기반으로 일부 인스턴스를 파생시킬 수 있으며, 제네릭이 출현 한 이후 유형간에 일반적인 함수를 작성할 수 있습니다. 이것은 매우 편리하며 Java에서보다 더 유창합니다.

또 다른 차이점은 Haskell이 (적어도 현재) 상대적으로 좋은 유형 오류를 갖는 경향이 있다는 것입니다. Haskell의 형식 유추는 정교하므로 컴파일 할 무언가를 얻기 위해 형식 주석을 제공해야하는 경우는 매우 드 rare니다. 이는 컴파일러가 원칙적으로 형식을 추론 할 수있는 경우에도 형식 유추에 주석이 필요할 수있는 Rust와는 대조적입니다.

마지막으로 Haskell에는 유명한 모나드 유형 클래스가 있습니다. 모나드는 오류를 처리하는 특히 좋은 방법입니다. 기본적으로 null끔찍한 디버깅이나 유형 안전을 포기하지 않고 거의 모든 편의를 제공합니다. 따라서 이러한 유형의 함수 를 작성하는 기능 은 실제로 사용하도록 권장 할 때 매우 중요합니다!

다른 방법으로, 당신은 좋은 또는 나쁜 자바를 작성할 수 있습니다, 나는 당신이 하스켈에서 똑같이 할 수 있다고 가정합니다.

아마도 사실이지만 결정적인 요점은 없습니다. 하스켈에서 발로 자신을 쏘기 시작하는 지점은 Java로 발을 촬상 할 때보 다 더 중요합니다.

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