Почему в Haskell есть "data" и "newtype"? [дубликат]



этот вопрос уже есть ответ здесь:



кажется, что a newtype определение-это просто data определение, которое подчиняется некоторым ограничениям (например, только один конструктор), и что из-за этих ограничений система выполнения может справиться newtypes более эффективно. И обработка сопоставления шаблонов для неопределенных значений немного отличается.



но предположим Хаскелл бы только знал data определения, нет newtypes: не мог ли компилятор выяснить для себя, подчиняется ли данное определение данных этим ограничениям, и автоматически обрабатывать его более эффективно?



Я уверен, что я упускаю что-то, должна быть какая-то более глубокая причина для этого.

711   4  

4 ответов:

и 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 во время выполнения, но компилятор обещает не позволяют спутать их.

по данным узнать вы на Haskell:

вместо ключевого слова data используется ключевое слово newtype. Теперь почему это это? Ну, во-первых, newtype быстрее. Если вы используете ключевое слово data для оберните тип, есть некоторые накладные расходы на всю эту упаковку и разворачивание когда ваша программа выполняется. Но если вы используете newtype, Haskell знает что вы просто используете его, чтобы обернуть существующий тип в новый тип (отсюда и название), потому что вы хотите быть таким же внутренне, но есть другой тип. Имея это в виду, Хаскелл может избавиться от обертывание и разворачивание после того, как он решает, какое значение имеет какой тип.

так почему бы просто не использовать newtype все время вместо данных тогда? Что ж, при создании нового типа из существующего типа с помощью newtype ключевое слово, вы можете иметь только один конструктор Стоимость и стоимость конструктор может иметь только одно поле. Но с данными, вы можете сделать данные типы, которые имеют несколько значений конструкторы и каждый конструктор может есть ноль или более полей:

data Profession = Fighter | Archer | Accountant  

data Race = Human | Elf | Orc | Goblin  

data PlayerCharacter = PlayerCharacter Race Profession 

при использовании newtype вы ограничены только одним конструктором с одним поле.

Теперь рассмотрим следующий вид:

data CoolBool = CoolBool { getCoolBool :: Bool } 

это ваш обычный алгебраический тип данных, который был определен с помощью ключевое слово Data. Он имеет один конструктор значений, который имеет одно поле чей тип-бул. Давайте сделаем функцию, которая шаблон матчи на CoolBool и возвращает значение "hello" независимо от того, является ли Bool внутри CoolBool было правдой или ложью:

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

вместо того, чтобы применять эту функцию к обычному CoolBool, давайте бросим его в curveball и применим его к undefined!

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

Yikes! Исключение! Почему же произошло это исключение? Определенные типы с помощью ключевого слова data можно иметь несколько конструкторов значений (даже хотя CoolBool только иметь одно.) Так что для того, чтобы увидеть, если значение задано чтобы наша функция соответствовала шаблону (CoolBool _), Haskell должен оцените значение достаточно, чтобы увидеть, какой конструктор значений был использован когда мы сделали значение. И когда мы пытаемся оценить неопределенное значение, даже немного, исключение.

вместо использования ключевого слова data для CoolBool, давайте попробуем использовать типа:

newtype CoolBool = CoolBool { getCoolBool :: Bool }   

мы не должны измени наш ад. функция, потому что синтаксис сопоставления шаблонов то же самое, если вы используете newtype или data для определения вашего типа. Давайте сделаем то же самое здесь и применить helloMe к неопределенному значению:

ghci> helloMe undefined  
"hello"

он работал! Хммм, почему это? Ну, как мы уже говорили, когда мы используем newtype, Haskell может внутренне представлять значения нового типа так же, как и исходные значения. Он не должен добавлять другой коробка вокруг них, она просто должна быть в курсе ценностей будучи различные формы. И потому, что Хаскелл знает, что типы сделаны с ключевое слово newtype может иметь только один конструктор, это не обязательно оцените значение, переданное функции, чтобы убедиться, что она соответствует шаблону (CoolBool _), потому что типы newtype могут только есть один конструктор возможных значений и одно поле!

