Haskell 유형 대 데이터 생성자


124

learnyouahaskell.com 에서 Haskell을 배우고 있습니다. 유형 생성자와 데이터 생성자를 이해하는 데 문제가 있습니다. 예를 들어, 나는 이것의 차이점을 정말로 이해하지 못합니다.

data Car = Car { company :: String  
               , model :: String  
               , year :: Int  
               } deriving (Show) 

이:

data Car a b c = Car { company :: a  
                     , model :: b  
                     , year :: c   
                     } deriving (Show)  

첫 번째는 단순히 하나의 생성자 ( Car)를 사용하여 유형의 데이터를 구축 하는 것임을 이해합니다 Car. 나는 두 번째 것을 정말로 이해하지 못한다.

또한 다음과 같이 데이터 유형을 어떻게 정의합니까?

data Color = Blue | Green | Red

이 모든 것에 적합합니까?

내가 이해 한 바에 따르면 세 번째 예 ( Color)는 Blue, Green또는 Red. 그러나 그것은 내가 처음 두 가지 예를 이해하는 방법과 충돌합니다. 유형 Car이 하나의 상태 일 수만 있고 Car빌드하는 데 다양한 매개 변수를 사용할 수있는 것입니까? 그렇다면 두 번째 예는 어떻게 맞습니까?

본질적으로 위의 세 가지 코드 예제 / 구성을 통합하는 설명을 찾고 있습니다.


18
Car 예제는 Car형식 생성자 (의 왼쪽에 있음 =)와 데이터 생성자 (오른쪽에 있음) 모두 이므로 약간 혼란 스러울 수 있습니다 . 첫 번째 예제에서 Car형식 생성자는 인수를받지 않고 두 번째 예제에서는 3 개를 사용합니다. 두 예제 모두에서 Car데이터 생성자는 세 개의 인수를 사용합니다 (하지만 이러한 인수의 유형은 한 경우에는 고정되고 다른 경우에는 매개 변수화됩니다).
Simon Shine

첫 번째는 단순히 하나의 데이터 생성자 ( Car :: String -> String -> Int -> Car)를 사용하여 유형의 데이터를 빌드하는 것입니다 Car. 두 번째는 단순히 하나의 데이터 생성자 ( Car :: a -> b -> c -> Car a b c)를 사용하여 유형의 데이터를 빌드하는 것입니다 Car a b c.
윌 네스

답변:


228

A의 data선언하는 형식의 생성자는 등호 기호의 왼쪽에있는 것입니다. 데이터 생성자 (들)은 등호 (=)의 오른쪽에있는 것들입니다. 형식이 예상되는 곳에 형식 생성자를 사용하고 값이 예상되는 곳에 데이터 생성자를 사용합니다.

데이터 생성자

간단하게하기 위해 색상을 나타내는 유형의 예부터 시작할 수 있습니다.

data Colour = Red | Green | Blue

여기에는 세 개의 데이터 생성자가 있습니다. Colour유형이고 Green유형 값을 포함하는 생성자입니다 Colour. 비슷하게,RedBlue은 유형 값을 생성하는 두 생성자입니다 Colour. 우리는 그것을 spicing하는 것을 상상할 수 있습니다!

data Colour = RGB Int Int Int

우리는 여전히 타입 만 가지고 Colour있지만 RGB값은 아닙니다. 그것은 3 개의 Int를 받고 반환 하는 함수입니다. 값을 하는 ! RGB유형이 있습니다

RGB :: Int -> Int -> Int -> Colour

RGB일부 을 인수로 취한 다음이를 사용하여 새 값을 생성하는 함수 인 데이터 생성자입니다 . 객체 지향 프로그래밍을 수행했다면이를 인식해야합니다. OOP에서 생성자는 일부 값을 인수로 취하고 새 값을 반환합니다!

이 경우 신청하면 RGB 세 개의 값에 하면 색상 값을 얻습니다!

Prelude> RGB 12 92 27
#0c5c1b

