Tür çıkarımı neden faydalıdır?


37

Kodları kod yazdığımdan daha sık okudum ve endüstriyel yazılım üzerinde çalışan programcıların çoğunun bunu yaptığını farz ediyorum. Varsayımım türünün avantajı, daha az ayrıntı ve daha az yazılı koddur. Ancak diğer yandan, kodu daha sık okursanız, muhtemelen okunabilir kod isteyeceksiniz.

Derleyici türünü etkiliyor; Bunun için eski algoritmalar var. Fakat asıl soru, programcı neden kodu okuduğumda değişkenlerimin türünü anlamak isteyeyim? Sadece türünü okumak kimsenin, hangi tür olduğunu düşünmekten daha hızlı değil mi?

Düzenleme: Sonuç olarak neden faydalı olduğunu anlıyorum. Ancak, dil özellikleri kategorisinde operatörün aşırı yüklendiği bir kovada görüyorum - bazı durumlarda yararlı ancak kötüye kullanım durumunda okunabilirliği etkiliyor.


5
Tecrübelerime göre kod yazarken okumaktan çok daha önemli . Kod okurken, iyi adlandırılmış değişkenlerin genellikle beni yönlendireceği algoritmalar ve özel bloklar arıyorum. Gerçekten çok kötü yazılmış olmadıkça ne yaptığını okumak ve anlamak için onay kodu yazmam gerekmez. Ancak, aradığım fazladan gereksiz ayrıntılarla şişirilmiş kodu okurken (çok fazla tür ek açıklaması gibi), aradığım bitleri bulmayı zorlaştırır. Söyleyeceğim tür çıkarım kod yazmaktan çok daha fazlasını okumak için büyük bir nimettir.
Jimmy Hoffa,

Aradığım kod parçasını bulduktan sonra, onu kontrol etmeye başlayabilirim, ancak herhangi bir zamanda belki 10 satırdan daha fazla kod satırına odaklanmamanız gerekir; kendiniz çıkarsayın çünkü başlangıçta tüm bloğu zihinsel olarak seçiyorsunuz ve muhtemelen bunu yapmanıza yardımcı olmak için alet kullanıyorsunuz. Bölgeye girmeye çalıştığınız 10 satır kodun türlerini nadiren bulmak çok zaman alıyor ancak bu, zaten her zaman çok daha az yaygın olan okumadan yazmaya geçtiğiniz bölüm.
Jimmy Hoffa,

Bir programcının koddan daha sık kod okumasına rağmen , kodun yazıldığından daha sık okunması anlamına gelmediğini unutmayın . Çok fazla kod kısa ömürlü olabilir veya başka bir şekilde bir daha asla okunmaz ve hangi kodun hayatta kalacağını ve en iyi okunabilirliğe yazılması gerektiğini söylemek kolay olmayabilir.
jpa

2
@JimmyHoffa 'nın ilk noktası üzerinde genel olarak okumayı düşünün. Bir cümlenin, tek tek kelimelerinin konuşma kısmına odaklanırken, tek kelimeyle anlaşılması ve okunması daha kolay mıdır? "(Makale) ineği (isim-tekil) ayın (isim) (madde) 'in üzerine (edat) aştı (fiil-geçmiş).
Zev Spitz

Yanıtlar:


46

Java'ya bir göz atalım. Java, türetilmiş değişkenlere sahip olamaz. Bu, türün ne olduğu bir insan okuyucu için tamamen açık olsa bile, sıkça yazıyı yazmam gerektiği anlamına gelir:

int x = 42;  // yes I see it's an int, because it's a bloody integer literal!

// Why the hell do I have to spell the name twice?
SomeObjectFactory<OtherObject> obj = new SomeObjectFactory<>();

Ve bazen sadece tüm türü heceleyerek sade bir sinir bozucu.

// this code walks through all entries in an "(int, int) -> SomeObject" table
// represented as two nested maps
// Why are there more types than actual code?
for (Map.Entry<Integer, Map<Integer, SomeObject<SomeObject, T>>> row : table.entrySet()) {
    Integer rowKey = entry.getKey();
    Map<Integer, SomeObject<SomeObject, T>> rowValue = entry.getValue();
    for (Map.Entry<Integer, SomeObject<SomeObject, T>> col : rowValue.entrySet()) {
        Integer colKey = col.getKey();
        SomeObject<SomeObject, T> colValue = col.getValue();
        doSomethingWith<SomeObject<SomeObject, T>>(rowKey, colKey, colValue);
    }
}

