Daha yüksek türden türler ne zaman faydalıdır?


89

Bir süredir F # 'da geliştirme yapıyorum ve hoşuma gitti. Ancak F # 'da olmadığını bildiğim moda sözcüklerden biri daha yüksek türler. Üst düzey tiplerle ilgili materyaller okudum ve tanımlarını anladığımı düşünüyorum. Neden yararlı olduklarından emin değilim. Scala veya Haskell'de daha yüksek türden türlerin F #'da geçici çözümler gerektiren hangi türlerin kolaylaştırdığına dair bazı örnekler verilebilir mi? Ayrıca bu örnekler için, daha yüksek türde türler olmadan (veya F #'da tersi) geçici çözümler ne olurdu? Belki de üzerinde çalışmaya o kadar alıştım ki, bu özelliğin yokluğunu fark etmiyorum.

(Sanırım) Bunu anlıyorum myList |> List.map fveya myList |> Seq.map f |> Seq.toListdaha yüksek türden türler basitçe yazmanıza izin veriyor myList |> map fve bir List. Bu harika (doğru olduğunu varsayarsak), ama biraz önemsiz görünüyor? (Ve basitçe fonksiyon aşırı yüklemesine izin vererek yapılamaz mıydı?) Genellikle Seqyine de dönüştürürüm ve daha sonra istediğim şeye dönüştürebilirim. Yine, belki de bunun etrafında çalışmaya çok alıştım. Ancak, daha iyi türden türlerin sizi tuş vuruşlarında veya tür güvenliğinde gerçekten kurtardığı herhangi bir örnek var mı?


2
Control.Monad'daki işlevlerin çoğu daha yüksek türleri kullanır, bu nedenle bazı örnekler için oraya bakmak isteyebilirsiniz. F # 'da uygulamaların her somut monad türü için tekrarlanması gerekir.
Lee

1
@Lee ama sadece bir arayüz oluşturup IMonad<T>sonra onu örneğin IEnumerable<int>ya da IObservable<int>işiniz bittiğinde geri çeviremez miydiniz ? Bunların hepsi sadece oyuncu seçiminden kaçınmak için mi?
ıstakoz

4
İyi yayınlama güvenli değildir, bu nedenle tür güvenliği hakkındaki sorunuza yanıt budur. Başka bir sorun da, returnbu belirli bir örneğe değil, gerçekten monad türüne ait olduğu için nasıl çalışacağıdır, bu yüzden onu IMonadarayüze hiç koymak istemezsiniz .
Lee

4
@Lee yeah Ben sadece ifadeden sonra nihai sonucu atmanız gerektiğini düşünüyordum, önemli değil çünkü sadece ifadeyi yaptınız, böylece türü biliyorsunuz. Ama görünen o ki, her bir bindaka SelectManyvb. İmpl'in içine de atmanız gerekiyor . Birisi için API kullanabilirsiniz da demek oluyor ki bindbir IObservablebir etmek IEnumerableve, işe yarar mıydı varsayalım ki bu durum ve hiçbir şekilde bu çevrede olup olmadığını iğrenç evet. Sadece% 100 emin değilim.
ıstakoz

6
Harika soru. Henüz bu dil özelliğinin kullanışlı IRL olduğuna dair tek bir zorlayıcı pratik örnek görmedim.
JD

Yanıtlar:


79