우리는 유형 의 값구성했습니다.Colour 데이터 생성자를 적용하여. 데이터 생성자는 변수와 같은 값을 포함하거나 다른 값을 인수로 사용하여 새 값을 만듭니다 . 이전 프로그래밍을 해본 적이 있다면이 개념이별로 이상하지 않을 것입니다.

중지

Strings 를 저장하기 위해 이진 트리를 구성 하려면 다음과 같은 작업을 수행하는 것을 상상할 수 있습니다.

data SBTree = Leaf String
            | Branch String SBTree SBTree

여기에서 볼 수있는 것은 SBTree두 개의 데이터 생성자를 포함 하는 유형 입니다. 즉, 값을 구성하는 두 개의 함수 (즉 LeafBranch)가 있습니다.SBTree 유형의 있습니다. 바이너리 트리가 작동하는 방식에 익숙하지 않다면 그냥 거기에 머 무르십시오. 실제로 바이너리 트리가 어떻게 작동하는지 알 필요는 없습니다. 단지이 트리 String가 어떤 방식으로 s를 저장한다는 것입니다.

우리는 또한 두 데이터 생성자가 String 인수를받는 것을 볼 수 있습니다. 이것은 트리에 저장할 문자열입니다.

그러나! 을 저장할 수 있기를 원한다면 Bool새로운 바이너리 트리를 만들어야합니다. 다음과 같이 보일 수 있습니다.

data BBTree = Leaf Bool
            | Branch Bool BBTree BBTree

유형 생성자

SBTree및 둘 다 BBTree형식 생성자입니다. 그러나 눈에 띄는 문제가 있습니다. 그들이 얼마나 비슷한 지 보십니까? 그것은 당신이 정말로 어딘가에 매개 변수를 원한다는 신호입니다.

그래서 우리는 이것을 할 수 있습니다 :

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

이제 유형 생성자에 매개 변수 a유형 변수 를 소개합니다 . 이 선언에서 BTree함수가되었습니다. 인수로 유형 을 취하고 새로운 유형을 반환합니다 .

여기서는 프로그램의 값에 할당 할 수있는 유형 인 구체적인 유형 (예 Int: [Char]및 포함 ) 과 유형 을 공급해야하는 유형 생성자 함수 의 차이점을 고려하는 것이 중요 합니다. 값에 할당됩니다. 그것은 "목록 할 필요가 있기 때문에 값, 유형"목록 "이 될 수 없다 무엇인가 ". 같은 정신으로 값은 " 무언가를 저장하는 이진 트리"여야하므로 "이진 트리"유형이 될 수 없습니다 .Maybe Bool

예를 Bool들어에 인수로 전달하면 s 를 저장하는 이진 트리 인 BTree유형을 반환합니다 . 유형 변수의 모든 발생을 교체 유형과를 , 그리고 사실 어떻게 자신을 위해 볼 수 있습니다.BTree BoolBoolaBool

원하는 경우 종류BTree 와 함께 함수로 볼 수 있습니다.

BTree :: * -> *

종류는 유형과 다소 유사합니다. *는 콘크리트 유형을 나타내므로 BTree콘크리트 유형에서 콘크리트 유형까지입니다.

마무리

여기로 잠시 뒤로 물러나서 유사점에 주목하십시오.

  • 데이터 생성자는 0 이상 소요 "기능"입니다 과 새 값을 당신을 다시 제공합니다.

  • 타입 생성자는 0 이상 소요 "기능"입니다 유형 및 새로운 유형의 당신을 다시 제공합니다.

매개 변수가있는 데이터 생성자는 값에 약간의 변화를 원할 경우 멋집니다. 이러한 변수를 매개 변수에 넣고 값을 생성하는 사람이 어떤 인수를 넣을지 결정하게합니다. 같은 의미에서 매개 변수가있는 유형 생성자는 멋집니다. 우리 유형의 약간의 변형을 원한다면! 우리는 이러한 변형을 매개 변수로 입력하고 유형을 생성하는 사람이 어떤 인수를 넣을지 결정하도록합니다.