Bu ayrıntılı statik yazım programcı, benim önümde olur. Çoğu tür ek açıklaması, tekrarlayan satır dolgusu, önceden bildiklerimizin içeriksiz düzenlenmesidir. Ancak, statik yazmayı severim, çünkü hataları keşfetmeye gerçekten yardımcı olabilir, bu nedenle dinamik yazmayı kullanmak her zaman iyi bir cevap değildir. Yazım çıkarımı her iki dünyanın da en iyisidir: Alakasız türleri atlayabilir, ancak programımın (yazım tipinin) denetlediğinden emin olun.

Tür çıkarımı yerel değişkenler için gerçekten yararlı olsa da, açıkça belgelenmesi gereken genel API'ler için kullanılmamalıdır. Ve bazen türler kodda neler olup bittiğini anlamak için gerçekten kritiktir. Bu gibi durumlarda, yalnızca tür çıkarımına güvenmek aptallık olur.

Tür çıkarımını destekleyen birçok dil vardır. Örneğin:

  • C ++. autoAnahtar kelime tetikler çıkarım yazın. O olmadan, lambda türlerini veya kaplara girişleri hecelemek cehennem olurdu.

  • C #. varSınırlı türden bir çıkarım şeklini tetikleyen değişkenleri tanımlayabilirsiniz. Hala tür çıkarımı istediğiniz çoğu durumda yönetir. Bazı yerlerde tipi tamamen dışarıda bırakabilirsiniz (örn. Lambdalar).

  • Haskell ve ML ailesindeki herhangi bir dilde. Burada kullanılan tür çıkarımının kendine özgü lezzeti oldukça güçlü olsa da, yine de genellikle fonksiyonlar için yazım notlarını ve iki nedenden dolayı görüyorsunuz: Birincisi, dokümantasyon ve ikincisi, tür çıkarımının gerçekte beklediğiniz türleri bulduğunu kontrol eden bir kontrol. Bir tutarsızlık varsa, muhtemelen bir tür böcek var.


13
Ayrıca C # 'nın isimsiz tiplere, yani isimsiz tiplere sahip olduğuna, ancak C #' nın nominal tipte bir sistemine, yani adlara dayalı tip tipine sahip olduğuna dikkat edin. Tür çıkarımı olmadan, bu türler asla kullanılamaz!
Jörg W Mittag

10
Bence bazı örnekler biraz tartışmalı. 42'ye başlatma, otomatik olarak değişkenin bir olduğu anlamına gelmez, inthatta dahil olmak üzere herhangi bir sayısal tür olabilir char. Ayrıca neden Entrysınıf türünü yazabildiğiniz zamanlar için neden tüm türü hecelemek istediğinizi anlamıyorum ve IDE'nizin gerekli içe aktarmayı yapmasına izin verin. Tam adı açıklamanız gereken tek durum, kendi adınıza aynı isimde bir sınıfınız olduğu zamandır. Ama yine de bana kötü tasarım gibi geliyor.
Malcolm

