Haskell / GHC'deki `forall` anahtar kelimesi ne işe yarar?


312

forallAnahtar kelimenin sözde "varoluş türleri" olarak nasıl kullanıldığını anlamaya başlıyorum :

data ShowBox = forall s. Show s => SB s

Bununla birlikte, forallbu sadece nasıl kullanıldığı hakkında bir alt kümedir ve aklımı böyle şeylerde kullanımı etrafında satamıyorum:

runST :: forall a. (forall s. ST s a) -> a

Veya bunların neden farklı olduğunu açıklamak için:

foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

Veya tüm bu RankNTypesşeyler ...

Akademik ortamlarda normal olan dil türlerinden ziyade açık, jargon içermeyen İngilizce'yi tercih ediyorum. Ben bu konuda okumaya çalışıyorum açıklamaların çoğu (arama motorları aracılığıyla bulabilirsiniz olanlar) bu sorunları var:

  1. Tamamlanmamışlar. Onlar kod okuyana kadar beni mutlu hissettiriyor ( "varoluşsal türleri" gibi) bu anahtar kullanımının bir kısmını açıklamak ki tamamen farklı bir şekilde kullanır bunu (gibi runST, foove barüzeri).
  2. Ayrık matematik, kategori teorisi veya soyut cebirin hangi dalında bu hafta popüler olduğunu en son okuduğum varsayımlarla yoğun bir şekilde doludurlar. (Bir daha asla " uygulama ayrıntıları için ne olursa olsun makaleye danışın" kelimesini okumam, çok yakında olacaktır.)
  3. Sık sık basit kavramları bile kıvrımlı bükülmüş ve kırılmış dilbilgisi ve semantiğe dönüştürecek şekilde yazılmıştır.

Yani...

Asıl soru üzerine. forallAnahtar kelimeyi jargonda demlenmiş bir matematikçi olduğumu varsaymayan, açık, düz İngilizce (veya bir yerde varsa, kaçırdığım açık bir açıklamaya işaret eden) tamamen açıklayabilir mi?


Eklemek için düzenlendi:

Aşağıdaki yüksek kaliteli cevaplardan iki çarpıcı cevap vardı, ama ne yazık ki en iyisini seçebiliyorum. Norman'ın cevabı , bazı şeylerin teorik temellerini forallve aynı zamanda bana bazı pratik sonuçlarını gösteren bir şekilde açıklayan ayrıntılı ve yararlıydı . yairchu'nun cevabıbaşka hiç kimsenin bahsetmediği bir alanı (kapsam tipi değişkenleri) kapsamakta ve tüm kavramları kod ve bir GHCi oturumu ile göstermektedir. Her ikisini de en iyi şekilde seçebilseydim, yapardım. Ne yazık ki yapamam ve her iki yanıtı da yakından inceledikten sonra, yairchu'nun açıklayıcı kod ve ekli açıklama nedeniyle Norman'ın biraz dışına çıkmasına karar verdim. Bu biraz haksızlık, çünkü gerçekten forallbunu bir tip imzayla gördüğümde beni zayıf bir korku hissi bırakmayan noktaya anlamak için her iki cevaba da ihtiyacım vardı .


7
Haskell wiki bu konuda oldukça acemi görünüyor.
jhegedus

Yanıtlar:


263

Bir kod örneği ile başlayalım:

foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
foob postProcess onNothin onJust mval =
    postProcess val
    where
        val :: b
        val = maybe onNothin onJust mval

Bu kod, düz Haskell 98'de derlenmez (sözdizimi hatası) forall. Anahtar kelimeyi desteklemek için bir uzantı gerektirir .

Temelde, orada 3 olan farklı ortak için kullandığı forallanahtar kelime (ya da en azından öyle görünüyor ), ve her biri kendi Haskell uzantısı vardır: ScopedTypeVariables, RankNTypes/ Rank2Types, ExistentialQuantification.

Yukarıdaki kod, bunlardan herhangi birinde sözdizimi hatası almaz, yalnızca etkinleştirilmiş yazım denetimleri alır ScopedTypeVariables.

Kapsamlı Değişkenler:

Kapsamlı tür değişkenleri, bir wherecümle içindeki kod için türlerin belirlenmesine yardımcı olur . O yapar biçinde val :: baynı one bin foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b.

Kafa karıştırıcı bir nokta : forallBir türden çıkardığınızda, aslında hala dolaylı olarak orada olduğunu duyabilirsiniz . ( Norman'ın cevabından: "normalde bu diller foramili polimorfik tiplerden çıkarır" ). Bu iddia doğru, ancak bu diğer kullanımlar belirtmektedir foralliçin değil, ScopedTypeVariableskullanım.

Derece-N-Türleri:

Bununla başlayalım mayb :: b -> (a -> b) -> Maybe a -> beşdeğerdir mayb :: forall a b. b -> (a -> b) -> Maybe a -> b, hariç zaman için ScopedTypeVariablesetkindir.

Bu her için çalışır anlamına gelir ave b.

Diyelim ki böyle bir şey yapmak istiyorsunuz.

ghci> let putInList x = [x]
ghci> liftTup putInList (5, "Blah")
([5], ["Blah"])

Bunun türü ne olmalı liftTup? Öyle liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b). Nedenini görmek için kodu kodlamaya çalışalım:

