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
빌드하는 데 다양한 매개 변수를 사용할 수있는 것입니까? 그렇다면 두 번째 예는 어떻게 맞습니까?
본질적으로 위의 세 가지 코드 예제 / 구성을 통합하는 설명을 찾고 있습니다.
답변
A의 data
선언하는 형식의 생성자는 등호 기호의 왼쪽에있는 것입니다. 데이터 생성자 (들)은 등호 (=)의 오른쪽에있는 것들입니다. 형식이 예상되는 곳에 형식 생성자를 사용하고 값이 예상되는 곳에 데이터 생성자를 사용합니다.
데이터 생성자
간단하게하기 위해 색상을 나타내는 유형의 예부터 시작할 수 있습니다.
data Colour = Red | Green | Blue
여기에는 세 개의 데이터 생성자가 있습니다. Colour
유형이고 Green
유형 값을 포함하는 생성자입니다 Colour
. 비슷하게,Red
및 Blue
은 유형 값을 생성하는 두 생성자입니다 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
데이터 생성자를 적용하여. 데이터 생성자는 변수와 같은 값을 포함하거나 다른 값을 인수로 사용하여 새 값을 만듭니다 . 이전 프로그래밍을 해본 적이 있다면이 개념이별로 이상하지 않을 것입니다.
중지
String
s 를 저장하기 위해 이진 트리를 구성 하려면 다음과 같은 작업을 수행하는 것을 상상할 수 있습니다.
data SBTree = Leaf String
| Branch String SBTree SBTree
여기에서 볼 수있는 것은 SBTree
두 개의 데이터 생성자를 포함 하는 유형 입니다. 즉, 값을 구성하는 두 개의 함수 (즉 Leaf
및 Branch
)가 있습니다.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 Bool
Bool
a
Bool
원하는 경우 종류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
구체적인 유형을 취하고 구체적인 유형을 반환합니다.
다시 한번! 구체적인 유형과 유형 생성자 함수의 차이점. Maybe
s 목록을 만들 수 없습니다. 실행하려고하면
[] :: [Maybe]
오류가 발생합니다. 그러나 Maybe Int
, 또는 의 목록을 만들 수 있습니다 Maybe a
. Maybe
유형 생성자 함수 이기 때문 입니다. 그러나 목록에는 구체적인 유형의 값이 포함되어야합니다. Maybe Int
그리고 Maybe a
구체적인 유형은 (또는 통화 구체적인 형태를 돌려 생성자 함수를 입력합니다.)
답변
Haskell에는 대수 데이터 유형 이 있으며 다른 언어에는 거의 없습니다. 이것은 아마도 당신을 혼란스럽게하는 것입니다.
다른 언어에서는 일반적으로 “레코드”, “구조체”또는 이와 유사한 것을 만들 수 있습니다. 여기에는 다양한 유형의 데이터를 보유하는 여러 이름의 필드가 있습니다. 때로는 고정 가능한 값의 (작은) 세트 (예 : Red
, Green
및 Blue
) 가있는 “열거”를 만들 수도 있습니다 .
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 {...}
(당연히 적절한 필드가 있습니다.) 일반적인 프로그래밍 언어로는 이와 같은 작업을 할 수 없습니다. 이것이 대부분의 사람들이 익숙하지 않은 이유입니다.
답변
가장 간단한 경우부터 시작하십시오.
data Color = Blue | Green | Red
이 정의하는 “유형 생성자” Color
인수를 사용하지 않습니다 – 그것은 세 가지 “데이터 생성자”를 가지고 Blue
, Green
하고 Red
. 데이터 생성자는 인수를받지 않습니다. 이것은 세 가지 유형이 있음을 의미합니다 Color
: Blue
, Green
및 Red
.
데이터 생성자는 일종의 값을 생성해야 할 때 사용됩니다. 처럼:
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.
답변
다른 사람들이 지적했듯이 다형성은 여기서 그다지 유용하지 않습니다. 이미 익숙한 또 다른 예를 살펴 보겠습니다.
Maybe a = Just a | Nothing
이 유형에는 두 개의 데이터 생성자가 있습니다. Nothing
다소 지루하고 유용한 데이터가 포함되어 있지 않습니다. 반면에 어떤 유형이든 가질 수 Just
있는 값을 포함합니다 . 이 유형을 사용하는 함수를 작성해 보겠습니다. 예를 들어 목록 의 머리글을 가져 오는 경우 (있는 경우 오류를 던지는 것보다 더 유용하다는 데 동의하시기 바랍니다) :a
a
Int
maybeHead :: [Int] -> Maybe Int
maybeHead [] = Nothing
maybeHead (x:_) = Just x
> maybeHead [1,2,3] -- Just 1
> maybeHead [] -- None
따라서이 경우 a
는 Int
이지만 다른 유형에서도 작동합니다. 실제로 모든 유형의 목록에 대해 함수가 작동하도록 할 수 있습니다 (구현을 변경하지 않고도).
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
사용하도록 변경해야 합니다. 두 번째 구현은 괜찮습니다. 그대로 사용하면 이전처럼 작동합니다 (물론 회사 데이터에 액세스하는 기능은 변경해야 함).Company
String
Car Company String Int
답변
두 번째는 “다형성”이라는 개념을 가지고 있습니다.
a b c
는 모든 유형 이 될 수 있습니다. 예를 들어, a
을 할 수있다 [String]
, b
할 수 있습니다 [Int]
및 c
수 있습니다 [Char]
.
첫 번째 유형은 고정되어 있지만 회사는 String
, 모델은 a String
, 연도는 Int
입니다.
Car 예제는 다형성 사용의 중요성을 보여주지 않을 수 있습니다. 그러나 데이터가 목록 유형이라고 상상해보십시오. 목록에는 String, Char, Int ...
다음이 포함될 수 있습니다 . 이러한 상황에서는 데이터를 정의하는 두 번째 방법이 필요합니다.
세 번째 방법에 관해서는 이전 유형에 맞을 필요가 없다고 생각합니다. Haskell에서 데이터를 정의하는 또 다른 방법입니다.
이것은 초보자로서 저의 겸손한 의견입니다.
Btw : 당신은 당신의 두뇌를 잘 훈련하고 이것에 대해 편안하게 느끼는지 확인하십시오. 나중에 Monad를 이해하는 열쇠입니다.
답변
유형 에 관한 것입니다 . 첫 번째 경우에는 유형 String
(회사 및 모델)과 Int
연도를 설정합니다. 두 번째 경우에는 더 일반적입니다. a
, b
및 c
상기 제 1 실시 예에 매우 동일한 종류 일 수도 또는 완전히 다른 무언가. 예를 들어, 정수 대신 문자열로 연도를 제공하는 것이 유용 할 수 있습니다. 원하는 경우 Color
유형을 사용할 수도 있습니다 .