Development Tip

Haskell 대수 데이터 유형이 "닫힌"이유는 무엇입니까?

yourdevel 2020. 12. 13. 11:15
반응형

Haskell 대수 데이터 유형이 "닫힌"이유는 무엇입니까?


내가 틀렸다면 정정하십시오. 그러나 Haskell의 대수 데이터 유형은 OO 언어로 클래스와 상속을 사용하는 많은 경우에 유용합니다. 그러나 큰 차이가 있습니다. 일단 대수 데이터 유형이 선언되면 다른 곳으로 확장 할 수 없습니다. "폐쇄"입니다. OO에서는 이미 정의 된 클래스를 확장 할 수 있습니다. 예를 들면 :

data Maybe a = Nothing | Just a

이 선언을 수정하지 않고 나중에이 유형에 다른 옵션을 추가 할 수있는 방법은 없습니다. 그렇다면이 시스템의 이점은 무엇입니까? OO 방식이 훨씬 더 확장 가능한 것 같습니다.


ADT가 닫혀 있다는 사실은 전체 함수를 작성하는 것을 훨씬 쉽게 만듭니다. 그 유형의 가능한 모든 값에 대해 항상 결과를 생성하는 함수입니다.

maybeToList :: Maybe a -> [a]
maybeToList Nothing  = []
maybeToList (Just x) = [x]

Maybe열려 있으면 누군가 추가 생성자를 추가 할 수 있고 maybeToList함수가 갑자기 중단됩니다.

OO에서는 상속을 사용하여 유형을 확장 할 때 문제가되지 않습니다. 특정 오버로드가없는 함수를 호출 할 때 수퍼 클래스에 대한 구현을 사용할 수 있기 때문입니다. 즉,가의 하위 클래스 이면 객체로 printPerson(Person p)호출 할 수 있습니다 .StudentStudentPerson

Haskell에서는 유형을 확장해야 할 때 일반적으로 캡슐화 및 유형 클래스를 사용합니다. 예를 들면 :

class Eq a where
   (==) :: a -> a -> Bool

instance Eq Bool where
  False == False = True
  False == True  = False
  True  == False = False
  True  == True  = True

instance Eq a => Eq [a] where
  []     == []     = True
  (x:xs) == (y:ys) = x == y && xs == ys
  _      == _      = False

이제 ==함수가 완전히 열려 있으므로 Eq클래스 의 인스턴스로 만들어 고유 한 유형을 추가 할 수 있습니다 .


확장 가능한 데이터 유형 에 대한 작업이 있었지만 아직 Haskell의 일부가 아닙니다.


대답은 코드를 확장하기 쉬운 방법, 클래스와 대수 데이터 유형 간의 긴장, Phil Wadler가 "표현 문제"라고 명명 한 것과 관련이 있습니다.

  • 대수 데이터 유형을 사용하면

    • 새로운 작업 을 추가하는 것은 매우 저렴 합니다. 새 함수를 정의하기 만하면됩니다. 이러한 것들에 대한 모든 이전 기능은 변경되지 않고 계속 작동합니다.

    • 새로운 종류 를 추가하는 것은 매우 비쌉니다 . 기존 데이터 유형에 새 생성자를 추가해야하고 해당 유형을 사용하는 모든 함수편집하고 다시 컴파일해야합니다 .

  • 수업을 통해

    • 새로운 종류의 것을 추가하는 것은 매우 저렴 합니다. 새 하위 클래스를 추가하고 필요에 따라 기존의 모든 작업에 대해 해당 클래스에서 특수 메서드를 정의합니다. 수퍼 클래스와 다른 모든 서브 클래스는 변경없이 계속 작동합니다.

    • 매우이다 비싼 새로운 추가하는 일에 작업을 : 당신이해야 할 슈퍼 클래스에 새로운 메소드 선언을 추가 하고 잠재적으로 모든 기존의 서브 클래스에 메서드 정의를 추가 . 실제로 부담은 방법에 따라 다릅니다.

따라서 닫힌 유형이 특정 종류의 프로그램 진화를 잘 지원하기 때문에 대수 데이터 유형이 닫힙니다. 예를 들어 데이터 유형이 언어를 정의하는 경우 이전 컴파일러 패스를 무효화하거나 데이터를 변경하지 않고도 새 컴파일러 패스를 쉽게 추가 할 수 있습니다.

"열린"데이터 유형을 가질 수 있지만 신중하게 제어 된 상황을 제외하고는 유형 검사가 어려워집니다. Todd Millstein 은 개방형 대수 유형과 확장 가능한 함수를 지원하는 언어 설계에 대해 매우 아름다운 작업수행 했으며, 모두 모듈 형 유형 검사기를 사용했습니다. 나는 그의 논문이 읽기에 큰 기쁨을 느꼈다.


다음과 같은 함수를 작성하면

maybeToList Nothing = []
maybeToList (Just x) = [x]

그런 다음 모든 경우를 다루었으므로 런타임 오류가 발생하지 않을 것임을 알고 있습니다. Maybe 유형이 확장 가능 해지면 이는 사실이 아닙니다. 확장 가능한 유형이 필요한 경우 (생각보다 드문 경우) 표준 Haskell 솔루션은 유형 클래스를 사용하는 것입니다.


"오픈 데이터 유형 및 오픈 함수"를 확인 하십시오. http://lambda-the-ultimate.org/node/1453