Yani bir tür, basit tipidir. Örneğin Inttür *vardır, bu da bunun temel bir tür olduğu ve değerlerle somutlaştırılabileceği anlamına gelir. Yüksek türden türün gevşek bir tanımına göre (ve F #'ın çizgiyi nerede çizdiğinden emin değilim, bu yüzden sadece onu ekleyelim ) polimorfik kaplar , daha yüksek türden bir türün harika bir örneğidir.

data List a = Cons a (List a) | Nil

Tip kurucunun Listtürü * -> *vardır, bu da somut bir türle sonuçlanması için somut bir türden geçirilmesi gerektiği anlamına gelir: List Intsakinleri olabilir [1,2,3]ama Listkendisi olamaz.

Polimorfik kapların faydalarının açık olduğunu, ancak kaplardan daha yararlı * -> *türlerin var olduğunu varsayacağım . Örneğin ilişkiler

data Rel a = Rel (a -> a -> Bool)

veya ayrıştırıcılar

data Parser a = Parser (String -> [(a, String)])

ikisi de naziktir * -> *.


Haskell'de bunu daha da ileri götürerek, daha yüksek dereceli türlere sahip türlere sahip olabiliriz. Örneğin tür olan bir tip arayabiliriz (* -> *) -> *. Bunun basit bir örneği, Shapebir tür kabı doldurmaya çalışan olabilir * -> *.

data Shape f = Shape (f ())

[(), (), ()] :: Shape List

Bu, Traversableörneğin, her zaman şekillerine ve içeriklerine bölünebildikleri için Haskell'de s karakterizasyonu için kullanışlıdır .

split :: Traversable t => t a -> (Shape t, [a])

Başka bir örnek olarak, sahip olduğu dal türüne göre parametreleştirilmiş bir ağacı ele alalım. Örneğin, normal bir ağaç olabilir

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

Ancak dal türünün a Pairof Tree as içerdiğini görebiliriz ve böylece bu parçayı türden parametrik olarak çıkarabiliriz.

data TreeG f a = Branch a (f (TreeG f a)) | Leaf

data Pair a = Pair a a
type Tree a = TreeG Pair a

Bu TreeGtip kurucunun türü vardır (* -> *) -> * -> *. Bunu, aşağıdakiler gibi ilginç diğer varyasyonlar yapmak için kullanabiliriz:RoseTree

type RoseTree a = TreeG [] a

rose :: RoseTree Int
rose = Branch 3 [Branch 2 [Leaf, Leaf], Leaf, Branch 4 [Branch 4 []]]

Veya a gibi patolojik olanlar MaybeTree

data Empty a = Empty
type MaybeTree a = TreeG Empty a

nothing :: MaybeTree a
nothing = Leaf

just :: a -> MaybeTree a
just a = Branch a Empty

Veya bir TreeTree

type TreeTree a = TreeG Tree a

treetree :: TreeTree Int
treetree = Branch 3 (Branch Leaf (Pair Leaf Leaf))

Bunun ortaya çıktığı bir başka yer de "functors cebirleri" dir. Birkaç kat soyutluk bırakırsak, bu daha iyi bir katlama olarak düşünülebilir sum :: [Int] -> Int. Cebirler, functor ve taşıyıcı üzerinden parametreleştirilir . Funktoru tür vardır * -> *ve taşıyıcı tür *böylece tamamen

data Alg f a = Alg (f a -> a)

tür var (* -> *) -> * -> *. Algveri türleri ve bunların üzerine inşa edilmiş özyineleme şemaları ile ilişkisi nedeniyle kullanışlıdır.

-- | The "single-layer of an expression" functor has kind `(* -> *)`
data ExpF x = Lit Int
            | Add x x
            | Sub x x
            | Mult x x

-- | The fixed point of a functor has kind `(* -> *) -> *`
data Fix f = Fix (f (Fix f))

type Exp = Fix ExpF

exp :: Exp
exp = Fix (Add (Fix (Lit 3)) (Fix (Lit 4))) -- 3 + 4

fold :: Functor f => Alg f a -> Fix f -> a
fold (Alg phi) (Fix f) = phi (fmap (fold (Alg phi)) f)

Son olarak, teorik olarak mümkün olsalar da, daha üstün türden bir kurucu görmedim. Bazen bu türden işlevler görüyoruz mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b, ancak türlerdeki karmaşıklık düzeyini görmek için prolog veya bağımlı olarak yazılmış literatüre girmeniz gerektiğini düşünüyorum.


3
Kodu birkaç dakika içinde yazıp kontrol edip düzenleyeceğim, şu anda telefonumdayım.
J. Abrahamson

12
@ J.Abrahamson +1 iyi bir cevap için ve bunu telefonunuza yazmak için sabırlı olmak O_o
Daniel Gratzer

3
@lobsterism A TreeTreesadece patolojiktir, ancak daha pratik olarak, birbirinin arasına örülmüş iki farklı ağaç türünün olduğu anlamına gelir - bu fikri biraz daha ileri götürmek, statik olarak güvenli kırmızı / siyah ağaçlar ve düzgün, statik olarak dengelenmiş FingerTree tipi.
J. Abrahamson

3
@JonHarrop Standart bir gerçek dünya örneği, örneğin mtl tarzı efekt yığınları ile monadlar üzerinden soyutlama yapmaktır. Yine de bunun gerçek dünyanın değerli olduğu konusunda hemfikir olmayabilirsiniz. Genel olarak dillerin HKT'ler olmadan başarılı bir şekilde var olabileceğinin açık olduğunu düşünüyorum, bu nedenle herhangi bir örnek, diğer dillerden daha karmaşık olan bir tür soyutlama sağlayacaktır.
J. Abrahamson

2
Örneğin, çeşitli monadlarda yetkili efektlerin alt kümelerine ve bu spesifikasyonu karşılayan herhangi bir monad üzerinde soyuta sahip olabilirsiniz. Örneğin, karakter seviyesinde okuma ve yazmayı mümkün kılan "teletype" ı örnekleyen monadlar hem IO hem de bir boru soyutlamasını içerebilir. Başka bir örnek olarak çeşitli eşzamansız uygulamalar üzerinde soyutlama yapabilirsiniz. HKT'ler olmadan, o genel parçadan oluşan herhangi bir türü sınırlarsınız.
J. Abrahamson

64

FunctorHaskell'deki tür sınıfını düşünün , burada fdaha yüksek türde bir tür değişkeni vardır:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

Bu tür imzasının söylediği şey, fmap'in bir türünün tür parametresini folarak adeğiştirdiği b, ancak folduğu gibi bıraktığıdır . Yani fmapbir liste üzerinden kullanırsanız bir liste alırsınız, eğer onu bir ayrıştırıcı üzerinde kullanırsanız bir ayrıştırıcı alırsınız ve bu böyle devam eder. Ve bunlar statik , derleme zamanı garantileridir.

F # bilmiyorum, ama Functorsoyutlamayı Java veya C # gibi bir dilde, kalıtım ve jenerikle ifade etmeye çalışırsak , ancak daha yüksek türden jenerikler olmadan ne olacağını düşünelim. İlk deneme:

interface Functor<A> {
    Functor<B> map(Function<A, B> f);
}

Bu ilk denemedeki sorun, arabirimin gerçeklemesinin uygulayan herhangi bir sınıfı döndürmesine izin verilmesidir Functor. Biri yazabileceGin FunnyList<A> implements Functor<A>kimin mapyöntem hiç bir koleksiyon değil ama yine de bir olduğu koleksiyon, ya da başka hatta şeyin farklı bir tür döner Functor. Ayrıca, mapyöntemi kullandığınızda, aslında beklediğiniz türe indirgemediğiniz sürece, sonuçta alt türe özgü herhangi bir yöntemi çağıramazsınız. Yani iki sorunumuz var:

  1. Tür sistemi, mapyöntemin her zaman Functoralıcıyla aynı alt sınıfı döndürdüğü değişmezi ifade etmemize izin vermez .
  2. Bu nedenle, Functorsonucunda yöntem olmayan bir yöntemi çağırmanın statik olarak güvenli bir yolu yoktur map.

Deneyebileceğiniz başka, daha karmaşık yollar var, ancak hiçbiri gerçekten işe yaramıyor. Örneğin Functor, sonuç türünü kısıtlayan alt türlerini tanımlayarak ilk denemeyi artırmayı deneyebilirsiniz :

interface Collection<A> extends Functor<A> {
    Collection<B> map(Function<A, B> f);
}

interface List<A> extends Collection<A> {
    List<B> map(Function<A, B> f);
}

interface Set<A> extends Collection<A> {
    Set<B> map(Function<A, B> f);
}

interface Parser<A> extends Functor<A> {
    Parser<B> map(Function<A, B> f);
}

// …

Bu yanlış türde dönen gelenler dar arayüzleri uygulayıcıların yasaklamak için yardımcı olur Functorden mapyöntemle, ama kaç sınırı yok çünkü Functorsen olabilir uygulamaları, ihtiyacınız kaç dar arayüzleri konusunda herhangi bir sınırlama yoktur.

( DÜZENLEME: Ve bunun yalnızca Functor<B>sonuç türü olarak göründüğü için işe yaradığını ve bu nedenle alt arabirimlerin onu daraltabileceğini unutmayın. Bu nedenle AFAIK Monad<B>, aşağıdaki arabirimde her iki kullanımı da daraltamayız :

interface Monad<A> {
    <B> Monad<B> flatMap(Function<? super A, ? extends Monad<? extends B>> f);
}

Haskell'de, daha yüksek dereceli tip değişkenlerle, bu (>>=) :: Monad m => m a -> (a -> m b) -> m b.)

Yine başka bir deneme, ara yüzün alt tipin sonuç türünü alt tipin kendisiyle sınırlandırmasını sağlamak için özyinelemeli jenerikler kullanmaktır. Oyuncak örneği:

/**
 * A semigroup is a type with a binary associative operation.  Law:
 *
 * > x.append(y).append(z) = x.append(y.append(z))
 */
interface Semigroup<T extends Semigroup<T>> {
    T append(T arg);
}

class Foo implements Semigroup<Foo> {
    // Since this implements Semigroup<Foo>, now this method must accept 
    // a Foo argument and return a Foo result. 
    Foo append(Foo arg);
}

class Bar implements Semigroup<Bar> {
    // Any of these is a compilation error:

    Semigroup<Bar> append(Semigroup<Bar> arg);

    Semigroup<Foo> append(Bar arg);

    Semigroup append(Bar arg);

    Foo append(Bar arg);

}

Ancak bu tür bir teknik (bu, sıradan bir OOP geliştiriciniz için oldukça gizemli, aynı zamanda sıradan işlevsel geliştiricinize de bakın) hala istenen Functorkısıtlamayı ifade edemez :

interface Functor<FA extends Functor<FA, A>, A> {
    <FB extends Functor<FB, B>, B> FB map(Function<A, B> f);
}

Buradaki sorun bu engel olmayacak FBaynı olması Folarak FAbir türü bildirmek zaman o-yani List<A> implements Functor<List<A>, A>, mapyöntem olabilir hala bir dönüş NotAList<B> implements Functor<NotAList<B>, B>.

Ham türleri (parametresiz kapsayıcılar) kullanarak Java'da son deneme:

interface FunctorStrategy<F> {
    F map(Function f, F arg);
} 

Burada F, sadece Listveya gibi parametresiz tiplere örneklenecektir Map. Bu FunctorStrategy<List>, Lista'nın yalnızca a döndürebileceğini garanti eder - ancak listelerin öğe türlerini izlemek için tür değişkenlerini kullanmayı bıraktınız.

Buradaki sorunun özü, Java ve C # gibi dillerin tür parametrelerinin parametrelere sahip olmasına izin vermemesidir. Eğer Java'da, Tbir tür değişkendir, yazabilir Tve List<T>ancak, T<String>. Daha yüksek türden türler bu kısıtlamayı kaldırır, böylece böyle bir şeye sahip olabilirsiniz (tam olarak düşünülmemiş):

interface Functor<F, A> {
    <B> F<B> map(Function<A, B> f);
}

class List<A> implements Functor<List, A> {

    // Since F := List, F<B> := List<B>
    <B> List<B> map(Function<A, B> f) {
        // ...
    }

}

Ve özellikle bu kısma değinmek:

(Sanırım) Bunu anlıyorum myList |> List.map fveya myList |> Seq.map f |> Seq.toListdaha yüksek türden türler basitçe yazmanıza izin veriyor myList |> map fve bir List. Bu harika (doğru olduğunu varsayarsak), ama biraz önemsiz görünüyor? (Ve basitçe fonksiyon aşırı yüklemesine izin vererek yapılamaz mıydı?) Genellikle Seqyine de dönüştürürüm ve daha sonra istediğim şeye dönüştürebilirim.

mapİşlev fikrini , özünde haritalama dizilerle ilgiliymiş gibi modelleyerek bu şekilde genelleştiren birçok dil vardır . Bu yorumunuz bu ruhta: Eğer Seqiçeri ve dışarı dönüşümü destekleyen bir türünüz varsa , yeniden kullanarak harita işlemini "ücretsiz" alırsınız Seq.map.

Haskell'de ise Functorsınıf bundan daha geneldir; diziler kavramına bağlı değildir. Eylemler, ayrıştırıcı birleştiriciler, işlevler vb. fmapGibi dizilerle iyi bir eşlemesi olmayan türler için uygulayabilirsiniz IO:

instance Functor IO where
    fmap f action =
        do x <- action
           return (f x)

 -- This declaration is just to make things easier to read for non-Haskellers 
newtype Function a b = Function (a -> b)

instance Functor (Function a) where
    fmap f (Function g) = Function (f . g)  -- `.` is function composition

"Haritalama" kavramı aslında dizilere bağlı değildir. Functor yasalarını anlamak en iyisidir:

(1) fmap id xs == xs
(2) fmap f (fmap g xs) = fmap (f . g) xs

Gayri resmi olarak:

  1. Birinci yasa, bir kimlik / noop işleviyle haritalamanın hiçbir şey yapmamakla aynı olduğunu söylüyor.
  2. İkinci yasa, iki kez eşleştirerek üretebileceğiniz herhangi bir sonucu, bir kez eşleyerek de üretebileceğinizi söylüyor.

Bu yüzden fmaptürü korumak istiyorsunuz - çünkü mapfarklı bir sonuç türü üreten operasyonlar alır almaz, bunun gibi garantiler vermek çok çok daha zor hale geliyor.


Bu yüzden son parçanızla ilgileniyorum, zaten bir ameliyatı varken neden bir fmapon olması yararlı oluyor ? Operasyonun tanımı olmanın neden mantıklı olduğunu anlıyorum , ama bunun yerine kullanmanız gereken yeri anlamıyorum . Belki bunun yararlı olacağı bir örnek verebilirseniz, anlamama yardımcı olur. Function a..fmapfmap.
ıstakoz

1
Ah, anladım: günahın iki katı olan bir fn veren ve veren bir doublefunctor fn yapabilirsiniz . Bu zihniyette nerede düşünmeye başlarsanız, bir dizide bir harita çalıştırdığınızda bir dizi beklediğinizi görebiliyorum, sadece bir sekans değil, çünkü burada diziler üzerinde çalışıyoruz. double [1, 2, 3][2, 4, 6]double sin
ıstakoz

@lobsterizm: A'yı soyutlamaya Functorve kütüphanenin istemcisinin onu seçmesine izin vermeye dayanan algoritmalar / teknikler var . J. Abrahamson'ın cevabı bir örnek sağlar: özyinelemeli kıvrımlar, işlevler kullanılarak genelleştirilebilir. Başka bir örnek, serbest monadlardır; bunları, istemcinin "komut setini" keyfi olarak sağladığı bir tür genel yorumlayıcı uygulama kitaplığı olarak düşünebilirsiniz Functor.
Luis Casillas

3
Teknik olarak sağlam bir cevap ama bu, birisinin bunu pratikte neden isteyeceğini merak etmeme neden oluyor. Kendimi Haskell Functorveya a'ya ulaşırken bulamadım SemiGroup. Gerçek programlar bu dil özelliğini en çok nerede kullanır?
JD

28

Zaten burada bazı mükemmel cevaplarda bilgileri tekrarlamak istemiyorum, ancak eklemek istediğim önemli bir nokta var.

Belirli bir monad veya functor (veya aplikatif işlevci veya ok veya ...) uygulamak için genellikle daha yüksek türlere ihtiyacınız yoktur. Ancak bunu yapmak çoğunlukla noktayı kaçırmaktır.

Genel olarak, insanlar işlevsellerin / monadların / her şeyin yararlılığını görmedikleri zaman, bunun nedeni genellikle bu şeyleri birer birer düşünmeleridir . Functor / monad / etc işlemleri herhangi bir örneğe gerçekten hiçbir şey eklemez (bind, fmap vb. Çağırmak yerine, bind, fmap, vb. Uygulamak için kullandığım her türlü işlemi çağırabilirim ). Bu soyutlamaları gerçekten istediğiniz şey, herhangi bir functor / monad / etc ile genel olarak çalışan bir koda sahip olabilmenizdir .

Bu tür genel kodun yaygın olarak kullanıldığı bir bağlamda, bu, yeni bir monad örneği yazdığınız her seferde türünüzün, sizin için zaten yazılmış çok sayıda yararlı işleme anında erişim sağladığı anlamına gelir . Her yerde monadları (ve functorleri ve ...) görmenin amacı budur ; (ki bu bana kendi başına hiçbir şey kazandırmaz) uygulamak bindyerine kullanabileceğim concatve bunun yerine ihtiyaç duyduğumda ve başlangıçta gördüğüm kodu listeler açısından yeniden kullanabildiğim için çünkü aslında monad-jenerik .mapmyFunkyListOperationmyFunkyParserOperationmyFunkyIOOperation

Ancak tip güvenliğine sahip bir monad gibi parametreli bir tipte soyutlama yapmak için , daha yüksek türlere ihtiyacınız var (buradaki diğer cevaplarda da açıklanmıştır).


9
Bu, şimdiye kadar okuduğum diğer cevapların hepsinden daha faydalı bir cevap olmaya daha yakın, ancak yine de daha yüksek türlerin yararlı olduğu tek bir pratik uygulama görmek istiyorum.
JD

"Bu soyutlamaları gerçekten istediğiniz şey, herhangi bir functor / monad ile genel olarak çalışan bir koda sahip olabilmenizdir." F # 13 yıl önce hesaplama ifadeleri biçiminde monadlar aldı, başlangıçta sıralı ve eşzamansız monadlar kullanıyordu. Bugün F #, 3. bir sorguya sahiptir. Bu kadar az ortak noktası olan bu kadar az monad varken neden onları soyutlamak isteyesiniz?
JD

@JonHarrop Diğer insanların, HKT'leri destekleyen dillerde çok sayıda monad (ve işlevler, oklar, vb; HKT'ler yalnızca monadlardan ibaret değildir) kullanarak kod yazdığının ve bunları soyutlamak için kullanım alanları bulduğunun açıkça farkındasınız. Ve açıkça bu kodun herhangi bir pratik kullanımı olmadığını düşünüyorsunuz ve diğer insanların neden onu yazmakla uğraştığını merak ediyorsunuz. 5 yıl önce yorum yaptığınız 6 yıllık bir gönderiyle ilgili bir tartışma başlatmak için geri gelerek ne tür bir fikir edinmeyi umuyorsunuz?
Ben

"6 yaşındaki bir görevde bir tartışma başlatmak için geri gelerek kazanç elde etmeyi umuyorum". Geriye dönük. Geriye dönüp bakmanın yararı ile, artık F # 'nin monadlar üzerindeki soyutlamalarının büyük ölçüde kullanılmadığını biliyoruz. Bu nedenle, 3 büyük ölçüde farklı şeyi soyutlama yeteneği zorlayıcı değildir.
JD

@JonHarrop Cevabımın amacı, bireysel monadların (veya functors, vb.), Göçebe bir arayüz olmadan ifade edilen benzer işlevsellikten gerçekten daha kullanışlı olmadığı, ancak birçok farklı şeyi birleştirmenin olduğu. F # konusundaki uzmanlığınızı erteleyeceğim, ancak yalnızca 3 ayrı monad'a sahip olduğunu söylüyorsanız (başarısızlık, durum bilgisi, ayrıştırma vb. Gibi tüm kavramlara tekli bir arayüz uygulamak yerine), o zaman evet, bu 3 şeyi birleştirmekten çok fazla fayda görmemeniz şaşırtıcı değil.
Ben

15

Daha .NET'e özgü bir bakış açısı için, bir süre önce bununla ilgili bir blog yazısı yazdım . İşin püf noktası, yüksek türden türlerde, potansiyel olarak IEnumerablesve arasında aynı LINQ bloklarını yeniden kullanabilirsiniz IObservables, ancak daha yüksek türden türler olmadan bu imkansızdır.

Eğer (Ben blog yazdıktan sonra anladım) alabilir en yakın Kendi yapmaktır IEnumerable<T>ve IObservable<T>bir onları hem genişletilmiş IMonad<T>. Bu, LINQ bloklarınızı belirtilmişse yeniden kullanmanıza izin verir IMonad<T>, ancak artık tip güvenli değildir, çünkü karıştırıp eşleştirmenize IObservablesve IEnumerablesaynı blok içinde size izin verir , bu da bunu etkinleştirmek için ilgi çekici gelebilir temelde tanımlanmamış bazı davranışlar elde edin.

Haskell'in bunu nasıl kolaylaştırdığına dair sonraki bir yazı yazdım . (İşlemsiz, gerçekten - bir bloğu belirli bir tür monad ile sınırlamak kod gerektirir; yeniden kullanımı etkinleştirmek varsayılandır).


2
Pratik bir şeyden bahseden tek cevap olduğu için size +1 vereceğim, ancak IObservablesüretim kodunda hiç kullandığımı sanmıyorum .
JD

5
@JonHarrop Bu doğru değil. F # 'da tüm olaylar vardır IObservableve kendi kitabınızın WinForms bölümündeki olayları kullanırsınız.
Dax Fohl

1
Microsoft o kitabı yazmam için bana para ödedi ve bu özelliği ele almamı istedi. Üretim kodundaki olayları kullandığımı hatırlayamıyorum ama bakacağım.
JD

IQueryable ve IEnumerable arasında yeniden kullanım da mümkün olabilir sanırım
KolA

Dört yıl sonra aramayı bitirdim: Rx'i üretimden çıkardık.
JD

13

Haskell'de daha yüksek türden polimorfizmin en çok kullanılan örneği Monadarayüzdür. Functorve Applicativeaynı şekilde daha yüksek türdendir, bu yüzden Functorkısa ve öz bir şey göstermek için göstereceğim.

class Functor f where
    fmap :: (a -> b) -> f a -> f b

Şimdi, tür değişkeninin nasıl fkullanıldığına bakarak bu tanımı inceleyin . Bunun fdeğeri olan bir tür anlamına gelmediğini göreceksiniz . Bu tür imzadaki değerleri, bir işlevin bağımsız değişkenleri ve sonuçları oldukları için tanımlayabilirsiniz. Yani tür değişkenleri ave bdeğerleri olan türlerdir. Tip ifadeleri de f ave f b. Ama fkendisi değil . fdaha yüksek türde bir değişken örneğidir. Olduğu göz önüne alındığında *değerlere sahip olabilir türlerinin tür, fçeşit olması gerekir * -> *. Yani önceki muayene biliyoruz çünkü, değerlere sahip olabilir bir türünü dikkate alır olduğunu ave bdeğerlere sahip olmalıdır. Ve bunu da biliyoruz f avef b değerleri olmalıdır, bu nedenle değerlere sahip olması gereken bir tür döndürür.

Bu, daha yüksek türden bir tür değişkenin ftanımında kullanılmasını sağlar Functor.

ApplicativeVe Monadarayüzler daha eklemek, ama Uyumlu olmaları. Bu, tür değişkenleri üzerinde de çalıştıkları anlamına gelir * -> *.

Daha yüksek türlerde çalışmak, ek bir soyutlama düzeyi getirir - yalnızca temel türler üzerinde soyutlamalar oluşturmakla sınırlı değilsiniz. Ayrıca, diğer türleri değiştiren türler üzerinde soyutlamalar da oluşturabilirsiniz.


4
Hangi yüksek türlerin ne işe yaradığını merak etmeme neden olan bir başka harika teknik açıklama. Bunu gerçek kodda nerede kullandınız?
JD
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.