Java Arayüzü ve Haskell'in tür sınıfı: farklılıklar ve benzerlikler?


112

Haskell'i öğrenirken, Haskell'den kaynaklanan harika bir icat olduğu varsayılan yazı sınıfını fark ettim .

Bununla birlikte, Wikipedia sayfasında type sınıfında :

Programcı, sınıfa ait her tür için var olması gereken ilgili türleriyle birlikte bir dizi işlev veya sabit isim belirleyerek bir tür sınıfını tanımlar.

Bana göre Java Arayüzüne oldukça yakın görünüyor ( Wikipedia Arayüzü (Java) sayfasından alıntı ):

Java programlama dilindeki bir arabirim, sınıfların uygulaması gereken bir arabirimi (terimin genel anlamıyla) belirtmek için kullanılan soyut bir türdür.

Bu ikisi oldukça benzer görünür: tür sınıfı, bir türün davranışını sınırlarken, arabirim bir sınıfın davranışını sınırlar.

Haskell'deki tip sınıfı ile Java'daki arayüz arasındaki farklar ve benzerlikler nelerdir merak ediyorum, ya da belki temelde farklılar?

DÜZENLEME: Haskell.org'un bile benzer olduklarını kabul ettiğini fark ettim . Çok benziyorlarsa (ya da öyle mi?), O zaman neden tür sınıfına bu kadar heyecan veriliyor?

DAHA FAZLA DÜZENLEME: Vay canına, pek çok harika yanıt! Sanırım topluluğun hangisinin en iyisi olduğuna karar vermesine izin vermem gerekecek. Bununla birlikte, cevapları okurken, hepsi sadece "typeclass'ın yapabileceği pek çok şey vardır ve arayüzün jeneriklerle baş edemeyeceği veya başa çıkması gerekmediği" gibi görünüyor . Yardımcı olamıyorum ama merak ediyorum, tip sınıfları yapamazken arayüzlerin yapabileceği bir şey var mı? Ayrıca, Wikipedia'nın ilk olarak tip sınıfının 1989 tarihli makalesinde icat edildiğini iddia ettiğini fark ettim * "Geçici polimorfizm nasıl daha az geçici hale getirilir", Haskell hala beşiğinde iken, Java projesi 1991'de başlatılmış ve ilk olarak 1995'te piyasaya sürülmüştür. Yani belki typeclass arabirimlere benzemek yerine, tam tersi, arabirimler typeclass tarafından etkilenmiştir?Bunu destekleyen veya çürüten herhangi bir belge / belge var mı? Tüm cevaplar için teşekkürler, hepsi çok aydınlatıcı!

Tüm girdiler için teşekkürler!


3
Hayır, arabirimlerin genellikle Haskell'de bulunmayan yerleşik özelliklere sahip dillerde göründüğü büyük uyarı ile, bu tür sınıflarının yapamayacağı hiçbir şey yoktur. Java'ya tür sınıfları eklenmiş olsaydı, bu özellikleri de kullanabilirlerdi.
CA McCann

8
Birden fazla sorunuz varsa, birden çok soru sormalısınız, hepsini tek bir soruya sığdırmaya çalışmayın. Neyse, son soruyu cevaplamak için: Java'nın ana etkisi Objective-C (ve olduğu değil , asıl etkiler sırayla Smalltalk ve C. Java'nın çoğu zaman yanlış bildirilmektedir olarak C ++), arabirimler Amaç-C'nin bir uyum vardır protokolleri yine vardır OO'da protokol fikrinin resmileştirilmesi , bu da ağ iletişimindeki protokoller fikrine , özellikle ARPANet'e dayanmaktadır. Bunların hepsi, alıntı yaptığınız gazeteden çok önce oldu. ...
Jörg W Mittag

1
... Haskell'in Java üzerindeki etkisi çok daha sonra geldi ve sonuçta Haskell'in tasarımcılarından Phil Wadler tarafından birlikte tasarlanan Generics ile sınırlı.
Jörg W Mittag

5
Bu, Java'nın orijinal tasarımcılarından Patrick Naughton tarafından yazılan bir Usenet makalesidir: Java, C ++ 'dan değil, Objective-C'den Güçlü Bir Şekilde Etkilendi . Ne yazık ki, o kadar eskidir ki, orijinal gönderi Google'ın arşivlerinde bile görünmüyor.
Jörg W Mittag

8
Bunun birebir kopyası olarak kapatılan başka bir soru daha var, ancak çok daha derinlemesine bir cevabı var: stackoverflow.com/questions/8122109/…
Ben

Yanıtlar:


50

Bir arayüzün, SomeInterface ttüm değerlerin türüne sahip olduğu t -> whatever(nerede whateveriçermediği t) bir tür sınıfına benzediğini söyleyebilirim . Bunun nedeni, Java ve benzer dillerdeki kalıtım ilişkisinin türü ile çağrılan yöntemin çağrıldıkları nesnenin türüne bağlı olmasıdır, başka hiçbir şeye bağlı değildir.