사례 연구

여기에서 홈 스트레치로 Maybe a유형을 고려할 수 있습니다 . 그 정의는

data Maybe a = Nothing
             | Just a

다음 Maybe은 구체적인 유형을 반환하는 유형 생성자입니다. Just값을 반환하는 데이터 생성자입니다. Nothing값을 포함하는 데이터 생성자입니다. 우리의 유형을 보면 Just, 우리는 볼

Just :: a -> Maybe a

즉, Just유형 값을 취하고 유형 값을 a반환합니다 Maybe a. 우리의 종류를 보면 Maybe, 우리는 볼

Maybe :: * -> *

즉, Maybe구체적인 유형을 취하고 구체적인 유형을 반환합니다.

다시 한번! 구체적인 유형과 유형 생성자 함수의 차이점. Maybes 목록을 만들 수 없습니다. 실행하려고하면

[] :: [Maybe]

오류가 발생합니다. 그러나 Maybe Int, 또는 의 목록을 만들 수 있습니다 Maybe a. Maybe유형 생성자 함수 이기 때문 입니다. 그러나 목록에는 구체적인 유형의 값이 포함되어야합니다. Maybe Int그리고 Maybe a구체적인 유형은 (또는 통화 구체적인 형태를 돌려 생성자 함수를 입력합니다.)


2
첫 번째 예에서 RED GREEN과 BLUE는 모두 인수를 사용하지 않는 생성자입니다.
OllieB 2013-08-13

3
에 있음을 주장 data Colour = Red | Green | Blue"우리는 전혀 생성자가없는"은 명백히 잘못이다. 유형 생성자와 데이터 생성자는 인수를 취할 필요가 없습니다. 예를 들어 haskell.org/haskellwiki/Constructor 에서 data Tree a = Tip | Node a (Tree a) (Tree a)"두 개의 데이터 생성자, 팁과 노드가 있습니다"를 지적합니다.
Frerich Raabe 2013-08-13

1
@CMCDragonkai 당신은 절대적으로 맞습니다! 종류는 "유형의 유형"입니다. 유형과 값의 개념을 결합하는 일반적인 접근 방식을 종속 입력 이라고 합니다. Idris 는 Haskell에서 영감을받은 종속 유형 언어입니다. 올바른 GHC 확장을 사용하면 Haskell에서 종속 입력에 다소 가까워 질 수 있습니다. (어떤 사람들은 "하스켈 조사에 의존 유형을하지 않고 알아내는 얼마나 가까운 우리가 얻을 수 의존 유형에 관한 것입니다."고 농담 한)
kqr

1
@CMCDragonkai 실제로 표준 Haskell에서 빈 데이터 선언을 가질 수 없습니다. 하지만 -XEmptyDataDecls그렇게 할 수 있는 GHC 확장 ( )이 있습니다. 말했듯이 해당 유형의 값이 f :: Int -> Z없기 때문에 함수 는 예를 들어 절대 반환하지 않을 수 있습니다 (무엇을 반환할까요?). 그러나 유형을 원하지만 값에 대해서는 신경 쓰지 않을 때 유용 할 수 있습니다 .
kqr

1
정말 불가능한가요? 방금 GHC에서 시도했는데 오류없이 실행되었습니다. GHC 확장을로드 할 필요가 없었고 바닐라 GHC 만로드했습니다. 그런 다음 글을 쓸 수 있었고 :k Z별을주었습니다.
CMCDragonkai

42

Haskell에는 대수 데이터 유형 이 있으며 다른 언어에는 거의 없습니다. 이것은 아마도 당신을 혼란스럽게하는 것입니다.

다른 언어에서는 일반적으로 "레코드", "구조체"또는 이와 유사한 것을 만들 수 있습니다. 여기에는 다양한 유형의 데이터를 보유하는 여러 이름의 필드가 있습니다. 때로는 고정 가능한 값의 (작은) 세트 (예 : Red, GreenBlue) 가있는 "열거"를 만들 수도 있습니다 .

