Programming

하스켈 타입 vs 데이터 생성자

procodes 2020. 7. 26. 13:25
반응형

하스켈 타입 vs 데이터 생성자


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이며 type 값을 포함하는 생성자입니다 Colour. 유사하게, Red그리고 Blue모두 생성자는 그 타입의 구조체 값 Colour. 우리는 그것을 spicing하는 것을 상상할 수 있었다!

data Colour = RGB Int Int Int

우리는 여전히 type 만 가지고 Colour있지만 RGB값은 아닙니다. 세 개의 Ints를 가져 와서 값을 반환 하는 함수입니다 ! RGB유형이 있습니다

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

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

이 경우 RGB세 가지 값에 적용 하면 색상 값이 나타납니다!

Prelude> RGB 12 92 27
#0c5c1b

우리는 한 값 구축 유형을 Colour데이터 생성자를 적용하여. 데이터 생성자는 변수처럼 값을 포함하거나 다른 값을 인수로 사용하여 새 값을 만듭니다 . 이전 프로그래밍을 수행했다면이 개념이 그리 이상해서는 안됩니다.

중지

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

data SBTree = Leaf String
            | Branch String SBTree SBTree

여기서 볼 수있는 것은 SBTree두 개의 데이터 생성자가 포함 된 유형 입니다. 즉, 유형의 값을 구성하는 두 가지 함수 (즉 Leaf, 및 Branch)가 SBTree있습니다. 이진 트리의 작동 방식에 익숙하지 않은 경우 여기에 전화하십시오. 이진 트리의 작동 방식을 실제로 알 필요는 없으며 String어떤 방식 으로든 저장 합니다.

또한 두 데이터 생성자 모두 String인수를 취합니다 . 이것이 트리에 저장할 문자열입니다.

그러나! 우리가 저장하기를 원한다면 Bool, 새로운 이진 트리를 만들어야합니다. 다음과 같이 보일 수 있습니다.

data BBTree = Leaf Bool
            | Branch Bool BBTree BBTree

타입 생성자

모두 SBTreeBBTree타입 생성자이다. 그러나 눈부신 문제가 있습니다. 그것들이 얼마나 비슷한 지 보십니까? 그것은 당신이 정말로 어딘가에 매개 변수를 원한다는 표시입니다.

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

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

이제 타입 생성자를 매개 변수 a타입 변수소개합니다 . 이 선언에서 BTree함수가되었습니다. 그것은 소요 유형을 인수로서 그것은 새로운 반환 유형을 .

It is important here to consider the difference between a concrete type (examples include Int, [Char] and Maybe Bool) which is a type that can be assigned to a value in your program, and a type constructor function which you need to feed a type to be able to be assigned to a value. A value can never be of type "list", because it needs to be a "list of something". In the same spirit, a value can never be of type "binary tree", because it needs to be a "binary tree storing something".

If we pass in, say, Bool as an argument to BTree, it returns the type BTree Bool, which is a binary tree that stores Bools. Replace every occurrence of the type variable a with the type Bool, and you can see for yourself how it's true.

If you want to, you can view BTree as a function with the kind

BTree :: * -> *

Kinds are somewhat like types – the * indicates a concrete type, so we say BTree is from a concrete type to a concrete type.

Wrapping up

Step back here a moment and take note of the similarities.

  • A data constructor is a "function" that takes 0 or more values and gives you back a new value.

  • A type constructor is a "function" that takes 0 or more types and gives you back a new type.

Data constructors with parameters are cool if we want slight variations in our values – we put those variations in parameters and let the guy who creates the value decide what arguments they are going to put in. In the same sense, type constructors with parameters are cool if we want slight variations in our types! We put those variations as parameters and let the guy who creates the type decide what arguments they are going to put in.

A case study

As the home stretch here, we can consider the Maybe a type. Its definition is

data Maybe a = Nothing
             | Just a

Here, Maybe is a type constructor that returns a concrete type. Just is a data constructor that returns a value. Nothing is a data constructor that contains a value. If we look at the type of Just, we see that

Just :: a -> Maybe a

In other words, Just takes a value of type a and returns a value of type Maybe a. If we look at the kind of Maybe, we see that

Maybe :: * -> *

In other words, Maybe takes a concrete type and returns a concrete type.

Once again! The difference between a concrete type and a type constructor function. You cannot create a list of Maybes - if you try to execute

[] :: [Maybe]

you'll get an error. You can however create a list of Maybe Int, or Maybe a. That's because Maybe is a type constructor function, but a list needs to contain values of a concrete type. Maybe Int and Maybe a are concrete types (or if you want, calls to type constructor functions that return concrete types.)


Haskell has algebraic data types, which very few other languages have. This is perhaps what's confusing you.

In other languages, you can usually make a "record", "struct" or similar, which has a bunch of named fields that hold various different types of data. You can also sometimes make an "enumeration", which has a (small) set of fixed possible values (e.g., your Red, Green and Blue).

