Haskell Türü ve Veri Oluşturucu


124

Learnyouahaskell.com'dan Haskell'i öğreniyorum . Tür oluşturucuları ve veri oluşturucuları anlamakta sorun yaşıyorum. Örneğin, bunun arasındaki farkı gerçekten anlamıyorum:

data Car = Car { company :: String  
               , model :: String  
               , year :: Int  
               } deriving (Show) 

ve bu:

data Car a b c = Car { company :: a  
                     , model :: b  
                     , year :: c   
                     } deriving (Show)  

İlkinin, Carveri türünde veri oluşturmak için yalnızca bir yapıcı ( ) kullanmak olduğunu anlıyorum Car. İkincisini gerçekten anlamıyorum.

Ayrıca, veri türleri şu şekilde nasıl tanımlanır:

data Color = Blue | Green | Red

tüm bunlara uyuyor mu?

Anladığım kadarıyla, üçüncü örnek ( Color:) üç eyalette olabilir türüdür Blue, Greenya Red. Ancak bu, ilk iki örneği nasıl anladığımla çelişiyor: bu, tür Caryalnızca tek bir durumda Carolabilir mi, bu da oluşturmak için çeşitli parametreler alabilir? Öyleyse, ikinci örnek nasıl uyuyor?

Esasen, yukarıdaki üç kod örneğini / yapısını birleştiren bir açıklama arıyorum.


18
Araba örneğiniz biraz kafa karıştırıcı olabilir Car, çünkü hem bir tür oluşturucu (sol tarafında =) hem de bir veri oluşturucu (sağ tarafta). İlk örnekte, Cartür yapıcısı hiçbir argüman almaz, ikinci örnekte ise üç tane alır. Her iki örnekte de, Carveri yapıcısı üç argüman alır (ancak bu argümanların türleri bir durumda sabit, diğerinde parametreleştirilmiştir).
Simon Shine

ilki, Car :: String -> String -> Int -> Carveri türünde veri oluşturmak için yalnızca bir veri yapıcısı ( ) kullanmaktır Car. ikincisi, Car :: a -> b -> c -> Car a b cveri türünde veri oluşturmak için yalnızca bir veri yapıcısı ( ) kullanmaktır Car a b c.
Will Ness

Yanıtlar:


228

Bir databildirimde, bir tür kurucusu eşittir işaretinin sol tarafındaki şeydir. Veri yapıcı (lar) eşittir işareti sağ taraftaki şeylerdir. Bir türün beklendiği yerde tür oluşturucuları ve bir değerin beklendiği durumlarda veri oluşturucularını kullanırsınız.

Veri oluşturucular

İşleri basitleştirmek için, bir rengi temsil eden bir tür örneği ile başlayabiliriz.

data Colour = Red | Green | Blue

Burada üç veri oluşturucumuz var. Colourbir tür ve bir tür Greendeğeri içeren bir yapıcıdır Colour. Benzer şekilde Redve Blueher ikisi de tür değerleri oluşturan yapıcılardır Colour. Yine de onu baharatlandırmayı hayal edebiliriz!

data Colour = RGB Int Int Int

Halen sadece türe sahibiz Colour, ancak RGBbir değer değil - bu, üç İnç alan ve bir değer döndüren bir fonksiyondur ! RGBtürü var

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

RGBbazı değerleri bağımsız değişkenleri olarak alan ve daha sonra bunları yeni bir değer oluşturmak için kullanan bir veri yapıcısıdır . Herhangi bir nesne tabanlı programlama yaptıysanız, bunu anlamalısınız. OOP'de, kurucular bazı değerleri bağımsız değişken olarak alır ve yeni bir değer döndürür!

Bu durumda RGBüç değere uygularsak bir renk değeri elde ederiz!

Prelude> RGB 12 92 27
#0c5c1b

Biz bir değer inşa Çeşidi Colourveri kurucu uygulayarak. Bir veri yapıcısı, bir değişkenin yapacağı gibi bir değer içerir veya argümanı olarak başka değerleri alır ve yeni bir değer oluşturur . Daha önce programlama yaptıysanız, bu kavram size çok garip gelmemelidir.

perde arası

'Leri depolamak için bir ikili ağaç oluşturmak Stringistiyorsanız, şöyle bir şey yapmayı hayal edebilirsiniz:

data SBTree = Leaf String
            | Branch String SBTree SBTree