Haskell에서는이 두 가지를 동시에 결합 할 수 있습니다 . 이상하지만 사실입니다!

"대수"라고하는 이유는 무엇입니까? 음, 괴짜들은 "합계 유형"과 "제품 유형"에 대해 이야기합니다. 예를 들면 :

data Eg1 = One Int | Two String

Eg1값은 기본적으로 하나 의 정수 또는 문자열입니다. 따라서 가능한 모든 Eg1값의 집합은 가능한 모든 정수 값과 가능한 모든 문자열 값 집합의 "합계"입니다. 따라서 괴짜 Eg1는 "합계 유형"이라고합니다. 반면에 :

data Eg2 = Pair Int String

모든 Eg2값은 구성 모두 정수와 문자열. 따라서 가능한 모든 Eg2값의 집합은 모든 정수 집합과 모든 문자열 집합의 데카르트 곱입니다. 두 세트는 함께 "곱해 지므로" "제품 유형"입니다.

Haskell의 대수 유형은 제품 유형의 합계 유형입니다 . 생성자에게 제품 유형을 만들기 위해 여러 필드를 제공하고 제품의 합계를 만들기 위해 여러 생성자가 있습니다.

이것이 유용한 이유의 예로, 데이터를 XML 또는 JSON으로 출력하고 구성 레코드를 사용하는 것이 있다고 가정합니다. 그러나 분명히 XML과 JSON의 구성 설정은 완전히 다릅니다. 따라서 다음과 같이 할 수 있습니다 .

data Config = XML_Config {...} | JSON_Config {...}

(당연히 적절한 필드가 있습니다.) 일반적인 프로그래밍 언어로는 이와 같은 작업을 할 수 없습니다. 이것이 대부분의 사람들이 익숙하지 않은 이유입니다.


4
큰! 한 가지, "그들은 거의 모든 언어로 구성 될 수 있습니다" 라고 Wikipedia는 말합니다 . :) 예를 들어 C / ++에서 union태그 규율이있는 s입니다. :)
Will Ness

5
그래,하지만 내가 말할 때마다 union, 사람들은 같은 날 봐 "도대체 지금까지 사용하고있는 것을 ?" ;-)
MathematicalOrchid

1
union내 C 경력에서 많이 사용되는 것을 보았습니다 . 그렇지 않기 때문에 불필요하게 들리지 마십시오.
truthadjustr

26

가장 간단한 경우부터 시작하십시오.

data Color = Blue | Green | Red

이 정의하는 "유형 생성자" Color인수를 사용하지 않습니다 - 그것은 세 가지 "데이터 생성자"를 가지고 Blue, Green하고 Red. 데이터 생성자는 인수를받지 않습니다. 이것은 세 가지 유형이 있음을 의미합니다 Color: Blue, GreenRed.

데이터 생성자는 일종의 값을 생성해야 할 때 사용됩니다. 처럼:

myFavoriteColor :: Color
myFavoriteColor = Green

데이터 생성자를 myFavoriteColor사용하여 값 을 생성하며 Green데이터 생성자 가 생성 한 값 myFavoriteColor의 유형 Color이므로 유형이됩니다.

유형 생성자는 일종의 유형 을 만들어야 할 때 사용됩니다 . 일반적으로 서명을 작성할 때 발생합니다.

isFavoriteColor :: Color -> Bool

이 경우 Color유형 생성자를 호출합니다 (인수를 사용하지 않음).

아직도 나와 함꼐?

이제 빨강 / 녹색 / 파랑 값을 만들고 싶을뿐만 아니라 "강도"도 지정하고 싶다고 상상해보십시오. 0에서 256 사이의 값처럼 각 데이터 생성자에 인수를 추가하여이를 수행 할 수 있으므로 다음과 같이 끝납니다.

data Color = Blue Int | Green Int | Red Int