Bu add :: t -> t -> t, birden fazla parametrede polimorfik olduğu bir arayüz gibi şeyler yapmanın gerçekten zor olduğu anlamına gelir , çünkü arayüzün, yöntemin argüman türünün ve dönüş türünün türü ile aynı olduğunu belirtmesinin bir yolu yoktur. çağrıldığı nesne (yani "öz" tipi). Generics ile bunu, nesnenin kendisiyle aynı türde olması beklenen genel parametreyle bir arabirim oluşturarak taklit etmenin çeşitli yolları vardır, örneğin nasıl Comparable<T>yapar, nerede kullanmanız beklenir, Foo implements Comparable<Foo>böylece compareTo(T otherobject)türün türü vardır t -> t -> Ordering. Ancak bu yine de programcının bu kuralı takip etmesini gerektirir ve aynı zamanda insanlar bu arayüzü kullanan bir işlev yapmak istediklerinde baş ağrısına neden olur, yinelemeli genel tür parametrelerine sahip olmaları gerekir.

Ayrıca, empty :: tburada bir işlevi çağırmadığınız için böyle şeyler olmayacak, bu yüzden bu bir yöntem değil.


1
Scala özellikleri (temelde arayüzler) this.type'a izin verir, böylece "self type" parametrelerini iade edebilir veya kabul edebilirsiniz. Scala, bununla hiçbir ilgisi olmayan, "kendi kendine tipler" olarak adlandırdıkları tamamen ayrı bir özelliğe sahiptir. Bunların hiçbiri kavramsal bir farklılık değil, sadece uygulama farkı.
clay

45

Arabirimler ve tür sınıfları arasında benzer olan şey, bir dizi ilgili işlemi adlandırmaları ve açıklamalarıdır. İşlemlerin kendileri adları, girdileri ve çıktıları aracılığıyla tanımlanır. Aynı şekilde, bu işlemlerin uygulanmasında büyük olasılıkla farklılık gösterecek birçok uygulaması olabilir.

Bunun dışında, işte bazı önemli farklılıklar:

  • Arayüz yöntemleri her zaman bir nesne örneğiyle ilişkilendirilir. Başka bir deyişle, yöntemin çağrıldığı nesne olan her zaman zımni bir 'bu' parametresi vardır. Bir tür sınıfı işlevinin tüm girdileri açıktır.
  • Bir arabirim uygulaması, arabirimi uygulayan sınıfın bir parçası olarak tanımlanmalıdır. Tersine, bir tür sınıfı 'örneği', başka bir modülde bile, ilişkili türünden tamamen ayrı olarak tanımlanabilir.

Genel olarak, tip sınıflarının arayüzlerden daha güçlü ve esnek olduğunu söylemenin adil olduğunu düşünüyorum. Bir dizeyi uygulama türünün bir değerine veya örneğine dönüştürmek için bir arabirimi nasıl tanımlarsınız? Kesinlikle imkansız değil, ancak sonuç sezgisel veya zarif olmayacak. Derlenmiş bir kitaplıkta bir tür için bir arabirim uygulamanın mümkün olmasını hiç dilediniz mi? Bunların her ikisinin de tür sınıflarıyla gerçekleştirilmesi kolaydır.


1
Tip sınıflarını nasıl uzatırsınız? Tip sınıfları, arayüzlerin arayüzleri nasıl genişletebileceği gibi diğer tip sınıflarını genişletebilir mi?
CMCDragonkai

10
Java 8'in arabirimlerdeki varsayılan uygulamaları ışığında bu yanıtı güncellemeye değer olabilir.
Monica'yı

1
@CMCDragonkai Evet, örneğin Bar türü sınıfının Foo türü sınıfını genişlettiğini belirtmek için "class (Foo a) => Bar a where ..." diyebilirsiniz. Java gibi, Haskell de burada birden fazla mirasa sahiptir.
Monica'yı

Bu durumda tip sınıfı Clojure'daki protokollerle aynı değil, ancak tip güvenliği ile mi?
nawfal

24

Tip sınıfları, temelde aşırı yüklenmiş fonksiyonlar için teknik bir terim olan "geçici polimorfizmi" ifade etmenin yapılandırılmış bir yolu olarak oluşturulmuştur . Bir tür sınıfı tanımı şuna benzer:

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

Bunun anlamı, işlevi foosınıfa ait bir türdeki bazı argümanlara Foobaruyguladığınızda, fooo türe özgü bir uygulama arar ve onu kullanır. Bu, daha esnek ve genelleştirilmiş olanlar dışında, C ++ / C # gibi dillerde operatörün aşırı yüklenmesi durumuna çok benzer.