эта разница в поведении может показаться тривиальной, но на самом деле это довольно важно, потому что это помогает нам понять, что даже если типы определенный с данными и типа ведут себя аналогично с точки зрения программиста поскольку они оба имеют конструкторы значений и поля, они являются на самом деле два разных механизма. В то время как данные могут быть использованы для создания ваши собственные типы с нуля, newtype предназначен для создания совершенно нового тип из существующего типа. Сопоставление значений типа не как взять что-то из коробки (как это с данными), это больше о прямом преобразовании из одного типа в другой другой.

вот еще один источник. Согласно эта статья Newtype:

объявление newtype создает новый тип во многом так же, как и данные. Синтаксис и использование новых типов практически идентичны синтаксису и использованию новых типов. объявления данных-фактически, вы можете заменить ключевое слово newtype на данные и он все равно будет компилироваться, действительно есть даже хороший шанс, что ваш программа все равно будет работать. Обратное не верно, однако-данные могут заменяется только на newtype, если тип имеет ровно один конструктор с ровно одним полем внутри него.

Примеры:

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)

звучит довольно ограничены! Так почему же кто-то использует newtype?

краткая версия ограничение на один конструктор с одним полем означает, что новый тип и тип поля находятся в прямой переписка:

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

или в математических терминах они изоморфны. Это означает, что после тип проверяется во время компиляции, во время выполнения эти два типа могут быть обрабатывается по существу то же самое, без накладных расходов или косвенности обычно связывается с конструктором данных. Так что если вы хотите объявить различные экземпляры класса типа для определенного типа, или хотите сделать абстрактный тип, вы можете обернуть его в newtype, и он будет рассмотрен отличный от средства проверки типов, но идентичный во время выполнения. Тогда вы можете используйте все виды глубокого обмана, как призрак или рекурсивные типы без беспокоиться о том, что GHC перетасовывает ведра байтов без причины.

посмотреть статьи для грязных бит...

простая версия для людей, одержимых списками пуль (не удалось найти один, так что придется написать его самостоятельно):

сведения - создает новый алгебраический тип с конструкторами значение

  • может иметь несколько конструкторов стоимостью
  • конструкторы значений ленивы
  • значения могут иметь несколько полей
  • влияет как на компиляцию, так и на время выполнения, имеет накладные расходы во время выполнения
  • созданный тип-это отдельный новый типа
  • может иметь свои собственные экземпляры класса типа
  • при сопоставлении шаблонов с конструкторами значений будет оцениваться, по крайней мере, слабая головная нормальная форма (WHNF) *
  • используется для создания нового типа данных (пример: адрес { zip :: String, street :: String } )

newtype - создает новый тип "украшения" с конструктором значений

  • может иметь только один конструктор значений
  • конструктор значений является строгим
  • значение может иметь только одно поле
  • влияет только на компиляцию, без накладных расходов во время выполнения
  • созданный тип-это отдельный новый тип
  • может иметь свои собственные экземпляры класса типа
  • при сопоставлении шаблонов с конструктором значений, могут быть не оценены вообще *
  • используется для создания концепции более высокого уровня на основе существующего типа с отдельным набором поддерживаемых операций или не является взаимозаменяемым с исходным типом (пример: метр, см, Ноги двойные)

тип - создает альтернативное имя (синоним) для типа (например, typedef в C)

  • нет конструкторов стоимостью
  • поля
  • влияет только на компиляцию, без накладных расходов во время выполнения
  • новый тип не создается (только новое имя для существующего типа)
  • не может иметь свой собственный тип экземпляров класса
  • при сопоставлении шаблонов с конструктором данных, ведет себя так же, как оригинал типа
  • используется для создания концепции более высокого уровня на основе существующего типа с тем же набором поддерживаемых операций (например: String is [Char])

[*] по шаблону лень:

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"

с верхней части моей головы; объявления данных используют ленивую оценку в доступе и хранении своих "членов", тогда как newtype этого не делает. Newtype также удаляет все предыдущие экземпляры типа из своих компонентов, эффективно скрывая его реализацию; в то время как данные оставляют реализацию открытой.

Я склонен использовать newtype, когда избегаю шаблонного кода в сложных типах данных, где мне не обязательно нужен доступ к внутренним устройствам при их использовании. Это ускоряет обе компиляции и выполнение, и уменьшает сложность кода, где используется новый тип.

при первом чтении об этом я нашел эта глава мягкого введения в Хаскелл довольно интуитивно.

Comments

    Ничего не найдено.