Sınıflar vs nesne arayüzleri


33

Tip sınıflarını anladığımı sanmıyorum. Bir yerde, tip sınıflarını bir tipin uyguladığı “OO'dan” (“OO”) arayüzler olarak düşünmenin yanlış ve yanıltıcı olduğunu okudum. Sorun şu ki, onları farklı bir şey olarak görme konusunda sorun yaşıyorum ve bunun nasıl yanlış olduğunu.

Örneğin, bir tür sınıfım varsa (Haskell sözdiziminde)

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

Arayüzden [1] farklı (Java sözdiziminde)

interface Functor<A> {
  <B> Functor<B> fmap(Function<B, A> fn)
}

interface Function<Return, Argument> {
  Return apply(Argument arg);
}

Aklıma gelebilecek bir fark, belirli bir çağrıda kullanılan tip sınıfı uygulamasının tanımlanmadığı, ancak çevreden belirlendiği, yani bu tür bir uygulama için mevcut modülleri inceleyerek olduğu. Bu, bir OO dilinde ele alınabilecek bir uygulama eseri gibi görünüyor; gibi derleyici (veya çalışma zamanı) türünde gerekli arayüzü ortaya çıkaran bir sarmalayıcı / genişletici / maymun yamalı için tarama yapabilir.

Neyi kaçırıyorum?

[1] f aArgümanın fmapbir OO dili verildiğinden beri kaldırıldığını unutmayın, bu yöntemi bir nesnede çağırırsınız. Bu arayüz, f aargümanın çözüldüğünü varsayar .

Yanıtlar:


46

Temel formlarında, tip sınıfları nesne arayüzlerine biraz benzer. Ancak, birçok bakımdan, onlar çok daha geneldir.

  1. Sevkiyat, değerlerde değil türlerde. Bunu gerçekleştirmek için değer gerekli değildir. Örneğin, Haskell'in Readsınıfında olduğu gibi, sonuç fonksiyonu türünde gönderme yapmak mümkündür :

    class Read a where
      readsPrec :: Int -> String -> [(a, String)]
      ...
    

    Bu tür bir gönderme geleneksel OO'da açıkça mümkün değildir.

  2. Tip sınıfları doğal olarak, birden fazla parametre sağlayarak, birden fazla gönderime kadar uzanır:

    class Mul a b c where
      (*) :: a -> b -> c
    
    instance Mul Int Int Int where ...
    instance Mul Int Vec Vec where ...
    instance Mul Vec Vec Int where ...
    
  3. Örnek tanımları hem sınıf hem de tür tanımlarından bağımsızdır, bu da onları daha modüler hale getirir. A modülünden bir T tipi, M3 modülünde bir örnek sağlayarak, ikisinin de tanımını değiştirmeden M2 modülünden bir C sınıfına uyarlanabilir. OO'da bu, uzatma yöntemleri gibi daha ezoterik (ve daha az OO-ish) dili özellikleri gerektirir.

  4. Tip sınıfları, alt tipe değil, parametrik polimorfizme dayanır. Bu daha doğru yazmayı sağlar. Düşünün örneğin

    pick :: Enum a => a -> a -> a
    pick x y = if fromEnum x == 0 then y else x
    

    vs.

    pick(x : Enum, y : Enum) : Enum = if x.fromEnum() == 0 then y else x
    

    Eski durumda, başvurunun bir pick '\0' 'x'türü vardır Char, ikinci durumda, sonuç hakkında bildiğiniz her şey bir Enum olacaktır. (Bu günümüzde çoğu OO dilinin parametrik polimorfizmi bütünleştirmesinin nedeni de budur.)

  5. Yakından ilgili ikili yöntemler meselesi. Tip sınıfları ile tamamen doğaldırlar:

    class Ord a where
      (<) :: a -> a -> Bool
      ...
    
    min :: Ord a => a -> a -> a
    min x y = if x < y then x else y
    

    Sadece alt yazı ile Ordarayüz ifade etmek imkansızdır. Bunu doğru bir şekilde yapabilmek için daha karmaşık, özyinelemeli bir form veya "F sınırlı kantitatif" adı verilen parametrik polimorfizm gerekir. Java'ları Comparableve kullanımını karşılaştırın:

    interface Comparable<T> {
      int compareTo(T y);
    };
    
    <T extends Comparable<T>> T min(T x, T y) {
      if (x.compareTo(y) < 0)
        return x;
      else
        return y;
    }
    

Öte yandan, alt tipleme tabanlı arayüzler doğal olarak heterojen koleksiyonların oluşmasına izin verir, örneğin bir tip listesi ( List<C>alt tiplerini Ckullanmak dışında tam tiplerini elde etmek mümkün olmasa da) çeşitli alt tiplerine sahip olan üyeler içerebilir . Aynısını, sınıf sınıflarına göre yapmak için, ek bir özellik olarak varolan türlere ihtiyacınız vardır.