Arayüzler OO dillerinde benzer bir amaca hizmet eder, ancak temelde yatan kavram biraz farklıdır; OO dilleri, Haskell'in basitçe sahip olmadığı yerleşik bir tür hiyerarşisi kavramıyla birlikte gelir; bu, bazı yönlerden sorunları karmaşık hale getirir, çünkü arayüzler hem alt tipleme yoluyla aşırı yüklemeyi içerebilir (yani, uygun örneklerde yöntemleri çağırma, alt tiplerin üst türlerinin yaptığı arayüzleri uygulama) ve düz tip tabanlı gönderim yoluyla (çünkü bir arabirimi uygulayan iki sınıf, aynı zamanda onu uygulayan ortak bir üst sınıfa sahip olmayabilir). Alt tiplemenin getirdiği büyük ek karmaşıklık göz önüne alındığında, tür sınıflarını OO olmayan bir dilde aşırı yüklenmiş işlevlerin geliştirilmiş bir sürümü olarak düşünmenin daha yararlı olduğunu düşünüyorum.

Ayrıca belirtmeye değer, tür sınıflarının çok daha esnek gönderme araçlarına sahip olmasıdır - arabirimler genellikle yalnızca onu uygulayan tek bir sınıf için geçerlidir, oysa tür sınıfları , sınıfın işlevlerinin imzasında herhangi bir yerde görünebilen bir tür için tanımlanır . Bunun OO arayüzlerindeki eşdeğeri, arayüzün o sınıfın bir nesnesini diğer sınıflara geçirme yollarını tanımlamasına , çağırma bağlamında hangi dönüş türünün gerekli olduğuna bağlı olarak bir uygulama seçecek statik yöntemler ve oluşturucular tanımlamasına, Arabirimi uygulayan sınıfla aynı türden argümanlar ve gerçekten çevrilemeyen çeşitli başka şeyler alın.

Kısaca: Benzer amaçlara hizmet ederler, ancak çalışma biçimleri biraz farklıdır ve tür sınıfları hem önemli ölçüde daha açıklayıcıdır hem de bazı durumlarda kalıtım hiyerarşisinin parçaları yerine sabit türler üzerinde çalıştıkları için kullanımı daha kolaydır.


Haskell'de tip hiyerarşilerini anlamakta zorlanıyordum, Omega gibi tip sistemler hakkında ne düşünüyorsunuz? Tür hiyerarşilerini simüle edebilirler mi?
CMCDragonkai

@CMCDragonkai: Omega'ya gerçekten özür dileyecek kadar aşina değilim.
CA McCann

16

Yukarıdaki cevapları okudum. Biraz daha net cevap verebileceğimi hissediyorum:

Bir Haskell "tip sınıfı" ve bir Java / C # "arayüzü" veya bir Scala "özelliği" temelde benzerdir. Aralarında kavramsal bir ayrım yoktur, ancak uygulama farklılıkları vardır:

  • Haskell türü sınıfları, veri türü tanımından ayrı olan "örnekler" ile uygulanır. C # / Java / Scala'da, arayüzler / özellikler sınıf tanımında uygulanmalıdır.
  • Haskell türü sınıflar, bu tür veya kendi kendine tür döndürmenize izin verir. Scala özellikleri de öyle (this.type). Scala'daki "kendi kendine türlerin" tamamen alakasız bir özellik olduğunu unutmayın. Java / C #, bu davranışı tahmin etmek için jeneriklerle karmaşık bir geçici çözüm gerektirir.
  • Haskell türü sınıflar, "this" türü parametresi olmadan işlevleri (sabitler dahil) tanımlamanıza izin verir. Java / C # arayüzleri ve Scala özellikleri, tüm işlevlerde "bu" giriş parametresi gerektirir.
  • Haskell türü sınıfları, işlevler için varsayılan uygulamaları tanımlamanıza izin verir. Scala özellikleri ve Java 8+ arayüzleri de öyle. C #, uzantı yöntemleriyle buna benzer bir şeyi tahmin edebilir.

2
Sadece bu cevap noktalarına biraz literatür eklemek için, bugün Javas yerine C # arayüzleriyle karşılaştıran Açık (Haskell) Tip Sınıfları ve (C #) Arayüzlerini okudum , ancak kavramsal açıdan bir kavramını inceleyerek iyi bir anlayış vermeliyim. dil sınırları arasında arayüz.
daniel.kahlenberg

Varsayılan uygulamalar gibi bazı yaklaşımların C # soyut sınıflarla daha verimli yapıldığını düşünüyorum.
Arwin

12

Gelen Programlama Master kafasında , Haskell Java arayüzleri ve tip sınıflar arasındaki benzerlikleri açıklamak Phil bakınız, Wadler, tip sınıfları mucidi ile Haskell hakkında bir röportaj var:

Şunun gibi bir Java yöntemi:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