Burada gördüğümüz SBTree, iki veri yapıcısı içeren bir türdür . Başka bir deyişle, türün değerlerini oluşturacak iki işlev vardır (yani Leafve Branch) SBTree. İkili ağaçların nasıl çalıştığına aşina değilseniz, orada kalın. Aslında ikili ağaçların nasıl çalıştığını bilmenize gerek yok, sadece bu ağaçların bir şekilde depolandığını String.

Ayrıca her iki veri yapıcısının da bir Stringargüman aldığını görüyoruz - bu ağaçta saklayacakları String.

Fakat! Ya biz de depolayabilmek Boolistiyorsak, yeni bir ikili ağaç oluşturmalıyız. Şunun gibi görünebilir:

data BBTree = Leaf Bool
            | Branch Bool BBTree BBTree

Tip oluşturucular

Her ikisi de SBTreeve BBTreetür oluşturuculardır. Ama göze batan bir sorun var. Ne kadar benzer olduklarını görüyor musun? Bu, bir yerde gerçekten bir parametre istediğinizi gösteren bir işarettir.

Böylece bunu yapabiliriz:

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

Şimdi tür yapıcısına parametre olarak bir tür değişkeni a ekliyoruz. Bu beyanda BTreebir fonksiyon haline gelmiştir. Bir türü argüman olarak alır ve yeni bir tür döndürür .

Bir arasındaki farkı dikkate almak burada önemli olan beton türü (örnekler Int, [Char]ve Maybe Bool) programınızda bir değere atanabilir türüdür ve hangi tip yapıcı işlevi Bir tipin beslemek gerekir olmaya muktedir bir değere atanmış. Bir değer asla "liste" türünde olamaz çünkü "bir şeylerin listesi " olması gerekir. Aynı ruhla, bir değer asla "ikili ağaç" tipinde olamaz, çünkü "bir şeyi depolayan ikili ağaç " olması gerekir.

Örneğin, Boolbir argüman olarak BTreeiletirsek, s'yi BTree Booldepolayan ikili ağaç olan türü döndürür Bool. Tipi değişken her geçtiği yerini atürüyle Boolve bu doğru nasıl kendiniz görebilirsiniz.

İsterseniz BTree, tür ile bir işlev olarak görüntüleyebilirsiniz .

BTree :: * -> *

Türler bir şekilde türlere benzer - *somut bir türü belirtir, bu yüzden somut bir türden somut bir türe olduğunu söylüyoruz BTree.

Sarmak

Bir an geri gelin ve benzerlikleri not edin.

  • Bir veri yapıcı 0 veya daha fazla süren bir "işlev" olduğu değerleri ve yeni bir değer size geri verir.

  • Bir tür kurucusu , 0 veya daha fazla türü alan ve size yeni bir türü geri veren bir "işlev" dir .

Değerlerimizde küçük değişiklikler istiyorsak, parametrelere sahip veri oluşturucular harikadır - bu varyasyonları parametrelere koyarız ve değeri yaratan kişinin hangi argümanları koyacağına karar vermesine izin veririz. Aynı anlamda, parametrelere sahip yazım oluşturucular da iyidir. türlerimizde küçük değişiklikler istiyorsak! Bu varyasyonları parametre olarak koyarız ve türü yaratan kişinin hangi argümanları koyacağına karar vermesine izin veririz.

Bir vaka çalışması

Burada ev gerginliği olarak, Maybe atürünü düşünebiliriz . Tanımı

data Maybe a = Nothing
             | Just a

Burada, Maybesomut bir tür döndüren bir tür oluşturucu. Justbir değer döndüren bir veri yapıcısıdır. Nothingbir değer içeren bir veri yapıcısıdır. Türüne bakarsak Just, görürüz

Just :: a -> Maybe a

Başka bir deyişle, Justbir tür değeri alır ave bir tür değeri döndürür Maybe a. Türüne bakarsak Maybe, bunu görürüz

Maybe :: * -> *

Başka bir deyişle, Maybesomut bir türü alır ve somut bir türü döndürür.

Bir kere daha! Somut bir tür ve bir tür yapıcı işlevi arasındaki fark. Bir Maybes listesi oluşturamazsınız - eğer yürütmeye çalışırsanız

[] :: [Maybe]

bir hata alırsınız. Ancak Maybe Int, bir veya listesi oluşturabilirsiniz Maybe a. Bunun nedeni Maybe, bir tür yapıcı işlev olmasıdır, ancak bir listenin somut türde değerler içermesi gerekir. Maybe Intve Maybe asomut türlerdir (veya isterseniz, somut türleri döndüren yapıcı işlevlerini yazma çağrıları.)