이제 세 데이터 생성자 각각은 유형의 인수를 사용합니다 Int. 형식 생성자 ( Color)는 여전히 인수를받지 않습니다. 그래서 제가 가장 좋아하는 색은 짙은 녹색입니다.

    myFavoriteColor :: Color
    myFavoriteColor = Green 50

그리고 다시 Green데이터 생성자를 호출하고 type 값을 얻습니다 Color.

사람들이 색의 강도를 표현하는 방법을 지시하고 싶지 않다고 상상해보십시오. 일부는 우리가 방금했던 것과 같은 숫자 값을 원할 수 있습니다. "밝음"또는 "그다지 밝지 않음"을 나타내는 부울만으로도 괜찮을 수 있습니다. 이에 대한 해결책 Int은 데이터 생성자에서 하드 코딩하지 않고 형식 변수를 사용하는 것입니다.

data Color a = Blue a | Green a | Red a

이제 유형 생성자는 하나의 인수 (방금 a! 라고 부르는 다른 유형)를 취하고 모든 데이터 생성자는 해당 유형의 하나의 인수 (값!)를 취합니다 a. 그래서 당신은 가질 수 있습니다

myFavoriteColor :: Color Bool
myFavoriteColor = Green False

또는

myFavoriteColor :: Color Int
myFavoriteColor = Green 50

Color데이터 생성자에 의해 반환 될 "유효"유형을 얻기 위해 인수 (다른 유형)를 사용 하여 유형 생성자를 호출하는 방법에 주목 하십시오. 이것은 한두 잔의 커피를 마시 며 읽고 싶은 종류 의 개념을 다룹니다 .

이제 우리는 데이터 생성자와 유형 생성자가 무엇인지, 데이터 생성자가 다른 값을 인수로 취하고 유형 생성자가 다른 유형을 인수로 취하는 방법을 알아 냈습니다. HTH.


나는 널 데이터 생성자에 대한 당신의 개념과 친구인지 잘 모르겠습니다. Haskell에서 상수에 대해 이야기하는 일반적인 방법이라는 것을 알고 있지만, 여러 번 잘못된 것으로 입증되지 않았습니까?
kqr

@kqr : 데이터 생성자는 널일 수 있지만 더 이상 함수가 아닙니다. 함수는 인수를 받아 값을 산출하는 것, 즉 ->서명에있는 것입니다.
Frerich Raabe

값이 여러 유형을 가리킬 수 있습니까? 아니면 모든 값이 하나의 유형에만 연관되어 있습니까?
CMCDragonkai