Haskell yöntemine çok benzer:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

Dolayısıyla, tür sınıfları arabirimlerle ilişkilidir, ancak gerçek karşılık, yukarıdaki gibi bir türle parametrik hale getirilmiş statik bir yöntem olacaktır.


Cevap bu olmalı, çünkü Phil Wadler'ın ana sayfasına göre Haskell'in baş tasarımcısıydı, aynı zamanda daha sonra dilin kendisine dahil edilen Java için Jenerik uzantısını da tasarladı.
Wong Jia Hau


8

Tür sınıflarının arabirimlerin çözemediği bir dizi sorunu nasıl çözebileceğine dair örneklerin verildiği Yazılım Uzantısı ve Tür Sınıflarıyla Entegrasyonu okuyun .

Makalede listelenen örnekler şunlardır:

  • ifade problemi,
  • çerçeve entegrasyon sorunu,
  • bağımsız genişletilebilirlik sorunu,
  • baskın ayrışmanın, dağılmanın ve dolaşmanın zorbalığı.

3
Bağlantı yukarıda yok. Onun yerine bunu deneyin .
Justin Leitgeb

1
Peki şimdi o da öldü. Deneyin bu bir
RichardW

7

O kadar iyi görünüyorsa, "hype" düzeyiyle konuşamam. Ancak evet tipi sınıflar birçok yönden benzerdir. Düşünebildiğim bir fark, Haskell'in tür sınıfının işlemlerinden bazıları için davranış sağlayabilmesidir :

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

bu, tip sınıfının örnekleri olan şeyler için (==)eşit ve eşit olmayan iki işlem olduğunu gösterir . Ancak eşit olmayan işlem eşitler açısından tanımlanır (böylece yalnızca bir tane sağlamanız gerekir) ve bunun tersi de geçerlidir.(/=)Eq

Yani muhtemelen yasal olmayan Java'da bu şöyle bir şey olurdu:

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

ve bunun çalışma şekli, arayüzü uygulamak için bu yöntemlerden yalnızca birini sağlamanız gerektiğidir. Bu yüzden, arayüz seviyesinde istediğiniz davranışın bir tür kısmi uygulamasını sağlama yeteneğinin bir fark olduğunu söyleyebilirim .


6

Bunlar benzerdir (okuyun: benzer kullanıma sahiptir) ve muhtemelen benzer şekilde uygulanır: Haskell'deki polimorfik işlevler, başlık altında tip sınıfıyla ilişkili işlevleri listeleyen bir 'vtable' alır.

Bu tablo genellikle derleme sırasında çıkarılabilir. Bu muhtemelen Java'da daha az doğrudur.

Ancak bu, yöntemler değil , işlevler tablosudur . Yöntemler bir nesneye bağlıdır, Haskell tip sınıfları değildir.

Bunları Java'nın jenerikleri gibi görün.


3

Daniel'in dediği gibi, arayüz uygulamaları veri bildirimlerinden ayrı olarak tanımlanır . Ve diğerlerinin de belirttiği gibi, aynı serbest türü birden fazla yerde kullanan işlemleri tanımlamanın basit bir yolu var. Yani Numbir tip sınıfı olarak tanımlamak kolaydır . Böylece Haskell'de, gerçekten sihirli aşırı yüklenmiş operatörler olmadan operatör aşırı yüklemesinin sözdizimsel faydalarını elde ederiz - sadece standart tip sınıfları.

Diğer bir fark ise, henüz o türden somut bir değeriniz olmasa bile, bir türe dayalı yöntemleri kullanabilmenizdir!

Örneğin read :: Read a => String -> a,. Dolayısıyla, bir "okuma" sonucunu nasıl kullanacağınıza dair ortalıkta dolaşan yeterince başka tür bilginiz varsa, derleyicinin sizin için hangi sözlüğü kullanacağını bulmasına izin verebilirsiniz.

Okunabilir şeylerin herhangi bir listesi instance (Read a) => Read [a] where...için bir okuma örneği tanımlamanıza izin veren gibi şeyler de yapabilirsiniz . Java'da bunun pek mümkün olduğunu sanmıyorum.

Ve tüm bunlar, hiçbir hile olmadan sadece standart tek parametreli tip sınıflarıdır. Çok parametreli tip sınıflarını bir kez sunduğumuzda, o zaman yepyeni bir olasılıklar dünyası açılır ve daha da fazlası, tip sistemine çok daha fazla bilgi ve hesaplama yerleştirmenize izin veren işlevsel bağımlılıklar ve tip aileleri ile açılır.


1
Normal tip sınıfları arayüzler içindir, çünkü çok parametreli tip sınıfları OOP'de çoklu gönderime sahiptir; hem programlama dilinin hem de programcının baş ağrısının gücünde karşılık gelen bir artış elde edersiniz.
CA McCann
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.