2
İlk örneğinizde, hem RED GREEN hem de BLUE, hiçbir argüman almayan kuruculardır.
OllieB

3
data Colour = Red | Green | Blue"Hiçbir kurucumuz yok" iddiası düpedüz yanlıştır. Tip oluşturucuların ve veri oluşturucuların argüman almasına gerek yoktur, örneğin haskell.org/haskellwiki/Constructor'dadata Tree a = Tip | Node a (Tree a) (Tree a) "iki veri oluşturucu, Tip ve Node vardır" olduğuna dikkat edin .
Frerich Raabe

1
@CMCDragonkai Kesinlikle haklısınız! Türler "tür türleridir". Tür ve değer kavramlarını birleştirmeye yönelik yaygın bir yaklaşım, bağımlı yazım olarak adlandırılır . İdris , Haskell'den esinlenen, bağımlı olarak yazılmış bir dildir. Doğru GHC uzantılarıyla Haskell'de bağımlı yazmaya biraz da yaklaşabilirsiniz. (Bazı insanlar "Haskell araştırması, bağımlı türlere sahip olmadan bağımlı türlere ne kadar yaklaşabileceğimizi
bulmakla ilgilidir

1
@CMCDragonkai Aslında standart Haskell'de boş bir veri bildirimine sahip olmak mümkün değildir. Ancak -XEmptyDataDeclsbunu yapmanıza izin veren bir GHC uzantısı ( ) vardır. Dediğiniz gibi, bu türle hiçbir değer olmadığından, bir işlev f :: Int -> Zörneğin hiçbir zaman geri dönmeyebilir (çünkü ne döndürür?) Bununla birlikte, türleri istediğinizde ancak değerleri gerçekten önemsemediğinizde yararlı olabilirler .
kqr

1
Gerçekten mümkün değil mi? Sadece GHC'de denedim ve hatasız çalıştırdı. Herhangi bir GHC uzantısı yüklemem gerekmedi, sadece vanilya GHC. O zaman yazabilirim :k Zve bana bir yıldız verdi.
CMCDragonkai

42

Haskell, çok az başka dilin sahip olduğu cebirsel veri türlerine sahiptir. Belki de kafanızı karıştıran şey budur.

Diğer dillerde, genellikle bir "kayıt", "yapı" veya benzeri, çeşitli farklı veri türlerini tutan bir grup adlandırılmış alana sahip olabilirsiniz. Ayrıca bazen (küçük) sabit olası değerler (örneğin, sizin Red, Greenve Blue) içeren bir "numaralandırma" da yapabilirsiniz .

Haskell, sen yapabilirsiniz birleştirmek aynı zamanda bu iki. Garip ama gerçek!

Neden "cebirsel" deniyor? İnekler "toplam türleri" ve "ürün türleri" hakkında konuşurlar. Örneğin:

data Eg1 = One Int | Two String

Bir Eg1değer temelde ya bir tamsayı veya bir dize. Dolayısıyla, tüm olası Eg1değerlerin kümesi, tüm olası tamsayı değerlerinin ve olası tüm dizi değerlerinin kümesinin "toplamıdır". Bu nedenle, inekler Eg1bir "toplam türü" olarak adlandırılır. Diğer yandan:

data Eg2 = Pair Int String

Her Eg2değer hem bir tam sayıdan hem de bir dizeden oluşur. Dolayısıyla, tüm olası Eg2değerlerin kümesi, tüm tam sayılar kümesinin ve tüm dizelerin kümesinin Kartezyen çarpımıdır. İki küme birlikte "çarpılır", dolayısıyla bu bir "ürün türü" dür.

Haskell'in cebirsel türleri, toplam ürün türleridir . Bir kurucuya bir ürün türü oluşturmak için birden çok alan verirsiniz ve bir toplam (ürünler) yapmak için birden çok kurucunuz olur.

Bunun neden yararlı olabileceğine bir örnek olarak, verileri XML veya JSON olarak çıkaran bir şeye sahip olduğunuzu ve bunun bir yapılandırma kaydı aldığını varsayalım - ancak açıkça, XML ve JSON için yapılandırma ayarları tamamen farklıdır. Öyleyse şöyle bir şey yapabilirsiniz:

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

(Açıkçası bazı uygun alanlar var.) Normal programlama dillerinde böyle şeyler yapamazsınız, bu yüzden çoğu insan buna alışmaz.


4
harika! Wikipedia , "Neredeyse her dilde inşa edilebilirler" diye bir şey var . :) Örneğin C / ++ 'da, bu unionbir etiket disiplini ile s'dir. :)
Will Ness