In Haskell, you can combine both of these at the same time. Weird, but true!

Why is it called "algebraic"? Well, the nerds talk about "sum types" and "product types". For example:

data Eg1 = One Int | Two String

An Eg1 value is basically either an integer or a string. So the set of all possible Eg1 values is the "sum" of the set of all possible integer values and all possible string values. Thus, nerds refer to Eg1 as a "sum type". On the other hand:

data Eg2 = Pair Int String

Every Eg2 value consists of both an integer and a string. So the set of all possible Eg2 values is the Cartesian product of the set of all integers and the set of all strings. The two sets are "multiplied" together, so this is a "product type".

Haskell's algebraic types are sum types of product types. You give a constructor multiple fields to make a product type, and you have multiple constructors to make a sum (of products).

As an example of why that might be useful, suppose you have something that outputs data as either XML or JSON, and it takes a configuration record - but obviously, the configuration settings for XML and for JSON are totally different. So you might do something like this:

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

(With some suitable fields in there, obviously.) You can't do stuff like this in normal programming languages, which is why most people aren't used to it.


Start with the simplest case:

data Color = Blue | Green | Red

This defines a "type constructor" Color which takes no arguments - and it has three "data constructors", Blue, Green and Red. None of the data constructors takes any arguments. This means that there are three of type Color: Blue, Green and Red.

A data constructor is used when you need to create a value of some sort. Like:

myFavoriteColor :: Color
myFavoriteColor = Green

creates a value myFavoriteColor using the Green data constructor - and myFavoriteColor will be of type Color since that's the type of values produced by the data constructor.

A type constructor is used when you need to create a type of some sort. This is usually the case when writing signatures:

isFavoriteColor :: Color -> Bool

In this case, you are calling the Color type constructor (which takes no arguments).

Still with me?

Now, imagine you not only wanted to create red/green/blue values but you also wanted to specify an "intensity". Like, a value between 0 and 256. You could do that by adding an argument to each of the data constructors, so you end up with:

data Color = Blue Int | Green Int | Red Int

Now, each of the three data constructors takes an argument of type Int. The type constructor (Color) still doesn't take any arguments. So, my favorite color being a darkish green, I could write

    myFavoriteColor :: Color
    myFavoriteColor = Green 50

And again, it calls the Green data constructor and I get a value of type Color.

Imagine if you don't want to dictate how people express the intensity of a color. Some might want a numeric value like we just did. Others may be fine with just a boolean indicating "bright" or "not so bright". The solution to this is to not hardcode Int in the data constructors but rather use a type variable:

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

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. So you could have

myFavoriteColor :: Color Bool
myFavoriteColor = Green False

or

myFavoriteColor :: Color Int
myFavoriteColor = Green 50

Notice how we call the Color type constructor with an argument (another type) to get the "effective" type which will be returned by the data constructors. This touches the concept of kinds which you may want to read about over a cup of coffee or two.

Now we figured out what data constructors and type constructors are, and how data constructors can take other values as arguments and type constructors can take other types as arguments. HTH.


As others pointed out, polymorphism isn't that terrible useful here. Let's look at another example you're probably already familiar with:

Maybe a = Just a | Nothing

This type has two data constructors. Nothing is somewhat boring, it doesn't contain any useful data. On the other hand Just contains a value of a - whatever type a may have. Let's write a function which uses this type, e.g. getting the head of an Int list, if there is any (I hope you agree this is more useful than throwing an error):

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

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

So in this case a is an Int, but it would work as well for any other type. In fact you can make our function work for every type of list (even without changing the implementation):

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

On the other hand you can write functions which accept only a certain type of Maybe, e.g.

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

So long story short, with polymorphism you give your own type the flexibility to work with values of different other types.

In your example, you may decide at some point that String isn't sufficient to identify the company, but it needs to have its own type Company (which holds additional data like country, address, back accounts etc). Your first implementation of Car would need to change to use Company instead of String for its first value. Your second implementation is just fine, you use it as Car Company String Int and it would work as before (of course functions accessing company data need to be changed).


The second one has the notion of "polymorphism" in it.

The a b c can be of any type. For example, a can be a [String], b can be [Int] and c can be [Char].

While the first one's type is fixed: company is a String, model is a String and year is Int.

The Car example might not show the significance of using polymorphism. But imagine your data is of the list type. A list can contain String, Char, Int ... In those situations, you will need the second way of defining your data.

As to the third way I don't think it needs to fit into the previous type. It's just one other way of defining data in Haskell.

This is my humble opinion as a beginner myself.

Btw: Make sure that you train your brain well and feel comfortable to this. It is the key to understand Monad later.


It's about types: In the first case, your set the types String (for company and model) and Int for year. In the second case, your are more generic. a, b, and c may be the very same types as in the first example, or something completely different. E.g., it may be useful to give the year as string instead of integer. And if you want, you may even use your Color type.

참고URL : https://stackoverflow.com/questions/18204308/haskell-type-vs-data-constructor

반응형