10
@Malcolm Evet, tüm örneklerim kabul edildi. Bir noktayı açıklamaya hizmet ediyorlar. intÖrneği yazdığımda, tür çıkarımı olan birçok dilin (bence oldukça aklı başında davranışı) olduğunu düşünüyordum. Genelde , o dilde ne denirse, bir intya da Integerya çıkarırlar . Tür çıkarımının güzelliği, daima isteğe bağlı olmasıdır; ihtiyacınız varsa, yine de farklı bir tür belirleyebilirsiniz. Örnekle ilgili olarak Entry: iyi nokta, onunla değiştireceğim Map.Entry<Integer, Map<Integer, SomeObject<SomeObject, T>>>. Java, diğer ad türlerine bile sahip değil :(
amon

4
@ m3th0dman Tür, anlamak için önemliyse, o zaman açıkça açıkça belirtebilirsiniz. Tür çıkarımı her zaman isteğe bağlıdır. Fakat burada, türü colKeyhem açık hem de alakasız: biz sadece onun ikinci argümanı olarak uygun olmasını önemsiyoruz doSomethingWith. Eğer bu döngüyü tekrarlanabilen (key1, key2, value)üçlüler veren bir fonksiyona çıkarsaydım, en genel imza olur <K1, K2, V> Iterable<TableEntry<K1, K2, V>> flattenTable(Map<K1, Map<K2, V>> table). Bu işlevin içinde, gerçek tür colKey( Integer, değil K2) kesinlikle alakasızdır.
amon

4
@ m3th0dman oldukça geniş kapsamlı bir açıklama, " en " kodunun bu ya da böyle olduğu hakkında. Fıkra istatistikleri. Başlatıcıları iki kez türünü yazılı hiçbir anlamı kesinlikle yoktur: View.OnClickListener listener = new View.OnClickListener(). Programlayıcı "tembel" olsa ve hala kısalsa bile türünü biliyordunuz var listener = new View.OnClickListener(eğer mümkün olsaydı). Fazlalık Bu tür yaygındır - Burada bir guesstimate riske edecek - ve kaldırma gelmez gelecek okuyucuların düşünmeye kaynaklanıyor. Her dilin özelliği dikkatle kullanılmalı, bunu sorgulamıyorum.
Konrad Morawski

26

Kodun, yazıldığından çok daha sık okunduğu doğru. Bununla birlikte, okuma da zaman alır ve iki kod ekranını gezinmek ve bir kod ekranından daha fazlasını okumak zordur, bu nedenle en iyi yararlı bilgi / okuma-efor oranı oranını paketlemeye öncelik vermemiz gerekir. Bu genel bir UX ilkesidir: Bir seferde çok fazla bilgi bunaltıcıdır ve aslında arayüzün etkinliğini düşürür.

Ve benim deneyim genellikle , kesin türü değil (yani) önemli. Elbette bazen iç içe ifadeler: x + y * z, monkey.eat(bananas.get(i)), factory.makeCar().drive(). Bunların her biri, türü yazılmayan bir değeri değerlendiren alt ifadeler içerir. Oysa onlar tamamen açık. Türü dengesiz bırakmaktan iyidir, çünkü bağlamdan anlamak kolaydır ve bunu yazmak iyi olmaktan daha fazla zarar verir (veri akışının anlaşılması, değerli ekran ve kısa süreli hafıza alanı).

İfadeleri yarın yokmuş gibi iç içe geçirmemenin bir nedeni, çizgilerin uzaması ve değerlerin akışının belirsiz hale gelmesidir. Geçici bir değişken tanıtmak bu konuda yardımcı olur, bir emir verir ve kısmi sonuca bir isim verir. Bununla birlikte, bu yönlerden yararlanan her şey, türünün açıklanmasından da yararsızdır:

user = db.get_poster(request.post['answer'])
name = db.get_display_name(user)

O olmadığını fark eder userbir varlık nesnesi, bir tamsayı, bir dize veya başka bir şey mi? Çoğu amaç için, bir kullanıcıyı temsil ettiğini, HTTP isteğinden geldiğini ve yanıtın sağ alt köşesinde görüntülenecek adı almak için kullanıldığını bilmek yeterli değildir.

O zaman yapar olsun, yazar tipini yazmak serbesttir. Bu, sorumlu bir şekilde kullanılması gereken bir özgürlüktür, ancak aynı şey okunabilirliği artırabilecek her şey için geçerlidir (değişken ve işlev adları, biçimlendirme, API tasarımı, beyaz alan). Ve aslında, Haskell ve ML'deki ( her şeyin ekstra çaba göstermeden çıkarılabileceği) konvansiyon, yerel olmayan fonksiyon fonksiyonlarının ve uygun olduğunda yerel değişkenlerin ve fonksiyonların türlerini yazmaktır. Sadece acemiler her tipin çıkarımına izin veriyor.


2
+1 Bu kabul edilen cevap olmalıdır. Tipik çıkarımın neden harika bir fikir olduğuna bağlı.
Christian Hayter

userİşlevin kapsamını genişletmeyi deniyorsanız bunun tam bir önemi vardır çünkü bununla ne yapabileceğinizi belirler user. Bazı sağlık kontrolü (örneğin bir güvenlik açığı nedeniyle) eklemek ya da yalnızca görüntülemenin yanı sıra kullanıcı ile bir şeyler yapmanız gerektiğini unuttum, bu önemlidir. Doğru, bu tür genişleme için okumalar sadece kodu okumaktan daha az sıklıkta, aynı zamanda işimizin hayati bir parçası.
cmaster

@cmaster Her zaman kolayca bu türü kolayca öğrenebilirsiniz (çoğu IDE size söyleyecektir ve bilerek bir tür hatasına neden olan ve derleyicinin gerçek türü yazdırmasına izin veren düşük teknoloji ürünü bir çözüm vardır) Ortak davada sizi rahatsız etmez.

4

Tür çıkarımının oldukça önemli olduğunu ve herhangi bir modern dilde desteklenmesi gerektiğini düşünüyorum. Hepimiz IDE'lerde gelişiriz ve çıkarılan türü bilmek istemeniz durumunda çok yardımcı olabilirler vi. Örneğin, Java'daki ayrıntısızlık ve tören kodunu düşünün.

  Map<String,HashMap<String,String>> map = getMap();

Ama IDE'nin bana yardım edebileceğinin doğru olduğunu söyleyebilirsin, geçerli bir nokta olabilir. Ancak, bazı özellikler, örneğin çıkarımın, C # anonim türlerin yardımı olmadan orada olmazdı.

 var person = new {Name="John Smith", Age = 105};

Linq, Selectörneğin tür çıkarımı yardımı olmadan şimdi olduğu kadar iyi olmaz

  var result = list.Select(c=> new {Name = c.Name.ToUpper(), Age = c.DOB - CurrentDate});

Bu adsız tür değişkene düzgün bir şekilde çıkarılacaktır.

İade türlerinde tür çıkarımını beğenmedim, Scalaçünkü amacınız burada geçerliyse, API'nın daha akıcı şekilde kullanabilmemiz için hangi fonksiyonun döndüğü bizim için açık olmalı


Map<String,HashMap<String,String>>? Elbette, türleri kullanmıyorsanız , bunları hecelemek çok az yarar sağlar. Table<User, File, String>Yine de daha bilgilendirici ve yazarken fayda var.
MikeFHay

4

Bunun cevabının gerçekten basit olduğunu düşünüyorum: gereksiz bilgileri okumak ve yazmaktan tasarruf eder. Özellikle, eşittir işaretinin her iki tarafında bir türün bulunduğu nesne yönelimli dillerde.

Ayrıca, ne zaman kullanmanız gerektiğini veya kullanmamanız gerektiğini söyleyen - bilgilerin gereksiz olmadığı zaman.


3
Eh, teknik olarak, el ile imza atmak mümkün olduğunda bilgi her zaman gereksizdir: Aksi halde derleyici bunları alamaz! Ancak ne demek istediğinizi anlıyorum: tek bir görünümde birden fazla noktaya bir imzayı çoğalttığınızda , beyne çok fazla ihtiyaç duyarken, iyi yerleştirilmiş birkaç tip uzun bir süre aramanız gereken bilgileri verir. iyi çok açık olmayan dönüşümler.
leftaroundabout

@leftaroundabout: Programlayıcı tarafından okunduğunda gereksiz.
jmoreno

3

Birinin kodu gördüğünü varsayalım:

someBigLongGenericType variableName = someBigLongGenericType.someFactoryMethod();

Eğer someBigLongGenericTypedönüş türünden atanabilir olan someFactoryMethod, türleri kesin olarak uymuyorsa kadar olası kod okuma birisi haber olacağını ve nasıl kolayca ihbar tutarsızlık yaptım birisi kasıtlı olup olmadığını tespit edebileceği?

Bir çıkarımı sağlayarak, bir dil, bir değişkenin türü açıkça belirtildiğinde kişinin bunun için bir neden bulmaya çalışması gerektiğini kod okuyan birine önerebilir. Bu da kod okuyan kişilerin çabalarını daha iyi odaklamalarını sağlar. Buna karşılık, bir yazının belirtildiği zamanların büyük çoğunluğu, tam olarak çıkarılan ile tam olarak aynı olursa, kodu okuyan bir kişi, incelikle farklı olduğu zamanları farketmeye daha az eğilimli olabilir. .


2

Görüyorum ki zaten çok sayıda iyi cevap var. Bazıları tekrarlayacağım ama bazen sadece kendi sözlerine bir şeyler koymak istiyorsun. C ++ 'dan bazı örnekler ile yorum yapacağım çünkü en çok aşina olduğum dil bu.

Gerekli olan asla akıllıca olmaz. Diğer dil özelliklerini pratik yapmak için tür çıkarımı gereklidir. C ++ 'da unutulmaz tiplere sahip olmak mümkündür.

struct {
    double x, y;
} p0 = { 0.0, 0.0 };
// there is no name for the type of p0
auto p1 = p0;

C ++ 11 de unutulmaz olan lambda ekler.

auto sq = [](int x) {
    return x * x;
};
// there is no name for the type of sq

Yazım çıkarımı ayrıca şablonları da destekler.

template <class x_t>
auto sq(x_t const& x)
{
    return x * x;
}
// x_t is not known until it is inferred from an expression
sq(2); // x_t is int
sq(2.0); // x_t is double

Ancak sorularınız "neden programcı, kodu okuduğumda değişkenlerimin türünü anlamak isterim? Bir kimsenin sadece türün ne tür olduğunu düşünmekten daha hızlı bir şekilde türünü okuması daha hızlı değil mi?" İdi.

Tür çıkarımı fazlalığı giderir. Kod okumaya gelince, kodda yedekli bilgiye sahip olmak bazen daha hızlı ve daha kolay olabilir, ancak fazlalık yararlı bilgilerin gölgesinde kalabilir . Örneğin:

std::vector<int> v;
std::vector<int>::iterator i = v.begin();

i = v.begin()Açık bir tür bildiriminin sınırlı değerli olması için i'nin bir yineleyici olduğunu tanımlamak için bir C ++ programcısının standart kütüphanesine fazla aşina gelmez . Varlığında daha önemli olan ayrıntıları gizler (örneğin i, vektörün başlangıcına işaret eder). @Amon'un ince cevabı, önemli detayları gölgeleyen ayrıntılı bir örnek teşkil eder. Aksine, tür çıkarımı kullanılması, önemli ayrıntılara daha fazla önem verir.

std::vector<int> v;
auto i = v.begin();

Kod okumak önemli olsa da, bu yeterli değildir, bir noktada okumayı bırakmanız ve yeni kod yazmaya başlamanız gerekecektir. Koddaki artıklık, kod değiştirmeyi yavaşlatır ve zorlaştırır. Örneğin, aşağıdaki kod parçasına sahip olduğumu varsayalım:

std::vector<int> v;
std::vector<int>::iterator i = v.begin();

Kodu iki katına çıkarmak için vektörün değer türünü değiştirmem gerekiyorsa:

std::vector<double> v;
std::vector<double>::iterator i = v.begin();

Bu durumda kodu iki yerde değiştirmem gerekiyor. Orijinal kodun bulunduğu tür çıkarımına sahip kontrast:

std::vector<int> v;
auto i = v.begin();

Ve değiştirilen kod:

std::vector<double> v;
auto i = v.begin();

Şimdi sadece bir kod satırını değiştirmem gerektiğine dikkat edin. Bunu ekstrapolate edip büyük bir programa yazın ve yazım çıkarımı, yazım değişikliklerini bir editörle yapabileceğinizden çok daha hızlı bir şekilde yayınlayabilir.

Koddaki fazlalık, hata olasılığını yaratır. Kodunuz her zaman eşdeğer tutulan iki bilgi parçasına bağlı olduğunda, bir hata olasılığı vardır. Örneğin, bu açıklamada muhtemelen amaçlanmayan iki tür arasında bir tutarsızlık var:

int pi = 3.14159;

Artıklık, niyeti ayırt etmeyi zorlaştırır. Bazı durumlarda, tür çıkarımının okunması ve anlaşılması daha kolay olabilir çünkü açık tip özelliklerinden daha basittir. Kod parçasını göz önünde bulundurun:

int y = sq(x);

Durumda olduğunu sq(x)döndürür bir int, o olmadığı belli değildir ybir olan intbu dönüş türü olduğu için sq(x)ya da ifadeleri kullanan uygun çünkü y. Diğer kodu sq(x)artık döndürmeyecek şekilde değiştirirsem int, türünün ygüncellenmesi gerekip gerekmediği yalnızca bu satırdan belirsizdir . Aynı kodla fakat tip çıkarımını kullanarak kontrast:

auto y = sq(x);

Buradaki amaç açıktır, ygeri döndürdüğü ile aynı tipte olmalıdır sq(x). Kod dönüş türünü değiştirdiğinde, otomatik olarak eşleşecek değişikliklerin sq(x)türü y.

C ++ 'da yukarıdaki örneğin tip çıkarımı ile daha basit olmasının ikinci bir nedeni var, tip çıkarımı örtük tip dönüşümü sağlayamıyor. Dönüş türü sq(x)değilse int, sessiz bir şekilde derleyici ile örtülü bir dönüşüm ekleyin int. Dönüş türü, sq(x)tanımlayan bir tip karmaşık tipse operator int(), bu gizli işlev çağrısı isteğe bağlı olarak karmaşık olabilir.


C ++ 'da unutulmaz tipler hakkında oldukça iyi bir nokta. Ancak, bu tür çıkarım eklemek için bir neden daha az olduğunu düşünüyorum, dili düzeltmek için bir neden. Sunacağınız ilk durumda, programcı sadece tip çıkarımını kullanmaktan kaçınmak için bir şeye bir isim vermek zorunda kalacaktı, bu yüzden bu güçlü bir örnek değil. İkinci örnek yalnızca güçlüdür çünkü C ++ açıkça söylenebilir olan lambda tiplerinin yasaklanmasını yasaklar, kullanımı ile tip tanımlaması bile typeofdil tarafından işe yaramaz hale getirilir. Ve bu bence düzeltilmesi gereken dilin kendisinin bir açığı.
cmaster
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.