5
Evet, ama ne zaman bahsetsem union, insanlar bana "bunu kim kullanır ki ?" Diye bakıyorlar. ;-)
MathematicalOrchid

1
unionC kariyerimde çok kullanıldığını gördüm . Lütfen gereksiz görünmesine izin vermeyin çünkü durum böyle değil.
truthadjustr

26

En basit durumla başlayın:

data Color = Blue | Green | Red

Bu, Colorargüman almayan bir "tür yapıcı" tanımlar - ve üç "veri oluşturucu" Blue, Greenve Red. Veri yapıcılarından hiçbiri argüman almaz. Bu araçlar orada tipte üç olduklarını Color: Blue, Greenve Red.

Bir veri yapıcısı, bir çeşit değer yaratmanız gerektiğinde kullanılır. Sevmek:

myFavoriteColor :: Color
myFavoriteColor = Green

Bir değer yaratan myFavoriteColorkullanarak Greenveri yapıcısı - ve myFavoriteColortipte olacak Colorbu veri yapıcısı tarafından üretilen değerlerin tipi beri.

Bir tür oluşturucu, bir tür tür oluşturmanız gerektiğinde kullanılır . İmza yazarken genellikle durum budur:

isFavoriteColor :: Color -> Bool

Bu durumda, Colortür yapıcısını çağırırsınız (hiçbir argüman almaz).

Hala benimle?

Şimdi, sadece kırmızı / yeşil / mavi değerler yaratmak istemediğinizi, aynı zamanda bir "yoğunluk" da belirlemek istediğinizi hayal edin. Örneğin, 0 ile 256 arasında bir değer. Bunu, veri oluşturucuların her birine bir bağımsız değişken ekleyerek yapabilirsiniz, böylece şunu elde edersiniz:

data Color = Blue Int | Green Int | Red Int

Şimdi, üç veri oluşturucunun her biri bir tür bağımsız değişken alıyor Int. Tip yapıcısı ( Color) hala herhangi bir argüman almaz. Bu yüzden favori rengim koyu yeşil, yazabilirim

    myFavoriteColor :: Color
    myFavoriteColor = Green 50

Ve yine, Greenveri yapıcısını çağırıyor ve bir tür değeri alıyorum Color.

İnsanların bir rengin yoğunluğunu nasıl ifade edeceğini dikte etmek istemediğinizi düşünün. Bazıları az önce yaptığımız gibi sayısal bir değer isteyebilir. Diğerleri, sadece "parlak" veya "çok parlak değil" ifadesini gösteren bir boole ile iyi olabilir. Bunun çözümü Int, veri oluşturucularda kod yazmak yerine bir tür değişkeni kullanmaktır:

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

Şimdi, tür oluşturucumuz bir bağımsız değişken alır (sadece adlandırdığımız başka bir tür a!) Ve tüm veri oluşturucuları bu türden bir bağımsız değişken (bir değer!) Alır a. Böylece alabilirsin

myFavoriteColor :: Color Bool
myFavoriteColor = Green False

veya

myFavoriteColor :: Color Int
myFavoriteColor = Green 50

ColorVeri yapıcıları tarafından döndürülecek olan "etkili" türü elde etmek için tür oluşturucuyu bir bağımsız değişkenle (başka bir tür) nasıl çağırdığımıza dikkat edin . Bu, bir veya iki fincan kahve eşliğinde okumak isteyebileceğiniz türlerin konseptine dokunuyor .

Şimdi, veri yapıcılarının ve tür oluşturucuların ne olduğunu ve veri oluşturucularının bağımsız değişken olarak diğer değerleri nasıl alabileceğini ve tür oluşturucuların diğer türleri bağımsız değişken olarak alabileceğini bulduk. HTH.


Sıfır veri yapıcı fikrinizle arkadaş olduğumdan emin değilim. Haskell'deki sabitler hakkında konuşmanın yaygın bir yolu olduğunu biliyorum, ancak birkaç kez yanlış olduğu kanıtlanmadı mı?
kqr

@kqr: Bir veri yapıcısı boş olabilir, ancak o zaman artık bir işlev değildir. Fonksiyon, bir argüman alan ve bir değer veren, yani ->imzasında olan bir şeydir .
Frerich Raabe

Bir değer birden çok türü işaret edebilir mi? Yoksa her değer yalnızca 1 türle mi ilişkili ve hepsi bu?
CMCDragonkai