Ah, bu çok mantıklı. Değere dayalı gönderi türü vs muhtemelen düzgün düşünemedim büyük şey. Parametrik polimorfizm ve daha spesifik tipleme konusu mantıklı. Ben sadece bu ve alt-tabanlı arayüzleri zihnimde bir araya getirmiştim (görünüşe göre Java'da: - /).
oconnor0

Varoluşsal türler, alt Cpencere olmadan alt türler oluşturmaya benzer bir şey midir?
oconnor0

Biraz. Bunlar bir tür soyut yapmak, yani temsilini gizlemek için bir araçtır. Haskell'de, ona sınıf kısıtlamaları da eklerseniz, üzerinde bu sınıfların yöntemlerini kullanabilirsiniz, ancak başka bir şey kullanmazsınız. - Downcast'ler aslında hem altyazı hem de varoluşsal nicelemeden ayrı olan ve prensipte ikincisinin yanında da eklenebilecek bir özelliktir. Tıpkı onu sağlamayan OO dilleri olduğu gibi.
Andreas Rossberg,

Not: FWIW, Java’daki joker karakterler varoluşsal türlerdir, ancak bunlar sınırlı ve geçicidir (bu biraz kafa karıştırıcı olma nedenlerinin bir parçası olabilir).
Andreas Rossberg

1
@ didierc, statik olarak tamamen çözülebilen davalarla sınırlandırılacak. Dahası, tip sınıflarını eşleştirmek için, sadece geri dönüş tipine göre ayırt edebilen bir aşırı yüklenme çözünürlüğü formu gerekir (bakınız madde 1).
Andreas Rossberg

6

Andreas'ın mükemmel cevabına ek olarak, lütfen tür sınıflarının küresel ad alanını etkileyen aşırı yüklemeyi kolaylaştırmak için tasarlandığını unutmayın . Haskell'de tip sınıfları aracılığıyla elde edebileceğinizlerin dışında aşırı yüklenme yoktur. Buna karşılık, nesne arayüzleri kullandığınızda, sadece bu arayüzün argümanlarını aldıkları bildirilen fonksiyonların, o arayüzdeki fonksiyon isimleri hakkında endişelenmesi gerekir. Böylece, arayüzler yerel ad alanları sağlar.

Örneğin, fmap"Functor" adlı bir nesne arayüzüne sahiptiniz. Başka fmapbir arayüzde başka bir tane olması mükemmel olur , "Yapıcı". Her nesne (veya sınıf) uygulamak istediği arayüzü seçip seçebilir. Buna karşılık, Haskell'de, fmapbelirli bir bağlamda yalnızca bir taneye sahip olabilirsiniz . Hem Functor hem de Structor type sınıflarını aynı içeriğe alamazsınız.

Nesne arayüzleri, standart ML imzalarına sınıfları yazmaktan daha benzer.


ve yine de, ML modülleri ve Haskell tipi sınıflar arasında yakın bir ilişki olduğu görülüyor. cse.unsw.edu.au/~chak/papers/DHC07.html
Steven Shaw

1

Somut örneğinizde (Functor type sınıfı ile) Haskell ve Java uygulamaları farklı davranır. Belki veri türüne sahip olduğunuzu ve bunun Functor olmasını istediğinizi hayal edin (Haskell'deki, Java'da da kolayca uygulayabileceğiniz gerçekten popüler bir veri türüdür). Java örneğinizde, belki sınıfının Functor arayüzünüzü uygulamasını sağlayabilirsiniz. Yani aşağıdakileri yazabilirsiniz (sadece yalancı kod çünkü sadece c # arka planım var):

Maybe<Int> val = new Maybe<Int>(5);
Functor<Int> res = val.fmap(someFunctionHere);

resFunctor türünde olduğuna dikkat edin, Belki de değil. Bu sayede, Java uygulaması neredeyse kullanılamaz hale geliyor, çünkü somut bilgi türlerini kaybediyorsunuz ve yayın yapmanız gerekiyor. (en azından türlerin hala bulunduğu yerlerde böyle bir uygulama yazamadım). Haskell tipi sınıfları ile sonuç olarak Maybe Int elde edeceksiniz.


Bence bu, Java'nın daha yüksek tür türlerini desteklememesi ve Vs sınıfları tartışması arayüzleriyle ilgili olmadığını düşünüyorum. Java daha yüksek türlere sahipse, fmap çok iyi bir dönüş yapabilirdi Maybe<Int>.
dcastro
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.