IT박스

Haskell에“데이터”와“새로운 유형”이있는 이유는 무엇입니까?

itboxs 2020. 6. 17. 19:21
반응형

Haskell에“데이터”와“새로운 유형”이있는 이유는 무엇입니까? [복제]


이 질문에는 이미 답변이 있습니다.

것 같다 newtype정의는 단지입니다 data순종 때문에 이러한 제한 런타임 시스템이 처리 할 수있는 몇 가지 제한 사항 (예를 들어, 하나의 생성자), 그리고 그 정의 newtype를보다 효율적들. 정의되지 않은 값에 대한 패턴 일치 처리는 약간 다릅니다.

그러나 Haskell이 data정의 만 알고 있다고 가정 해 봅시다 newtype. 컴파일러가 주어진 데이터 정의가 이러한 제한을 따르는 지 여부를 스스로 알아 내서 자동으로 더 효율적으로 처리 할 수 ​​없었습니까?

나는 무언가를 놓치고 있다고 확신합니다. 이것에 대한 더 깊은 이유가 있어야합니다.


newtype단일 생성자와 단일 생성자 모두 단일 data값 생성자를 도입하지만, 생성자가 도입 한 값 생성자 newtype는 엄격하고 도입 한 값 생성자 data는 게으 릅니다. 당신이 가지고 있다면

data D = D Int
newtype N = N Int

그러면 평가할 때와 N undefined동일 undefined하며 오류가 발생합니다. 그러나 D undefined입니다 하지 에 해당 undefined하고,이 같은 당신이 슬쩍 내부로하지 않는 평가 될 수있다.

컴파일러가 직접 처리 할 수 ​​없습니다.

아니요, 실제로는 아닙니다. 프로그래머가 생성자가 엄격한 지 아니면 게으른지를 결정할 수있는 경우입니다. 생성자를 엄격하거나 게으르게 만드는시기와 방법을 이해하려면 내가하는 것보다 게으른 평가에 대해 훨씬 더 잘 이해해야합니다. 보고서의 아이디어, 즉 newtype호환되지 않는 여러 종류의 측정과 같은 기존 유형의 이름을 바꿀 수 있습니다.

newtype Feet = Feet Double
newtype Cm   = Cm   Double

둘 다 Double런타임 과 똑같이 동작 하지만 컴파일러는 혼동하지 않도록 약속합니다.


에 따르면 당신에게 하스켈 알아보기 :

data 키워드 대신 newtype 키워드가 사용됩니다. 왜 그럴까요? 우선 새로운 유형이 더 빠릅니다. data 키워드를 사용하여 유형을 줄 바꿈하면 프로그램이 실행될 때 모든 줄 바꿈 및 줄 바꿈에 약간의 오버 헤드가 있습니다. 그러나 newtype을 사용하는 경우 Haskell은이를 사용하여 기존 유형을 새로운 유형 (따라서 이름)으로 랩핑하기 위해 사용하고 있다는 것을 알고 있습니다. 왜냐하면 유형은 내부적으로 동일하지만 다른 유형을 원하기 때문입니다. 이를 염두에두고 Haskell은 어떤 값이 어떤 유형인지 확인하면 래핑 및 언 래핑을 제거 할 수 있습니다.

그렇다면 왜 항상 데이터 대신 newtype을 사용하지 않습니까? newtype 키워드를 사용하여 기존 유형에서 새 유형을 만들 때 하나의 값 생성자 만 가질 수 있고 해당 값 생성자에는 하나의 필드 만있을 수 있습니다. 그러나 데이터를 사용하면 여러 값 생성자가있는 데이터 유형을 만들 수 있으며 각 생성자는 0 개 이상의 필드를 가질 수 있습니다.

data Profession = Fighter | Archer | Accountant  

data Race = Human | Elf | Orc | Goblin  

data PlayerCharacter = PlayerCharacter Race Profession 

newtype을 사용하는 경우 하나의 필드를 가진 하나의 생성자로 제한됩니다.

이제 다음 유형을 고려하십시오.

data CoolBool = CoolBool { getCoolBool :: Bool } 

data 키워드로 정의 된 방대한 대수 데이터 유형입니다. 하나의 값 생성자가 있으며 유형은 Bool 인 필드가 하나입니다. CoolBool에서 패턴이 일치하는 함수를 만들고 CoolBool 내부의 Bool이 True인지 False인지에 관계없이 "hello"값을 반환합니다.

helloMe :: CoolBool -> String  
helloMe (CoolBool _) = "hello"  

이 함수를 일반 CoolBool에 적용하는 대신 커브 볼을 던져 정의되지 않은 곳에 적용 해 봅시다!

ghci> helloMe undefined  
"*** Exception: Prelude.undefined  

이케! 예외! 이제 왜이 예외가 발생 했습니까? data 키워드로 정의 된 유형에는 여러 값 생성자가있을 수 있습니다 (CoolBool에는 하나만 있음). 따라서 함수에 주어진 값이 (CoolBool _) 패턴을 따르는 지 확인하기 위해 Haskell은 값을 만들 때 어떤 값 생성자가 사용되었는지 확인할 정도로 충분히 값을 평가해야합니다. 그리고 정의되지 않은 값을 평가하려고 할 때 약간이라도 예외가 발생합니다.

CoolBool에 data 키워드를 사용하는 대신 newtype을 사용해 봅시다.

newtype CoolBool = CoolBool { getCoolBool :: Bool }   