1
@jrg Bazı örtüşmeler vardır, ancak bu özellikle tür kurucuları nedeniyle değil, tür değişkenleri nedeniyle, örneğin ain data Color a = Red a. arastgele bir tür için yer tutucudur. , Tip örneğin bir fonksiyon olsa Sen düz fonksiyonlarda aynı olabilir (a, b) -> a(tiplerinin iki değerin bir demet alır ave b) ve ilk değeri verir. Bu, "genel" bir işlevdir, çünkü tuple öğelerinin türünü dikte etmez - yalnızca işlevin, ilk tuple öğesiyle aynı türde bir değer verdiğini belirtir.
Frerich Raabe

1
+1 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.Bu çok faydalıdır.
Jonas

5

Diğerlerinin de belirttiği gibi, polimorfizm burada o kadar da kullanışlı değil. Muhtemelen aşina olduğunuz başka bir örneğe bakalım:

Maybe a = Just a | Nothing

Bu türün iki veri oluşturucusu vardır. Nothingbiraz sıkıcı, herhangi bir yararlı veri içermiyor. Öte yandan Just, atürü ne olursa olsun bir değer içerir a. Bu türü kullanan bir işlev yazalım, örneğin Inteğer varsa bir listenin başına geçelim (umarım bunun bir hata atmaktan daha yararlı olduğunu kabul edersiniz):

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

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

Yani bu durumda abir Int, ancak diğer türler için de işe yarayacaktır. Aslında, işlevimizi her tür liste için çalıştırabilirsiniz (uygulamayı değiştirmeden bile):

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

Öte yandan, yalnızca belirli bir türü kabul eden işlevler yazabilirsiniz Maybe, örn.

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

Çok uzun lafın kısası, polimorfizm ile kendi tipinize farklı türlerdeki değerlerle çalışma esnekliği verirsiniz.

Örneğinizde, bir noktada Stringşirketi tanımlamak için yeterli olmadığına ancak kendi türüne sahip olması gerektiğine karar verebilirsiniz Company(ülke, adres, geçmiş hesaplar vb. Gibi ek verileri tutar). İlk uygulamanızın , ilk değeri yerine Carkullanmak üzere değiştirilmesi gerekir . İkinci uygulamanız gayet iyi, onu kullandığınız gibi kullanın ve eskisi gibi çalışacaktır (elbette şirket verilerine erişen işlevlerin değiştirilmesi gerekir).CompanyStringCar Company String Int


Başka bir veri bildiriminin veri bağlamında tür oluşturucuları kullanabilir misiniz? Gibi bir şey data Color = Blue ; data Bright = Color? Bunu ghci'de denedim ve tür yapıcısındaki Color'ın Bright tanımındaki Color veri yapıcısı ile hiçbir ilgisi yok gibi görünüyor. Yalnızca 2 Renk yapıcısı vardır, biri Veri, diğeri Türdür.
CMCDragonkai

@CMCDragonkai Bunu yapabileceğini sanmıyorum ve bununla ne elde etmek istediğinden bile emin değilim. Mevcut bir türü dataveya newtype(ör. data Bright = Bright Color) Kullanarak "kaydırabilir" veya typebir eşanlamlısı tanımlamak için kullanabilirsiniz (ör. type Bright = Color).
Landei

5

İkincisi, içinde "polimorfizm" kavramına sahiptir.

a b cHerhangi bir türde olabilir. Örneğin, aolabilir [String], bolabilir [Int] ve colabilir[Char] .

Birincisinin türü sabitken: şirket a String, model a Stringve yılInt .

Araba örneği, polimorfizm kullanmanın önemini göstermeyebilir. Ancak verilerinizin liste türünde olduğunu hayal edin. Bir liste içerebilirString, Char, Int ... Bu durumlarda, verilerinizi tanımlamanın ikinci yoluna ihtiyacınız olacaktır.

Üçüncü yola gelince, önceki tipe uyması gerektiğini düşünmüyorum. Haskell'de verileri tanımlamanın başka bir yolu.

Bu, yeni başlayan biri olarak benim mütevazi düşüncem.

Btw: Beyninizi iyi eğittiğinizden ve bu konuda kendinizi rahat hissettiğinizden emin olun. Monad'ı daha sonra anlamanın anahtarı budur.


1

Yaklaşık var türleri : İlk durumda, türlerini ayarlamak String(şirket ve model için) ve Intyıl için. İkinci durumda, daha genelsiniz. a, bve cilk örnekte olduğu gibi aynı tipte veya tamamen farklı bir şey olabilir. Örneğin, yılı tamsayı yerine dizge olarak vermek faydalı olabilir. Ve isterseniz, Colortürünüzü bile kullanabilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.