ghci> let liftTup liftFunc (a, b) = (liftFunc a, liftFunc b)
ghci> liftTup (\x -> [x]) (5, "Hello")
    No instance for (Num [Char])
    ...
ghci> -- huh?
ghci> :t liftTup
liftTup :: (t -> t1) -> (t, t) -> (t1, t1)

"Hmm .. GHC neden tuplein aynı tipte iki tane içermesi gerektiğini çıkartıyor? Diyelim ki olması gerekmiyor"

-- test.hs
liftTup :: (x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

ghci> :l test.hs
    Couldnt match expected type 'x' against inferred type 'b'
    ...

Hmm. işte GHC, uygulanacak izin vermez liftFuncüzerinde vnedeniyle v :: bve liftFuncbir istemektedir x. İşlevimizin mümkün olan her işlevi kabul etmesini istiyoruz x!

{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

Yani liftTupherkes için geçerli değil , xaldığı fonksiyon bu.

Varoluşsal Niceleme:

Bir örnek kullanalım:

-- test.hs
{-# LANGUAGE ExistentialQuantification #-}
data EQList = forall a. EQList [a]
eqListLen :: EQList -> Int
eqListLen (EQList x) = length x

ghci> :l test.hs
ghci> eqListLen $ EQList ["Hello", "World"]
2

Bu, Sıra-N-Türlerinden nasıl farklıdır?

ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
    Couldnt match expected type 'a' against inferred type '[Char]'
    ...

Sıralama-N-Türleri ile, forall aifadenizin mümkün olan tüm karakterlere uyması gerektiği anlamına gelir a. Örneğin:

ghci> length ([] :: forall a. [a])
0

Boş bir liste herhangi bir türün listesi olarak çalışır.

Yani Varoluşçu-Ölçülmesine ile, foralliçinde s datatanımları anlamına, değer ihtiva edebilir olması herhangi değil ki uygun tip gerekir olması tüm uygun türleri.


Tamam, altı saatimi aldım ve şimdi cevabınızı çözebilirim. :) Sizinle Norman arasında tam olarak aradığım cevabı buldum. Teşekkürler.
SADECE benim doğru GÖRÜŞÜM

2
Aslında, ScopedTypeVariablesolduğundan daha kötü görünüyorsun. b -> (a -> b) -> Maybe a -> bBu uzantıya sahip türü yazarsanız, yine de tam olarak eşdeğer olacaktır forall a b. b -> (a -> b) -> Maybe a -> b. Eğer başvurmak isterseniz Ancak, aynı b (ve dolaylı olarak ölçülebilir yok) o zaman açıkça niceliksel versiyonunu yazmak gerekir. Aksi takdirde, STVson derece müdahaleci bir uzantı olacaktır.
nominolo

1
@nominolo: Küçük düşürmek istemedim ScopedTypeVariablesve bunun kötü olduğunu düşünmüyorum. imho programlama süreci için ve özellikle Haskell yeni başlayanlar için çok yararlı bir araç ve ben var olan için minnettarım.
yairchu

2
Bu oldukça eski bir soru (ve cevap), ancak varoluşçu tiplerin GADT'leri kullanarak (en azından benim için) nicelemeyi daha kolay anlaşılır hale getirecek şekilde ifade edilebileceği gerçeğini yansıtacak şekilde güncellenmeye değer olabilir.
dfeuer

1
Şahsen, varoluşsal gösterimi GADT formuna çevirisi açısından kendi başına açıklamanın / anlamanın daha kolay olduğunu düşünüyorum, ancak başka türlü düşünmekten kesinlikle özgürsünüz.
dfeuer

117

Herkes forall anahtar kelimesini açık ve net İngilizce olarak tamamen açıklayabilir mi?

Hayır. (Belki Don Stewart yapabilir.)

Basit, açık bir açıklamanın önündeki engeller veya forall:

  • Bu bir nicelik belirteci. Bir evrensel veya varoluşsal nicelik belirleyici görmek için en azından küçük bir mantığa (yüklem hesabı) sahip olmanız gerekir. Daha önce yüklem hesabı görmediyseniz veya niceleyicilerle rahat değilseniz (ve doktora yeterlilik sınavları sırasında rahat olmayan öğrencileri gördüm), o zaman sizin için kolay bir açıklama yoktur forall.

  • Bu bir tür niceliktirici. Sistem F'yi görmediyseniz ve polimorfik tipler yazarken biraz pratik yaptıysanız , forallkafa karıştırıcı bulacaksınız . Haskell veya ML ile deneyim yeterli değildir, çünkü normalde bu diller forallpolimorfik tiplerden çıkarılır . (Aklımda, bu bir dil tasarım hatası.)

  • Özellikle Haskell'de forallkafa karıştırıcı bulduğum şekillerde kullanılıyor. (Bir tür teorisyeni değilim, ama benim çalışma ile temas halinde getiriyor lot tipi teorisinin ve onunla oldukça rahatım.) Benim için, karışıklık ana kaynağıdır ki forallbu tür kodlamak için kullanılır Ben yazmayı tercih ederim exists. Nicelik belirteçleri ve okları içeren zor bir tür izomorfizm ile haklı ve bunu her anlamak istediğimde, şeylere bakmam ve izomorfizmi kendim halletmem gerekiyor.

    Tip izomorfizması fikrinden memnun değilseniz veya tip izomorfizmleri hakkında düşünen bir uygulamanız yoksa, bu kullanımı forallsizi sabitleyecektir.

  • Genel konsepti forallher zaman aynı olsa da (bir tip değişkenini tanıtmak için bağlanma), farklı kullanımların ayrıntıları önemli ölçüde değişebilir. Gayri resmi İngilizce, varyasyonları açıklamak için çok iyi bir araç değildir. Neler olup bittiğini gerçekten anlamak için biraz matematiğe ihtiyacınız var. Bu durumda ilgili matematik, Benjamin Pierce'ın çok iyi bir kitap olan Türleri ve Programlama Dilleri tanıtım metninde bulunabilir .

Özel örneklerinize gelince,

  • runST gerektiğini başınızı zarar olun. Daha yüksek rütbe türleri (bir okun solunda) nadiren vahşi doğada bulunur. Ben tanıtıldı kağıdı okumanızı öneririz runST: "Tembel Fonksiyonel Devlet Konular" . Bu gerçekten iyi bir kağıttır ve runSTözellikle türü ve genel olarak daha üst sıra tipleri için size çok daha iyi bir sezgi verecektir . Açıklama birkaç sayfa alıyor, çok iyi yapılmış ve burada yoğunlaştırmaya çalışmayacağım.

  • Düşünmek

    foo :: (forall a. a -> a) -> (Char,Bool)
    bar :: forall a. ((a -> a) -> (Char, Bool))

    Ben ararsam bar, ben sadece her tür seçebilirsiniz aSevdiğim o ve ben türünden bunu bir işlev iletebilirsiniz atürüne a. Örneğin, işlevi (+1)veya işlevi geçebilirim reverse. Aklınıza gelebilecek forall"Şimdi türü seçmeniz olsun" şeklindeki. (Türü seçmek için kullanılan teknik kelime somutlaştırıcıdır .)

    Arama kısıtlamaları fooçok daha katıdır: bir argüman bir polimorfik fonksiyon foo olmalıdır . Bu tür ile, sadece fonksiyonlar ben geçebilir fooolan idveya bir işlev olduğunu hep ıraksadığını veya hatalar gibi undefined. Nedeni ile olduğunu foo, forallbir arayan böylece, okun solunda fooI neyi almaya alamadım aolduğunu-ziyade bu kadar uygulama arasında foobu ne almak için alır aolduğunu. Çünkü forallokun solunda, olduğu gibi okun üstünde değil bar, somutlaştırma çağrı yerine değil, fonksiyonun gövdesinde gerçekleşir.

Özet: Bir tam açıklama forallanahtar kelime matematik gerektirir ve sadece matematik okudu birisi tarafından anlaşılabilir. Kısmi açıklamaların bile matematik olmadan anlaşılması zordur. Ama belki benim kısmi, matematik dışı açıklamalarım biraz yardımcı oluyor. Launchbury ve Peyton Jones'u okuyun runST!


Ek: "üst", "alt", "solundaki Jargon". Bunların türlerin yazılı olduğu metinsel yollarla ve soyut sözdizimi ağaçlarıyla ilgili her şeyi ilgilendirmez. Soyut sözdiziminde a forall, bir tür değişkeninin adını alır ve daha sonra forall'ın tam altında "tam" bir tür vardır. Bir ok iki tür alır (bağımsız değişken ve sonuç türü) ve yeni bir tür (işlev türü) oluşturur. Bağımsız değişken türü okun "solunda"; soyut sözdizim ağacında okun sol çocuğudur.

Örnekler:

  • İçinde forall a . [a] -> [a], forall okun üstündedir; okun solunda ne var [a].

  • İçinde

    forall n f e x . (forall e x . n e x -> f -> Fact x f) 
                  -> Block n e x -> f -> Fact x f

    parantez içindeki tür "okun solunda forall" olarak adlandırılır. (Üzerinde çalıştığım bir optimizer'da böyle türleri kullanıyorum.)


Aslında ben bunu düşünmek zorunda kalmadan yukarı / aşağı / sol var. Ben bir dullardım, evet, ama daha önce bu şeylerle uğraşmak zorunda kalan yaşlı bir dullard. (Diğerleri arasında bir ASN.1 derleyicisi yazmak;) Zeyilname için teşekkürler.
SADECE benim doğru GÖRÜŞÜM

12
@ SADECE teşekkürler ama gelecek nesiller için yazıyorum. Ben forall a . [a] -> [a]forall oku solun olduğunu düşünen birden fazla programcı ile karşılaştım .
Norman Ramsey

Tamam, cevabınızı ayrıntılı olarak incelemek, şimdi, size kalbimin derinliklerinden teşekkür etmeliyim, Norman. Bir sürü yüksek sesle şimdi tıklayıp ile yerine konan ve gelmiştir hala en azından ben değilim olduğunu kabul I anlamıyorum bu şeyler geliyordu onu anlamak ve biraz üzerinde geçecek forallolan durumlarda, etkin hat olarak gürültü, ses. Bağlantı kurduğunuz makaleye bakacağım (bağlantı için teşekkürler!) Ve bunun benim anlama alanımda olup olmadığını göreceğim. Kudos.
SADECE benim doğru GÖRÜŞÜM

10
Sola okudum ve tam anlamıyla sola baktım. "Ağaç ayrıştırma" diyene kadar bu benim için oldukça belirsizdi.
Paul Nathan

Pierce'ın kitabına gösterici sayesinde. Sistem F'nin çok net bir açıklaması var. Neden existshiç uygulanmadığını açıklıyor . (Bu Sistem F'nin bir parçası değildir!) Haskell'de Sistem F'nin bir kısmı örtülü hale getirilir, ancak forallhalının altına tamamen süpürülemeyen şeylerden biridir. Sanki forallörtük yapılmasına izin verecek olan Hindley-Milner ile başlamışlar ve daha sonra daha güçlü bir tip sistemi seçmişlerdi, FOL'un 'forall'ı' ve 'varlığını' inceleyen ve orada durmuş olanlarımızı karıştırdık.
T_S_

50

Orijinal cevabım:

Herkes forall anahtar kelimesini açık ve anlaşılır bir şekilde tamamen açıklayabilir mi?

Norman'ın belirttiği gibi, tip teorisinden teknik bir terimin açık ve basit bir İngilizce açıklamasını yapmak çok zordur. Hepimiz deniyoruz.

'Forall' hakkında hatırlanması gereken tek bir şey var: türleri bir kapsama bağlar . Bunu anladıktan sonra, her şey oldukça kolaydır. Tür seviyesinde 'lambda' (veya bir tür 'let') eşdeğeri - Norman Ramsey, aynı kapsam kavramını mükemmel cevabında ifade etmek için "sol" / "yukarıda" kavramını kullanıyor .

'Forall' in çoğu kullanımı çok basittir ve bunları GHC Kullanıcı Kılavuzu, S7.8'de , özellikle 'forall' iç içe formlarında mükemmel S7.8.5'te bulabilirsiniz .

Haskell'de, tür evrensel olarak nicelleştirildiğinde, türler için genellikle bağlayıcıyı bırakıyoruz, şöyle:

length :: forall a. [a] -> Int

şuna eşittir:

length :: [a] -> Int

Bu kadar.

Tür değişkenlerini şimdi bir kapsama bağlayabildiğiniz için , tür değişkeninin yalnızca veri yapısı içinde görülebildiği ilk örneğiniz gibi üst düzeyden (" evrensel olarak nicelikli ") farklı kapsamlara sahip olabilirsiniz . Bu gizli türlere izin verir (" varoluşsal türler "). Ya da bağların keyfi yuvalanmasına sahip olabiliriz ("sıralama N türleri").

Tip sistemlerini derinlemesine anlamak için bazı jargon öğrenmeniz gerekecektir. Bilgisayar biliminin doğası budur. Bununla birlikte, yukarıdaki gibi basit kullanımlar, değer düzeyinde 'let' ile kıyaslanarak sezgisel olarak kavranabilmelidir. Harika bir tanıtım Launchbury ve Peyton Jones .


4
teknik olarak, etkinleştirildiği zamana length :: forall a. [a] -> Inteşdeğer değildir . Ne zaman orada, bu etkiler 'ın (eğer varsa) maddesini ve adlandırılmış tip değişkenlerin anlamını değiştirir içinde. length :: [a] -> IntScopedTypeVariablesforall a.lengthwherea
yairchu

2
Aslında. ScopedTypeVariables hikayeyi biraz karmaşıklaştırır.
Don Stewart

3
@DonStewart, açıklamanızda "tür değişkenlerini bir kapsama bağlar" şeklinde daha iyi ifade edilebilir mi?
Romildo

31

Ayrık matematik, kategori teorisi veya soyut cebirin hangi dalında bu hafta popüler olduğunu en son okuduğum varsayımlarla yoğun bir şekilde doludurlar. (Bir daha asla "uygulama ayrıntıları için ne olursa olsun makaleye danışın" kelimesini okumam, çok yakında olacaktır.)

Peki ya basit birinci dereceden mantık? evrensel nicelemeyeforall referans olarak oldukça açıktır ve bu bağlamda varoluşçu terimi de daha anlamlı olur, ancak bir anahtar kelime olsaydı daha az garip olurdu . Nicemlemenin etkili bir şekilde evrensel veya varoluşsal olması, değişkenlerin bir işlev okunun hangi tarafında kullanıldığı yere göre nicelleştiricinin yerleşmesine bağlıdır ve hepsi biraz kafa karıştırıcıdır.exists

Yani, bu yardımcı olmazsa veya sembolik mantığı sevmiyorsanız, daha işlevsel bir programlama-ish perspektifinden, tip değişkenlerini işleve sadece (örtük) tip parametreleri olarak düşünebilirsiniz . Bu anlamda tip parametrelerini alan işlevler geleneksel olarak büyük bir lambda kullanılarak yazılır, bu nedenle burada yazacağım /\.

Bu nedenle, idişlevi düşünün :

id :: forall a. a -> a
id x = x

Lambdas olarak yeniden yazabilir, "type parametresini" tür imzasının dışına çıkarabilir ve satır içi tür ek açıklamaları ekleyebiliriz:

id = /\a -> (\x -> x) :: a -> a

İşte aynı şey const:

const = /\a b -> (\x y -> x) :: a -> b -> a

Yani barfonksiyonunuz böyle bir şey olabilir:

bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)

Bağımsız bardeğişken olarak verilen işlevin bartürünün tür parametresine bağlı olduğunu unutmayın. Bunun yerine böyle bir şey olup olmadığını düşünün:

bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)

Burada bar2işlevi tür bir şeye uyguluyoruz Char, bu nedenle tür hatası bar2dışında herhangi bir tür parametresi vermek Char.

Öte yandan, fooşöyle görünebilir:

foo = (\f -> (f Char 't', f Bool True))

Aksine bar, fooaslında herhangi bir tür parametresi almaz! Kendisi bir tür parametresi alan bir işlevi alır, ardından bu işlevi iki farklı türe uygular .

Dolayısıyla forall, bir tür imzasında a gördüğünüzde , bunu tür imzaları için lambda ifadesi olarak düşünün . Tıpkı normal lambdalar gibi, kapsam forallda olabildiğince sağa, parantez içine alınmaya kadar uzanır ve tıpkı normal lambdaya bağlı değişkenler gibi, a ile bağlanan tip değişkenleri forallsadece nicel ifade içinde yer alır.


Post scriptum : Belki de merak edebilirsiniz - şimdi tip parametrelerini alan fonksiyonları düşündüğümüze göre, neden bu parametrelerle bir tip imzasına koymaktan daha ilginç bir şey yapamıyoruz? Cevap şu ki yapabiliriz!

Tür değişkenlerini bir etiketle bir araya getiren ve yeni bir tür döndüren bir işlev, aşağıdaki gibi bir şey yazabileceğiniz bir tür yapıcısıdır :

Either = /\a b -> ...

Ancak tamamen yeni bir gösterime ihtiyacımız var, çünkü böyle bir türün Either a byazılma şekli, "işlevi Eitherbu parametrelere uygula" yı zaten düşündürüyor .

Öte yandan, tür parametrelerinde farklı türler için farklı değerler döndüren bir tür "desen eşleşir" işlevi , bir tür sınıfının yöntemidir . /\Yukarıdaki sözdizimimde hafif bir genişleme şöyle bir şey olduğunu gösteriyor:

fmap = /\ f a b -> case f of
    Maybe -> (\g x -> case x of
        Just y -> Just b g y
        Nothing -> Nothing b) :: (a -> b) -> Maybe a -> Maybe b
    [] -> (\g x -> case x of
        (y:ys) -> g y : fmap [] a b g ys 
        []     -> [] b) :: (a -> b) -> [a] -> [b]

Şahsen, Haskell'in gerçek sözdizimini tercih ettiğimi düşünüyorum ...

Tip modelini "modeliyle eşleştiren" ve isteğe bağlı, mevcut tip döndüren bir işlev , bir tür ailesi veya işlevsel bağımlılıktır - eski durumda, zaten bir işlev tanımı gibi çok fazla görünüyor.


1
Buraya ilginç bir bakış. Bu bana uzun vadede verimli olabilecek bir başka saldırı açısı veriyor. Teşekkürler.
SADECE benim doğru GÖRÜŞÜM

@KennyTM: Ya λda bu konuda GHC'nin unicode sözdizimi uzantısı , λ bir mektup olduğu için varsayımsal olarak büyük lambda soyutlamalarım için de geçerli olacak talihsiz bir gerçek olduğunu desteklemez. Bu nedenle /\ kıyasen \ . Sanırım yeni kullanabilirdim ama yüklem analizinden kaçınmaya çalışıyordum ...
CA McCann

29

İşte size zaten aşina olabileceğiniz sade terimlerle ilgili hızlı ve kirli bir açıklama.

forallAnahtar kelime gerçekten sadece Haskell bir şekilde kullanılmaktadır. Gördüğünüz zaman her zaman aynı şey demektir.

Evrensel nicelik belirleme

Bir evrensel sayısal tipi şeklinde bir türüdür forall a. f a. Bu tür bir değer olarak düşünülebilir bir fonksiyonu , bir alan tipi a bağımsız değişken olarak ve bir döner değeri Çeşidi f a. Haskell'de bu tür argümanları tür sistemi tarafından dolaylı olarak iletilir. Bu "işlev" hangi tipte olursa olsun size aynı değeri vermelidir, bu nedenle değer polimorfiktir .

Örneğin, türü düşünün forall a. [a]. Bu türdeki bir değer başka bir tür alır ave aynı türdeki öğelerin listesini verir a. Elbette sadece bir olası uygulama vardır. aKesinlikle herhangi bir tür olabileceğinden , size boş liste vermek zorunda kalacaksınız . Boş liste, öğe türünde polimorfik olan tek liste değeridir (öğe içermediğinden).

Veya türü forall a. a -> a. Böyle bir işlevin arayanı hem bir tür hem de bir tür adeğeri sağlar a. Uygulama daha sonra aynı türde bir değer döndürmelidir a. Yine tek bir olası uygulama var. Verilen aynı değeri döndürmek zorunda kalacaktı.

Varoluşsal niceleme

Bir varoluşsal sayısal tip formunu olurdu exists a. f aHaskell o notasyonu destekleniyorsa,. Bu tür bir değer, bir tür ve bir tür değerden oluşan bir çift (veya "ürün") olarak düşünülebilir .af a

Örneğin, bir tür değeriniz varsa exists a. [a], bazı tür öğelerin bir listesine sahip olursunuz. Herhangi bir tür olabilir, ancak ne olduğunu bilmeseniz bile, böyle bir listede yapabileceğiniz çok şey var. Tersine çevirebilir veya öğe sayısını sayabilir veya öğelerin türüne bağlı olmayan başka bir liste işlemi gerçekleştirebilirsiniz.

Tamam, bir dakika bekle. Haskell neden forallaşağıdaki gibi "varoluşsal" bir türü belirtmek için kullanıyor ?

data ShowBox = forall s. Show s => SB s

Kafa karıştırıcı olabilir, ancak veri yapıcısının türünü gerçekten tanımlar SB:

SB :: forall s. Show s => s -> ShowBox

Bir kez inşa edildiğinde, tipin değerini ShowBoxiki şeyden oluşan olarak düşünebilirsiniz . Bir tür sdeğeri ile birlikte bir türdür s. Başka bir deyişle, varoluşsal olarak nicelenmiş türün bir değeridir. ShowBoxGerçekten olarak yazılmış olabilir exists s. Show s => sHaskell o notasyonu destekleniyorsa,.

runST ve arkadaşlar

Buna göre, bunlar nasıl farklı?

foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

Önce alalım bar. Bir tür ave bir tür işlevi alır ve bir tür a -> adeğeri üretir (Char, Bool). Biz Intolarak seçebilir ave buna Int -> Intörneğin bir işlev verebiliriz . Ama foofarklı. Uygulamanın fooistediği herhangi bir türü verebileceğimiz işleve geçirebilmesini gerektirir . Bu yüzden makul olarak verebileceğimiz tek işlev id.

Şimdi şu türlerin anlamını çözebilmeliyiz runST:

runST :: forall a. (forall s. ST s a) -> a

Dolayısıyla runST, ane tür olursa olsun , bir tür değer üretebilmelidir a. Bunu yapmak için, forall s. ST s akesinlikle bir şekilde üretmesi gereken bir tür argümanı kullanır a. Dahası, ane tür bir uygulama runSTyapmaya karar verirse versin , tür bir değer üretebilmelidir s.

Tamam, ne olmuş yani? Bunun yararı, bunun, arayanın runST, tipin türü aiçermeyeceği konusunda bir kısıtlama getirmesidir s. ST s [s]Örneğin , bir tür değer iletemezsiniz. Uygulamada bunun anlamı, uygulamanın runSTtip değeri ile mutasyon yapmakta serbest olmasıdır s. Tip, bu mutasyonun uygulanması için yerel olduğunu garanti eder runST.

Tipi runSTbir örneğidir seviye-2 polimorfik tip bağımsız değişken tipi bir içerdiği için forallnicelik. Yukarıdaki tip foode rütbe 2'dir. Sıradan bir polimorfik tip, bunun gibi bar, rütbe-1'dir, ancak argüman tiplerinin kendi forallnicelleştiricileri ile polimorfik olması gerekiyorsa rütbe-2 olur . Ve eğer bir fonksiyon rank-2 argümanlarını alırsa, onun türü rank-3'tür, vb. Genel olarak, polimorfik rütbe argümanlarını alan bir tür rütbeye nsahiptir n + 1.


11

Herkes forall anahtar kelimesini açık, düz İngilizce olarak açıklayabilir (veya bir yerde varsa, özlediğim açık bir açıklamaya işaret edebilir), jargonda demlenmiş bir matematikçi olduğumu varsaymıyor mu?

forallHaskell ve tip sistemleri bağlamında sadece anlamını ve belki de uygulanmasını açıklamaya çalışacağım .

Ama sizi anlamadan önce sizi Runar Bjarnason'un " Kısıtlar Özgür, Özgürlükler Kısıtlama " başlıklı çok erişilebilir ve hoş bir sohbete yönlendirmek istiyorum . Konuşma, gerçek dünya kullanım örneklerinden örneklerle ve bu ifadeyi desteklemese de Scala'daki örneklerle doludur forall. forallAşağıdaki perspektifi açıklamaya çalışacağım .

                CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN

Bu açıklamanın aşağıdaki açıklamaya devam etmesi için sindirilmesi ve inanması çok önemlidir, bu yüzden konuşmayı izlemenizi tavsiye ederim (en azından bölümleri).

Şimdi Haskell tipi sistemin ifadesini gösteren çok yaygın bir örnek bu tür bir imzadır:

foo :: a -> a

Bu tür imza verildiğinde, bu türü tatmin edebilecek tek bir işlev olduğu ve bu identityişlev veya daha popüler olarak bilinen şey olduğu söylenir id.

Haskell'i öğrenmemin başlangıç ​​aşamalarında her zaman aşağıdaki işlevleri merak ettim:

foo 5 = 6

foo True = False

ikisi de yukarıdaki tip imzayı karşılar, o zaman neden Haskell millet idtip imzayı karşılayan tek başına olduğunu iddia ediyor ?

Çünkü foralltür imzasında gizli bir gizli var . Gerçek tür:

id :: forall a. a -> a

Şimdi, ifadeye geri dönelim: Kısıtlamalar özgürleşiyor, özgürlükler kısıtlanıyor

Bunu tür sistemine çeviren bu ifade:

Tür düzeyinde bir kısıt, terim düzeyinde bir özgürlük haline gelir

ve

Tür düzeyinde bir özgürlük, terim düzeyinde bir kısıt haline gelir


İlk ifadeyi kanıtlamaya çalışalım:

Tür düzeyinde bir kısıtlama ..

Öyleyse tip imzamıza bir sınır koymak

foo :: (Num a) => a -> a

terim düzeyinde bir özgürlük haline gelir, tüm bunları yazma özgürlüğü veya esnekliği sağlar

foo 5 = 6
foo 4 = 2
foo 7 = 9
...

Aynı şey adiğer tip sınıflar vb. İle kısıtlanarak da görülebilir.

Şimdi bu tip imzanın ne anlama foo :: (Num a) => a -> ageldiği:

a , st a -> a, a  Num

Bu, türden bir şey beslendiğinde bir fonksiyonun aynı türden bir şey döndürdüğü ve bu örneklerin hepsinin Sayılar kümesine ait olduğu bazı örnekleri olduğu anlamına gelen varoluşsal niceleme olarak bilinir .aa

Bu nedenle a(Sayılar kümesine ait olması gereken) bir kısıtlama eklemeyi görebiliriz , terim düzeyini birden fazla olası uygulamaya sahip olacak şekilde serbest bırakır.


Şimdi ikinci ifadeye ve aslında şu açıklamayı taşıyan ifadeye geliyor forall:

Tür düzeyinde bir özgürlük, terim düzeyinde bir kısıt haline gelir

Şimdi işlevi tür düzeyinde özgürleştirelim:

foo :: forall a. a -> a

Şimdi bu şu anlama gelir:

a , a -> a

diğer bir deyişle, bu tür imzanın uygulanması a -> aher koşulda olduğu gibi olmalıdır .

Şimdi bu bizi terim düzeyinde kısıtlamaya başlıyor. Artık yazamıyoruz

foo 5 = 7

biz koyarsanız bu uygulama tatmin olmaz, çünkü abir şekilde Bool. aa Charveya a [Char]veya özel bir veri türü olabilir. Her koşulda benzer tipte bir şey döndürmelidir. Tip seviyesindeki bu özgürlük Evrensel Nicemleme olarak bilinen şeydir ve bunu karşılayabilecek tek işlev

foo a = a

yaygın olarak bilinen identityfonksiyon


Bu nedenle forall, libertyasıl amacı constrainbelirli bir uygulamaya yönelik terim düzeyinde olmak olan tür düzeyinde bir düzeydir.


9

Bu anahtar kelimenin farklı kullanımlarının nedeni, aslında en az iki farklı tür sistem uzantısında kullanılmasıdır: daha yüksek dereceli türler ve varoluşlar.

'Forall' in neden her ikisinde de uygun bir sözdizimi olduğunu açıklamaya çalışmak yerine, bu iki şeyi ayrı ayrı okumak ve anlamak muhtemelen en iyisidir.


3

Varoluşsal varoluş nasıl?

Varoluşçu Kantifikasyon ile, tanımlardaki foralls, dataiçerdiği değerin , tüm uygun tiplerde olması gerektiği değil , herhangi bir uygun tipte olabileceği anlamına gelir . - yachiru'nun cevabı

Neden bir açıklama forallyer dataizomorf olan tanımlara (exists a. a)(sözde Haskell) bulunabilir Vikikitap'ı yönettiği "Haskell / varoluşsal sayısal türleri" .

Aşağıda kısa bir kelimeyle özet olarak verilmiştir:

data T = forall a. MkT a -- an existential datatype
MkT :: forall a. a -> T -- the type of the existential constructor

Desen eşleme / yapısöküm oluştururken MkT x, tipi xnedir?

foo (MkT x) = ... -- -- what is the type of x?

xherhangi bir tür olabilir (bölümünde belirtildiği gibi forall) ve bu nedenle türü:

x :: exists a. a -- (pseudo-Haskell)

Bu nedenle, aşağıdakiler izomorfiktir:

data T = forall a. MkT a -- an existential datatype
data T = MkT (exists a. a) -- (pseudo-Haskell)

forall demek forall

Bütün bunlar hakkındaki basit yorumum, " forallgerçekten 'herkes için' demek." Yapılması gereken önemli bir ayrım forall, tanımın fonksiyon uygulamasına olan etkisidir .

A , değerin veya fonksiyonun tanımının polimorfik olması gerektiği forallanlamına gelir .

Tanımlanan şey polimorfik ise değer , o zaman değeri, tüm uygun için geçerli olması gerektiği anlamına gelir aoldukça kısıtlayıcı olan.

Tanımlanan şey bir polimorfik fonksiyon ise , o zaman fonksiyonun herkes için geçerli olması gerektiği anlamına gelir, abu sınırlayıcı değildir çünkü fonksiyon polimorfik olduğu için uygulanan parametrenin polimorfik olması gerektiği anlamına gelmez . Fonksiyon herkes için geçerli olup olmadığını kendisine, adaha sonra tersine herhangi uygun aolabilir uygulanan işleve. Ancak, parametrenin türü işlev tanımında yalnızca bir kez seçilebilir.

Bir ederse forallişlev parametre türü içindedir (yani Rank2Type) o zaman demek uygulamalı parametre olmalıdır gerçekten fikrine ile tutarlı olması için, polimorfik forallyollarla tanımı polimorfik olduğunu. Bu durumda, parametre tipi fonksiyon tanımında birden fazla kez seçilebilir ( "ve Norman tarafından işaret edildiği gibi" fonksiyonun uygulanmasıyla seçilir " ).

Bu nedenle, varoluşçu datatanımların herhangi birine izin vermesinin anedeni, veri yapıcısının polimorfik bir işlev olmasıdır :

MkT :: forall a. a -> T

tür MkT :: a -> *

Bu, herhangi bir aişleve uygulanabileceği anlamına gelir . Bir polimorfik değerin aksine :

valueT :: forall a. [a]

değer türüT :: a

Bu , valueT tanımının polimorfik olması gerektiği anlamına gelir . Bu durumda, tüm türlerin valueTboş listesi olarak tanımlanabilir [].

[] :: [t]

farklılıklar

Yapıcı kalıp eşleştirmede kullanılabileceğinden , anlamının foralltutarlı olması ExistentialQuantificationve RankNTypevaroluşların bir farkı vardır data. Belgelendiği gibi ghc kullanım kılavuzuna :

Kalıp eşleşmesi sırasında, her kalıp eşleşmesi her varoluşsal tip değişkeni için yeni, farklı bir tür sunar. Bu türler başka herhangi bir türle birleştirilemez veya desen eşleşmesinin kapsamından kaçamazlar.

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.