1
@jrg 겹치는 부분이 있지만 유형 생성자 때문이 아니라 유형 변수 (예 : ain data Color a = Red a. a임의 유형의 자리 표시 자입니다. 입력 등의 기능이 비록 사용자는 일반 함수에 동일한 수 (a, b) -> a(유형의 두 값 터플을 소요 a하고 b) 상기 제 1 값을 산출한다. 튜플 요소의 유형을 지정하지 않는다는 점에서 "일반적인"함수입니다. 함수가 첫 번째 튜플 요소와 동일한 유형의 값을 산출하도록 지정합니다.
Frerich Raabe 2015 년

1
+1 Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.이것은 매우 유용합니다.
Jonas

5

다른 사람들이 지적했듯이 다형성은 여기서 그다지 유용하지 않습니다. 이미 익숙한 또 다른 예를 살펴 보겠습니다.

Maybe a = Just a | Nothing

이 유형에는 두 개의 데이터 생성자가 있습니다. Nothing다소 지루하고 유용한 데이터가 포함되어 있지 않습니다. 반면에 어떤 유형이든 가질 수 Just있는 값을 포함합니다 . 이 유형을 사용하는 함수를 작성해 보겠습니다. 예를 들어 목록 의 머리글을 가져 오는 경우 (있는 경우 오류를 던지는 것보다 더 유용하다는 데 동의하시기 바랍니다) :aaInt

maybeHead :: [Int] -> Maybe Int
maybeHead [] = Nothing
maybeHead (x:_) = Just x

> maybeHead [1,2,3]    -- Just 1
> maybeHead []         -- None

따라서이 경우 aInt이지만 다른 유형에서도 작동합니다. 실제로 모든 유형의 목록에 대해 함수가 작동하도록 할 수 있습니다 (구현을 변경하지 않고도).

maybeHead :: [t] -> Maybe t
maybeHead [] = Nothing
maybeHead (x:_) = Just x

반면에 당신의 특정 유형의 동의 기능을 쓸 수 있습니다 Maybe예를,

doubleMaybe :: Maybe Int -> Maybe Int
doubleMaybe Just x = Just (2*x)
doubleMaybe Nothing= Nothing

간단히 말해서 다형성을 사용하면 다른 유형의 값으로 작업 할 수있는 유연성을 자신의 유형에 부여 할 수 있습니다.

귀하의 예 String에서 회사를 식별하기에 충분하지 않은 시점에서 결정할 수 있지만 자체 유형 Company(국가, 주소, 계정 등의 추가 데이터를 보유) 이 있어야합니다 . 의 첫 번째 구현은 첫 번째 값 대신 Car사용하도록 변경해야 합니다. 두 번째 구현은 괜찮습니다. 그대로 사용하면 이전처럼 작동합니다 (물론 회사 데이터에 액세스하는 기능은 변경해야 함).CompanyStringCar Company String Int


다른 데이터 선언의 데이터 컨텍스트에서 형식 생성자를 사용할 수 있습니까? 같은 거 data Color = Blue ; data Bright = Color? ghci에서 시도했는데 유형 생성자의 Color가 Bright 정의의 Color 데이터 생성자와 아무 관련이없는 것 같습니다. 2 개의 Color 생성자가 있는데 하나는 데이터이고 다른 하나는 유형입니다.
CMCDragonkai

@CMCDragonkai 나는 당신이 이것을 할 수 있다고 생각하지 않으며 당신이 이것으로 무엇을 성취하고 싶은지조차 확신하지 못합니다. data또는 newtype(예 data Bright = Bright Color)를 사용하여 기존 유형을 "래핑" 하거나 type동의어를 정의하기 위해 사용할 수 있습니다 (예 :) type Bright = Color.
Landei 2014

5

두 번째는 "다형성"이라는 개념을 가지고 있습니다.

a b c는 모든 유형 이 될 수 있습니다. 예를 들어, a을 할 수있다 [String], b할 수 있습니다 [Int]c수 있습니다 [Char].

첫 번째 유형은 고정되어 있지만 회사는 String, 모델은 a String, 연도는 Int입니다.

Car 예제는 다형성 사용의 중요성을 보여주지 않을 수 있습니다. 그러나 데이터가 목록 유형이라고 상상해보십시오. 목록에는 String, Char, Int ...다음이 포함될 수 있습니다 . 이러한 상황에서는 데이터를 정의하는 두 번째 방법이 필요합니다.

세 번째 방법에 관해서는 이전 유형에 맞을 필요가 없다고 생각합니다. Haskell에서 데이터를 정의하는 또 다른 방법입니다.

이것은 초보자로서 저의 겸손한 의견입니다.

Btw : 당신은 당신의 두뇌를 잘 훈련하고 이것에 대해 편안하게 느끼는지 확인하십시오. 나중에 Monad를 이해하는 열쇠입니다.


1

유형 에 관한 것입니다 . 첫 번째 경우에는 유형 String(회사 및 모델)과 Int연도를 설정합니다. 두 번째 경우에는 더 일반적입니다. a, bc상기 제 1 실시 예에 매우 동일한 종류 일 수도 또는 완전히 다른 무언가. 예를 들어, 정수 대신 문자열로 연도를 제공하는 것이 유용 할 수 있습니다. 원하는 경우 Color유형을 사용할 수도 있습니다 .

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