newtype 또는 data를 사용하여 유형을 정의하는 경우 패턴 일치 구문이 동일하므로 helloMe 함수를 변경할 필요가 없습니다. 여기에서 똑같은 일을하고 helloMe를 정의되지 않은 값에 적용하십시오.

ghci> helloMe undefined  
"hello"

효과가 있었다! 흠, 왜 그렇습니까? 우리가 말했듯이, 우리가 newtype을 사용할 때, Haskell은 원래 값과 같은 방식으로 새로운 유형의 값을 내부적으로 나타낼 수 있습니다. 주위에 다른 상자를 추가 할 필요가 없으며 다른 유형의 값을 알고 있어야합니다. Haskell은 newtype 키워드로 만든 유형에는 생성자가 하나만있을 수 있다는 것을 알고 있기 때문에 newtype 유형은 하나만 가질 수 있으므로 (CoolBool _) 패턴을 준수하는지 확인하기 위해 함수에 전달 된 값을 평가할 필요가 없습니다. 가능한 값 생성자와 하나의 필드!

This difference in behavior may seem trivial, but it's actually pretty important because it helps us realize that even though types defined with data and newtype behave similarly from the programmer's point of view because they both have value constructors and fields, they are actually two different mechanisms. Whereas data can be used to make your own types from scratch, newtype is for making a completely new type out of an existing type. Pattern matching on newtype values isn't like taking something out of a box (like it is with data), it's more about making a direct conversion from one type to another.

Here's another source. According to this Newtype article:

A newtype declaration creates a new type in much the same way as data. The syntax and usage of newtypes is virtually identical to that of data declarations - in fact, you can replace the newtype keyword with data and it'll still compile, indeed there's even a good chance your program will still work. The converse is not true, however - data can only be replaced with newtype if the type has exactly one constructor with exactly one field inside it.

Some Examples:

newtype Fd = Fd CInt
-- data Fd = Fd CInt would also be valid

-- newtypes can have deriving clauses just like normal types
newtype Identity a = Identity a
  deriving (Eq, Ord, Read, Show)

-- record syntax is still allowed, but only for one field
newtype State s a = State { runState :: s -> (s, a) }

-- this is *not* allowed:
-- newtype Pair a b = Pair { pairFst :: a, pairSnd :: b }
-- but this is:
data Pair a b = Pair { pairFst :: a, pairSnd :: b }
-- and so is this:
newtype Pair' a b = Pair' (a, b)

Sounds pretty limited! So why does anyone use newtype?

The short version The restriction to one constructor with one field means that the new type and the type of the field are in direct correspondence:

State :: (s -> (a, s)) -> State s a
runState :: State s a -> (s -> (a, s))

or in mathematical terms they are isomorphic. This means that after the type is checked at compile time, at run time the two types can be treated essentially the same, without the overhead or indirection normally associated with a data constructor. So if you want to declare different type class instances for a particular type, or want to make a type abstract, you can wrap it in a newtype and it'll be considered distinct to the type-checker, but identical at runtime. You can then use all sorts of deep trickery like phantom or recursive types without worrying about GHC shuffling buckets of bytes for no reason.

See the article for the messy bits...


Simple version for folks obsessed with bullet lists (failed to find one, so have to write it by myself):

data - creates new algebraic type with value constructors

  • Can have several value constructors
  • Value constructors are lazy
  • Values can have several fields
  • Affects both compilation and runtime, have runtime overhead
  • Created type is a distinct new type
  • Can have its own type class instances
  • When pattern matching against value constructors, WILL be evaluated at least to weak head normal form (WHNF) *
  • Used to create new data type (example: Address { zip :: String, street :: String } )

newtype - creates new “decorating” type with value constructor

  • Can have only one value constructor
  • Value constructor is strict
  • Value can have only one field
  • Affects only compilation, no runtime overhead
  • Created type is a distinct new type
  • Can have its own type class instances
  • When pattern matching against value constructor, CAN be not evaluated at all *
  • Used to create higher level concept based on existing type with distinct set of supported operations or that is not interchangeable with original type (example: Meter, Cm, Feet is Double)

type - creates an alternative name (synonym) for a type (like typedef in C)

  • No value constructors
  • No fields
  • Affects only compilation, no runtime overhead
  • No new type is created (only a new name for existing type)
  • Can NOT have its own type class instances
  • When pattern matching against data constructor, behaves the same as original type
  • Used to create higher level concept based on existing type with the same set of supported operations (example: String is [Char])

[*] On pattern matching laziness:

data DataBox a = DataBox Int
newtype NewtypeBox a = NewtypeBox Int

dataMatcher :: DataBox -> String
dataMatcher (DataBox _) = "data"

newtypeMatcher :: NewtypeBox -> String 
newtypeMatcher (NewtypeBox _) = "newtype"

ghci> dataMatcher undefined
"*** Exception: Prelude.undefined

ghci> newtypeMatcher undefined
“newtype"

Off the top of my head; data declarations use lazy evaluation in access and storage of their "members", whereas newtype does not. Newtype also strips away all previous type instances from its components, effectively hiding its implementation; whereas data leaves the implementation open.

I tend to use newtype's when avoiding boilerplate code in complex data types where I don't necessarily need access to the internals when using them. This speeds up both compilation and execution, and reduces code complexity where the new type is used.

When first reading about this I found this chapter of a Gentle Introduction to Haskell rather intuitive.

참고URL : https://stackoverflow.com/questions/2649305/why-is-there-data-and-newtype-in-haskell

반응형