Newtype adalah deklarasi tipe data khusus. Sehingga hanya berisi satu konstruktor dan bidang.
newtype Foo a = Bar a newtype Id = MkId Word

Pertanyaan Pemula yang Umum
Apa perbedaan dari data tipe data? data Foo a = Bar a data Id = MkId Word
Spesifisitas utama dari
tipe baru adalah bahwa ia terdiri dari bagian yang sama dengan satu-satunya bidang. Lebih tepatnya, ini berbeda dari yang asli di tingkat tipe, tetapi memiliki representasi yang sama dalam memori, dan dihitung secara ketat (tidak malas).
Singkatnya -
jenis baru lebih efektif karena presentasi mereka.
Ya, itu tidak ada artinya bagi saya ... Saya akan menggunakan dataTidak, well, pada akhirnya, Anda selalu dapat mengaktifkan bidang ekstensi
-funpack-strict- :) untuk
bidang ketat (tidak malas) atau tentukan secara langsung
data Id = MkId !Word
Namun, kekuatan
tipe baru tidak terbatas pada efisiensi komputasi. Mereka jauh lebih kuat!
3 peran tipe baru

Menyembunyikan Implementasi
module Data.Id (Id()) where newtype Id = MkId Word
newtype berbeda dari yang asli, hanya
Word secara internal.
Tapi kami menyembunyikan konstruktor
MkId di luar modul.
Implementasi implementasi
{-# LANGUAGE GeneralizedNewtypeDeriving #-} newtype Id = MkId Word deriving (Num, Eq)
Meskipun ini tidak dalam standar Haskell2010, dengan memperluas output tipe baru generik, Anda dapat secara otomatis menyimpulkan perilaku
tipe baru sama dengan perilaku bidang internal. Dalam kasus kami, perilaku
Eq Id dan
Num Id sama dengan
Eq Word dan
Num Word .
Jauh lebih banyak yang bisa dicapai melalui perluasan pemuliaan yang disempurnakan (
DerivingVia ), tetapi lebih banyak tentang itu nanti.
Implementasi pilihan
Terlepas dari konstruktornya sendiri, dalam beberapa kasus Anda dapat menggunakan representasi internal Anda.
Tantangan
Ada daftar bilangan bulat. Temukan jumlah maksimum dan total hanya dalam satu pass pada daftar.
Dan jangan gunakan
paket foldl and
folds .
Jawaban khas
Tentu saja,
lipat ! :)
foldr :: Foldable t => (a -> b -> b) -> b -> ta -> b
Dan, fungsi terakhir dijelaskan seperti ini:
aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = foldr (\el (m, s) -> (Just el `max` m, el + s)) (Nothing, 0)
Jika Anda melihat lebih dekat, Anda dapat melihat operasi serupa di kedua sisi:
Hanya el `max` m dan
el + s . Dalam kedua kasus, pemetaan dan operasi biner. Dan elemen yang kosong adalah
Nothing dan
0 .
Ya, ini adalah monoids!
Monoid dan Semigroup lebih terinciSemigroup adalah properti dari operasi biner asosiatif
x β (y β z) == (x β y) β z
Monoid adalah properti dari operasi asosiatif (mis., Semigroup)
x β (y β z) == (x β y) β z
yang memiliki elemen kosong yang tidak mengubah elemen apa pun di kanan atau di sebelah kiri
x β empty == x == empty β x
Maks dan
(+) keduanya asosiatif, keduanya memiliki elemen kosong -
Tidak ada dan
0 .
Dan kombinasi pemetaan monoids bersama dengan konvolusi
Lipat !
Lipat lebih detailIngat definisi lipat:
class Foldable t where foldMap :: (Monoid m) => (a -> m) -> ta -> m ...
Mari kita terapkan perilaku lipat ke
max dan
(+) . Kami dapat mengatur tidak lebih dari satu implementasi monoid
Word . Saatnya mengambil keuntungan dari penerapan pilihan jenis baru!
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
Perlu untuk membuat komentar.
Faktanya adalah bahwa untuk menjadi monoid untuk tipe data
Max , kita membutuhkan elemen minimal, yaitu, agar elemen kosong ada. Jadi, hanya
Max a yang terbatas
yang bisa menjadi monoid.
Secara teori benar maksimum elemen monoid newtype Max a = Max a instance Ord a => Semigroup (Max a) instance Bounded a => Monoid (Max a)
Jadi entah bagaimana kita harus mengubah tipe data kita sehingga elemen kosong muncul dan kita dapat menggunakan koagulasi.
Unsur konjugat
Mungkin mengubah semi-grup menjadi monoid!
Liberalisasi pembatasan dalam versi terbaru GHCKembali di GHC 8.2, diperlukan batasan tipe monoid
instance Monoid a => Monoid (Maybe a)
yang berarti kami membutuhkan jenis baru lainnya:
Dan itu sudah jauh lebih sederhana di GHC 8.4, di mana hanya diperlukan semigroup dalam pembatasan jenis, dan bahkan tidak perlu membuat jenis Opsi.
instance Semigroup a => Monoid (Maybe a)
Respon Lipat
Nah, sekarang perbarui kode menggunakan collapsibility dan panah.
Kami ingat bahwa (.) Hanya komposisi fungsional:
(.) :: (b -> c) -> (a -> b) -> a -> c f . g = \x -> f (gx)
Dan ingat bahwa
fmap adalah functor:
fmap :: Functor f => (a -> b) -> fa -> fb
dan implementasinya untuk
Maybe dijelaskan sedikit lebih tinggi.
Panah lebih detailPanah adalah properti dari beberapa fungsi yang memungkinkan Anda untuk bekerja dengannya dalam diagram blok.
Lebih detail dapat ditemukan di sini:
Panah: Antarmuka Umum ke KomputasiDalam kasus kami, kami menggunakan fungsi Panah
Yaitu
instance Arrow (->)
Kami akan menggunakan fungsi:
(***) :: Arrow a => abc -> ab' c' -> a (b, b') (c, c') (&&&) :: Arrow a => abc -> abc' -> ab (c, c')
Untuk kasus kami
abc == (->) bc == b -> c
Dan, karenanya, tanda tangan dari fungsi kami dikurangi menjadi:
(***) :: (b -> c) -> (b' -> c') -> ((b, b') -> (c, c')) (&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c'))
Atau dengan kata-kata yang cukup sederhana, fungsi
(***) menggabungkan dua fungsi dengan satu argumen (dan satu tipe output) ke dalam fungsi dengan kerja sepasang argumen pada input dan pada output, masing-masing, sepasang tipe output.
Fungsi
(&&&) adalah versi
singkat (***) , di mana tipe argumen input dari kedua fungsi adalah sama, dan pada input kita tidak memiliki sepasang argumen, tetapi satu argumen.
Total, fungsi pemersatu telah memperoleh formulir:
import Data.Semigroup import Data.Monoid import Control.Arrow aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = (fmap getMax *** getSum) . (foldMap (Just . Max &&& Sum))
Ternyata sangat singkat!
Namun, masih melelahkan untuk membungkus dan membalikkan data dari tipe bersarang!
Anda dapat menguranginya lebih jauh, dan konversi paksa bebas sumber daya akan membantu kami!
Konversi paksa yang aman dan bebas sumber daya dan mengetikkan peran
Ada fungsi dari paket
Unsafe.Coerce -
unsafeCoerce import Unsafe.Coerce(unsafeCoerce) unsafeCoerce :: a -> b
Fungsi secara paksa tidak aman mengubah tipe: dari
a ke
b .
Bahkan, fungsinya ajaib, ia memberitahu kompiler untuk mempertimbangkan data tipe
a sebagai tipe
b , tanpa mempertimbangkan konsekuensi dari langkah ini.
Ini dapat digunakan untuk mengonversi tipe bersarang, tetapi Anda harus sangat berhati-hati.
Pada 2014, ada revolusi dengan
tipe baru , yaitu konversi yang aman dan tanpa sumber daya!
import Data.Coerce(coerce) coerce :: Coercible ab => a -> b
Fitur ini telah membuka era baru dalam bekerja dengan
tipe baru .
Coercible Forced Converter bekerja dengan tipe yang memiliki struktur yang sama dalam memori. Itu terlihat seperti kelas tipe, tetapi pada kenyataannya GHC mengkonversi tipe pada waktu kompilasi dan tidak mungkin untuk mendefinisikan instance sendiri.
Fungsi
Data.Coerce.coerce memungkinkan Anda untuk mengkonversi tipe tanpa sumber daya, tetapi untuk ini kita perlu memiliki akses ke konstruktor tipe.
Sekarang sederhanakan fungsi kami:
import Data.Semigroup import Data.Monoid import Control.Arrow import Data.Coerce aggregate :: [Integer] -> (Maybe Integer, Integer) aggregate = coerce . (foldMap (Just . Max &&& Sum))
Kami menghindari rutinitas menarik tipe bersarang, kami melakukan ini tanpa menghabiskan sumber daya hanya dengan satu fungsi.
Peran Tipe Data Bersarang
Dengan fungsi
paksaan, kami dapat memaksa konversi dari semua tipe bersarang.
Tetapi apakah perlu menggunakan fitur ini secara luas?
Secara semantik, tidak masuk akal untuk mengonversi ke
Sorted a from
Sorted (Down a) .
Namun, Anda dapat mencoba:
ghci> let h = fromList2Sorted [1,2,3] :: Sorted Int ghci> let hDown = fromList2Sorted $ fmap Down [1,2,3] :: Sorted (Down Int) ghci> minView h Just (Down 1) ghci> minView (coerce h :: Sorted (Down Int)) Just (Down 1) ghci> minView hDown Just (Down 3)
Semuanya akan baik-baik saja, tetapi jawaban yang benar adalah
Just (Down 3) .
Yaitu, untuk memotong perilaku yang salah, ketik peran diperkenalkan.
{-# LANGUAGE RoleAnnotations #-} type role Sorted nominal
Mari kita coba sekarang:
ghci> minView (coerce h :: Sorted (Down Int)) error: Couldn't match type 'Int' with 'Down Int' arising from a use of 'coerce'
Secara signifikan lebih baik!
Total ada 3 peran (
tipe peran ):
- representasional - setara jika representasi yang sama
- nominal - harus memiliki tipe yang persis sama
- phantom - independen dari konten nyata. Setara dengan apa pun
Dalam kebanyakan kasus, kompiler cukup pintar untuk mengungkapkan peran tipe, tetapi dapat membantu.
Perilaku Deriving DerivingVia yang Dimurnikan
Berkat perluasan bahasa
DerivingVia, peran distribusi
tipe baru telah meningkat.
Dimulai dengan GHC 8.6, yang baru-baru ini dirilis, ekstensi baru ini telah muncul.
{-# LANGUAGE DerivingVia #-} newtype Id = MkId Word deriving (Semigroup, Monoid) via Max Word
Seperti yang Anda lihat, perilaku tipe disimpulkan secara otomatis karena penyempurnaan cara output.
DerivingVia dapat diterapkan untuk semua jenis yang mendukung
Coercible dan yang
paling penting - sepenuhnya tanpa konsumsi sumber daya!
Terlebih lagi,
DerivingVia dapat diterapkan tidak hanya untuk
tipe baru , tetapi juga untuk semua jenis isomorfik jika mereka mendukung
generik generik dan konversi paksa yang dipaksakan.
Kesimpulan
Jenis tipe
baru adalah kekuatan yang sangat menyederhanakan dan meningkatkan kode, menghilangkan rutin dan mengurangi konsumsi sumber daya.
Terjemahan asli :
The Great Power oftype (Hiromi Ishii)PS Saya pikir, setelah artikel ini, diterbitkan lebih dari setahun yang lalu [bukan oleh artikel saya], Keajaiban tipe baru di Haskell tentang Tipe baru akan menjadi sedikit lebih jelas!