객체 지향 언어에서는 새로운 클래스를 정의하여 데이터를 확장하기 쉽지만 새로운 기능을 추가하기는 어렵습니다. 함수형 언어에서는 상황이 역전됩니다. 새 함수를 추가해도 문제가 발생하지 않지만 데이터를 확장 (새 데이터 생성자 추가)하려면 기존 코드를 수정해야합니다 . 양방향 확장 성을 지원하는 문제를 표현 문제라고합니다.. 우리는 Haskell 언어의 표현 문제에 대한 간단한 해결책으로 개방형 데이터 유형과 개방형 함수를 제시합니다. 아이디어는 개방형 데이터 유형의 생성자와 개방형 함수의 방정식이 프로그램 전체에 흩어져 나타날 수 있다는 것입니다. 특히 다른 모듈에 상주 할 수 있습니다. 의도 한 의미는 다음과 같습니다. 프로그램은 마치 데이터 유형과 함수가 한곳에서 정의 된 것처럼 작동해야합니다. 함수 방정식의 순서는 특정 패턴이 불특정 패턴보다 우선하는 최적 패턴 일치에 의해 결정됩니다. 우리의 솔루션이 표현 문제, 일반 프로그래밍 및 예외에 적용 가능함을 보여줍니다. 두 가지 구현을 스케치합니다. 의미론에서 파생 된 간단한 것, 별도의 컴파일을 허용하는 상호 재귀 모듈을 기반으로하는 것.


First, as counterpoint to Charlie's answer, this isn't intrinsic to functional programming. OCaml has the concept of open unions or polymorphic variants, which essentially do what you want.

As for why, I believe this choice was made for Haskell because

  • this lets types be predictable - their are only a finite number of constructors for each
  • it's easy to define your own types.
  • many Haskell functions are polymorphic, and classes let you extend custom types to fit function parameters (think Java's Interfaces).

So if you'd rather have a data Color r b g = Red r | Blue b | Green g type, it's easy to make, and you can easily make it act like a monad or a functor or however other functions need.


Some excellent answers on this (admittedly old) question, but I feel I have to throw my few cents in.

There is no way that I can somehow add another option to this type later on without modifying this declaration. So what are the benefits of this system? It seems like the OO way would be much more extensible.

The answer to this, I believe, is that the sort of extensibility that open sums give you is not always a plus, and that, correspondingly, the fact that OO forces this on you is a weakness.

The advantage of closed unions is their exhaustiveness: if you have fixed all the alternatives at compilation time, then you can be certain that there will be no unforeseen cases that your code cannot handle. This is a valuable property in many problem domains, for example, in abstract syntax trees for languages. If you're writing a compiler, the expressions of the language fall into a predefined, closed set of subcases—you do not want people to be able to add new subcases at runtime that your compiler doesn't understand!

In fact, compiler ASTs are one of the classic Gang of Four motivating examples for the Visitor Pattern, which is the OOP counterpart to closed sums and exhaustive pattern matching. It is instructive to reflect on the fact that OO programmers ended up inventing a pattern to recover closed sums.

Likewise, procedural and functional programmers have invented patterns to obtain the effect of sums. The simplest one is the "record of functions" encoding, which corresponds to OO interfaces. A record of functions is, effectively, a dispatch table. (Note that C programmers have been using this technique for ages!) The trick is that there is very often a large number of possible functions of a given type—often infinitely many. So if you have a record type whose fields are functions, then that can easily support an astronomically large or infinite set of alternatives. And what's more, since records are created at runtime and can be done flexibly based on runtime conditions, the alternatives are late bound.

The final comment I'd make is that, in my mind, OO has made too many people believe that extensibility is synonymous with late binding (e.g., the ability to add new subcases to a type at runtime), when this just isn't generally true. Late binding is one technique for extensibility. Another technique is composition—building complex objects out of a fixed vocabulary of building blocks and rules for assembling them together. The vocabulary and rules are ideally small, but designed so that they have rich interactions that allow you to build very complex things.

Functional programming—and the ML/Haskell statically typed flavors in particular—have long emphasized composition over late binding. But in reality, both kinds of techniques exist in both paradigms, and should be in the toolkit of a good programmer.

It's also worth noting that programming languages themselves are fundamentally examples of composition. A programming language has a finite, hopefully simple syntax that allows you to combine its elements to write any possible program. (This in fact goes back to the compilers/Visitor Pattern example above and motivates it.)


Another (more or less) intuitive way of looking at data types and typeclasses versus object oriented classes is the following:

A class Foo in an OO language represents both a concrete type Foo as well as the class of all Foo-types: those which are directly or indirectly derived from Foo.

In OO languages, you just happen to implicitly program against the class of Foo-types which allows you to "extend" Foo.


Okay, by "open" here you're meaning "can be derived from" and not open in the sense of Ruby and Smalltalk, where you can extend a class with new methods at run-time, right?

In any case, notice two things: first, in most OO languages that are primarily inheritance-based, there is a way to declare a class to restrict it's ability to be inherited. Java has "final", and there are hacks for this in C++. So on that, it's just making as the default an option in other OO languages.

Second, you can still create a new type that uses the closed ADT and adds other methods or different implementations. So you aren't really restricted in that way. Again, they seem to formally have the same strength; what you can express in one can be expressed in the other.

The real thing is that functional programming really is a different paradigm ("pattern"). If you go into it with the expectation that it ought to be like an OO language, you'll be surprised regularly.

참고URL : https://stackoverflow.com/questions/870919/why-are-haskell-algebraic-data-types-closed

반응형