Boş olmayan diller için en iyi açıklama


225

Programcılar null hatalardan / istisnalardan şikayet ederken, birileri null olmadan ne yaptığımızı sorar.

Seçenek türlerinin serinliği hakkında temel bir fikrim var, ancak bunu en iyi ifade edebilecek bilgi veya dil becerisine sahip değilim. Aşağıdakilerin, o kişiyi işaret edebileceğimiz ortalama bir programcıya yakın bir şekilde yazılmış büyük açıklaması nedir ?

  • Referansların / işaretçilerin varsayılan olarak geçersiz olması istenmez
  • Seçenek türleri, null vakaları kontrol etmeyi kolaylaştıran stratejiler dahil nasıl çalışır?
    • kalıp eşleme ve
    • monadik anlayışlar
  • Mesaj yemek nil gibi alternatif çözüm
  • (kaçırdığım diğer yönler)

11
İşlevsel programlama veya F # için bu soruya etiket eklerseniz bazı harika yanıtlar almak zorunda kalırsınız.
Stephen Swensen

Seçenek türü ml dünyasından geldiğinden işlevsel programlama etiketi ekledim. F # (çok spesifik) olarak işaretlememeyi tercih ederim. Taksonomi yetkisine sahip birisinin belki tür veya seçenek türü etiketler eklemesi gerekir.
Roman A.Taycher

4
böyle özel etiketlere çok az ihtiyaç var, sanırım. Etiketler temel olarak insanların alakalı sorular bulmasına olanak tanır (örneğin, "hakkında çok şey bildiğim ve cevaplayabileceğim sorular" ve "işlevsel programlama" burada çok yararlıdır. Ancak "null" veya " seçenek türü "çok daha az kullanışlıdır. Çok az kişinin yanıtlayabilecekleri soruları arayan bir" seçenek türü "etiketi izlemesi olasıdır.;)
jalf

Unutmayalım ki, sıfırın temel nedenlerinden biri, bilgisayarların teoriye güçlü bir şekilde bağlı olduğu evrimleşmiştir. Null, küme teorisinin en önemli kümelerinden biridir. Bu olmadan tüm algoritmalar parçalanırdı. Örneğin, bir birleştirme sıralaması gerçekleştirin. Bu, bir listeyi birkaç kez yarıya indirmeyi içerir. Liste 7 öğe uzunluğundaysa ne olur? Önce 4 ve 3'e böldünüz. Sonra 2, 2, 2 ve 1. Sonra 1, 1, 1, 1, 1, 1, 1 ve .... null! Null'un sadece pratikte görmediğiniz bir amacı vardır. Teorik alan için daha çok var.
stevendesu

6
@steven_desu - Katılmıyorum. 'Sıfırlanabilir' dillerde boş bir listeye [] ve ayrıca bir boş liste referansına sahip olabilirsiniz. Bu soru ikisi arasındaki karışıklıkla ilgilidir.
stusmith

Yanıtlar:


434

Bence null'un istenmeyen olmasının özlü özeti, anlamsız devletlerin temsil edilemez olması gerektiğidir .

Diyelim ki bir kapı modelleniyorum. Üç durumdan birinde olabilir: açık, kapalı ancak kilidi açık ve kapalı ve kilitli. Şimdi onu çizgileri boyunca modelleyebilirim

class Door
    private bool isShut
    private bool isLocked

ve üç durumumu bu iki boole değişkenine nasıl eşleyeceğim açıktır. Ama bu yapraklar dördüncü, istenmeyen durum mevcut: isShut==false && isLocked==true. Temsilim olarak seçtiğim türler bu durumu kabul ettiğinden, sınıfın asla bu duruma girmemesini sağlamak için zihinsel çaba sarf etmeliyim (belki de açıkça bir değişmezi kodlayarak). Buna karşılık, cebirsel veri türlerine sahip bir dil veya tanımlamama izin veren işaretli numaralandırmalar kullanıyordum

type DoorState =
    | Open | ShutAndUnlocked | ShutAndLocked

o zaman tanımlayabilirim

class Door
    private DoorState state

ve daha fazla endişe yok. Tür sistemi, içinde class Doorbulunabileceği bir örnek için yalnızca üç olası durum olmasını sağlayacaktır . Bu tür sistemlerin iyi olduğu şeydir - derleme zamanında tüm hata sınıfını açıkça ortadan kaldırır.

Sorun null, her referans türünün kendi alanında genellikle istenmeyen bu fazladan durumu almasıdır. Bir stringdeğişken herhangi bir karakter dizisi olabilir, ya da bu çılgın ekstra olabilir nullbenim sorunum etki alanına eşleşmiyor değer. Bir Trianglenesnenin Pointkendileri Xve Ydeğerleri olan üç s'si vardır , ancak maalesef Points veya Trianglekendisi çalıştığım grafik alanı için anlamsız olan çılgın null değer olabilir.

Muhtemel olmayan bir değeri modellemek istediğinizde, bu değeri açıkça seçmelisiniz. İnsanları modelleme niyetimde herkesin Persona FirstNameve a olması LastName, ancak sadece bazı insanların sahip olması MiddleNameşuysa,

class Person
    private string FirstName
    private Option<string> MiddleName
    private string LastName

burada stringnull olmayan bir tür olduğu varsayılır. Sonra NullReferenceExceptionbirisinin adının uzunluğunu hesaplamaya çalışırken kurması zor bir değişmez ve beklenmedik bir şey yoktur . Tür sistemi, MiddleNamehesaplarla ilgili herhangi bir kodun olma olasılığını garanti eder None, oysa bu kodla ilgili herhangi bir kod, FirstNameorada bir değer olduğunu güvenle varsayabilir.

Örneğin, yukarıdaki türü kullanarak, bu aptalca işlevi yazabiliriz:

let TotalNumCharsInPersonsName(p:Person) =
    let middleLen = match p.MiddleName with
                    | None -> 0
                    | Some(s) -> s.Length
    p.FirstName.Length + middleLen + p.LastName.Length

endişelenmeden. Buna karşılık, dize gibi türler için null olabilecek referansları olan bir dilde,

class Person
    private string FirstName
    private string MiddleName
    private string LastName

sonunda böyle şeyler yazıyorsun

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length + p.MiddleName.Length + p.LastName.Length

gelen Person nesnesinin her şeyin null olmayan değişkeni yoksa patlar veya

let TotalNumCharsInPersonsName(p:Person) =
    (if p.FirstName=null then 0 else p.FirstName.Length)
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + (if p.LastName=null then 0 else p.LastName.Length)

ya da belki

let TotalNumCharsInPersonsName(p:Person) =
    p.FirstName.Length
    + (if p.MiddleName=null then 0 else p.MiddleName.Length)
    + p.LastName.Length

pilk / sonun orada olduğunu ancak ortada boş olabileceğini varsayarsak , ya da farklı türden istisnalar atan ya da kimin ne olduğunu bilen kontroller yaparsınız. Tüm bu çılgın uygulama seçenekleri ve ürün hakkında düşünecek şeyler çünkü istemediğiniz veya ihtiyacınız olmayan bu aptal temsili-değer var.

Null tipik olarak gereksiz karmaşıklık ekler. Karmaşıklık tüm yazılımların düşmanıdır ve makul olduğunda karmaşıklığı azaltmaya çalışmalısınız.

(Not bile bu basit örneklerden daha karmaşık olduğunu iyi ki. Bir Bile FirstNameolamaz null, bir stringtemsil edebilir ""da muhtemelen biz modele niyetinde olduklarını bir kişinin adı değil (boş dize). Bunun gibi, hatta olmayan ile nullable string'lerde yine de "anlamsız değerleri temsil ediyor" olmamız söz konusu olabilir. Yine, bununla ya çalışma zamanında değişmezler ve koşullu kod aracılığıyla ya da tür sistemini kullanarak (örneğin bir NonEmptyStringtüre sahip olmak ) savaşmayı seçebilirsiniz . ikincisi ( "iyi" tipleri ortak operasyonlar kümesi üzerinde genellikle "kapalı", ve mesela belki sakıncalı olduğu NonEmptyStringüzerinde kapalı değil.SubString(0,0)), ancak tasarım alanında daha fazla nokta gösterir. Günün sonunda, herhangi bir tip sistemde, kurtulmak için çok iyi olacak bazı karmaşıklıklar ve kurtulmak için kendinden daha zor olan diğer karmaşıklıklar vardır. Bu konunun anahtarı, hemen hemen her tür sistemde, "varsayılan olarak boş bırakılabilir başvurular" dan "varsayılan olarak boş bırakılamayan referanslara" geçişin, neredeyse her zaman, yazı sistemini karmaşıklıkla mücadele konusunda daha iyi hale getiren basit bir değişiklik olması ve belirli hata türlerini ve anlamsız durumları ortadan kaldırmak. Bu yüzden birçok dilin bu hatayı tekrar tekrar tekrarlaması oldukça çılgınca.)


31
Re: isimler - Gerçekten. Ve belki de açık asılı olan ancak kilit sürgüleri dışarı çıkarak kapının kapanmasını önleyen bir kapının modellenmesine önem veriyorsunuz. Dünyada çok fazla karmaşıklık var. Anahtar, yazılımınızda "dünya devletleri" ve "program durumları" arasındaki eşlemeyi uygularken daha fazla karmaşıklık eklemek değildir .
Brian

59
Ne, kapıları hiç kilitlemedin mi?
Joshua

58
İnsanların neden belirli bir alanın anlambilimi üzerinde çalıştıklarını anlamıyorum. Brian, kusurları özlü ve basit bir şekilde null ile temsil etti, evet, örneğinde herkesin adı ve soyadı olduğunu söyleyerek sorun alanını basitleştirdi. Soru bir 'T', Brian'a cevaplandı - eğer hiç Boston'daysanız, burada yaptığınız tüm kayıtlar için size bir bira borçluyum!
akaphenom

67
@akaphenom: teşekkürler, ama tüm insanların bira içmediğini unutmayın (ben sigara içmeyen biriyim). Ama minnettarlığı iletmek için sadece basitleştirilmiş bir dünya modeli kullandığınız için teşekkür ederim, bu yüzden dünya modelinizin kusurlu varsayımları hakkında daha fazla tartışmayacağım. : P (Gerçek dünyada çok karmaşık!))
Brian

4
Garip bir şekilde, bu dünyada 3-devlet kapısı var! Bazı otellerde tuvalet kapısı olarak kullanılırlar. Bir basma düğmesi içeriden bir anahtar görevi görür ve kapıyı dışarıdan kilitler. Mandal cıvatası hareket ettiği anda otomatik olarak kilidi açılır.
comonad

65

Seçenek türleri hakkında güzel olan şey, isteğe bağlı olmaları değildir. O olmasıdır tüm diğer türleri değildir .

Bazen bir tür "boş" durumu temsil edebilmemiz gerekir. Bazen bir "değer yok" seçeneğini ve bir değişkenin alabileceği diğer olası değerleri temsil etmeliyiz. Yani buna izin vermeyen bir dil bunun biraz sakatlanmasına neden olacak.

Ancak çoğu zaman buna ihtiyacımız yoktur ve böyle bir "boş" duruma izin vermek sadece belirsizlik ve karışıklığa yol açar: .NET'te bir referans türü değişkenine her eriştiğimde, bunun boş olabileceğini düşünmem gerekir .

Çoğu zaman, aslında hiçbir zaman boş olmaz, çünkü programcı kodu asla gerçekleşmeyecek şekilde yapılandırır. Ancak derleyici bunu doğrulayamıyor ve her gördüğünüzde kendinize "bu boş olabilir mi? Burada boş olup olmadığını kontrol etmem gerekiyor mu?"

İdeal olarak, null değerinin mantıklı olmadığı birçok durumda, buna izin verilmemelidir .

Neredeyse her şeyin boş olabileceği .NET'te başarmak zor. Eğer% 100 disiplinli ve tutarlı ve açıkça ve boş ya da paranoyak ve çek olmak zorunda değildir nelerin belgeledi edilecek aradığınız kod yazar güvenmek zorunda herşey .

Ancak, türler varsayılan olarak geçersiz olamazsa , boş olup olmadıklarını kontrol etmenize gerek yoktur. Asla boş olamazlar, çünkü derleyici / tip denetleyicisi bunu sizin için zorlar.

Ve sonra boş bir durumla başa çıkmamız gereken nadir durumlar için bir arka kapıya ihtiyacımız var. Sonra bir "seçenek" tipi kullanılabilir. Sonra, "değersiz" davayı temsil edebileceğimiz konusunda bilinçli bir karar verdiğimiz durumlarda null'a izin veririz ve her iki durumda da değerin asla boş olmayacağını biliyoruz.

Diğerlerinin de belirttiği gibi, örneğin C # veya Java'da null iki şeyden biri anlamına gelebilir:

  1. değişken başlatılmaz. Bu, ideal olarak, asla gerçekleşmemelidir. Bir değişken başlatılmadıkça mevcut olmamalıdır .
  2. Değişken bazı "isteğe bağlı" verileri içerir: davayı temsil edebilmek için ihtiyacı hiçbir veri yoktur . Bu bazen gereklidir. Belki de listede bir nesne bulmaya çalışıyorsunuz ve orada olup olmadığını önceden bilmiyorsunuz. O zaman "hiçbir nesne bulunamadığını" temsil edebilmeliyiz.

İkinci anlam korunmalıdır, ancak ilki tamamen ortadan kaldırılmalıdır. Ve ikinci anlam bile varsayılan olmamalıdır. Bu, ihtiyaç duyduğumuzda ve ihtiyaç duyduğumuzda seçebileceğimiz bir şey . Ancak isteğe bağlı olmak için bir şeye ihtiyacımız olmadığında, tür denetleyicisinin asla boş kalmayacağını garanti etmesini istiyoruz .


İkinci olarak, bu tür değişkenlere önce hükümsüzlüğü kontrol etmeden erişmeye çalışırsak derleyicinin bizi uyarmasını (durdurması?) İstiyoruz. İşte yaklaşmakta olan null / null olmayan C # özelliği (nihayet!) Hakkında
Ohad Schneider

44

Şimdiye kadarki tüm cevaplar neden nullkötü bir şey olduğuna ve bir dilin belirli değerlerin asla boş olmayacağını garanti edebiliyorsa nasıl kullanışlı olduğuna odaklanır .

Daha sonra, tüm değerler için null olmama durumunu zorlarsanız, her zaman tanımlanmış bir değeri olmayan türleri temsil etmek Optionveya Maybebunları temsil etmek için yapılabilecek oldukça temiz bir fikir olacağını öne sürerler . Haskell tarafından benimsenen yaklaşım budur.

Hepsi iyi şeyler! Ancak, aynı etkiyi elde etmek için açıkça geçersiz / boş olmayan türlerin kullanılmasını engellemez. Öyleyse neden Option hala iyi bir şey? (Tüm sonra, Scala null değerleri destekler vardır ama desteklerin Java kütüphaneleri ile çalışmak, böylece kadar) Optionsde.

S. Öyleyse null'ları bir dilden tamamen kaldırabilmenin ötesinde faydaları nelerdir?

A. Kompozisyon

Geçersiz koddan saf bir çeviri yaparsanız

def fullNameLength(p:Person) = {
  val middleLen =
    if (null == p.middleName)
      p.middleName.length
    else
      0
  p.firstName.length + middleLen + p.lastName.length
}

seçeneğe duyarlı koda

def fullNameLength(p:Person) = {
  val middleLen = p.middleName match {
    case Some(x) => x.length
    case _ => 0
  }
  p.firstName.length + middleLen + p.lastName.length
}

çok fazla fark yok! Ama aynı zamanda Seçenekler'i kullanmanın da korkunç bir yolu ... Bu yaklaşım çok daha temiz:

def fullNameLength(p:Person) = {
  val middleLen = p.middleName map {_.length} getOrElse 0
  p.firstName.length + middleLen + p.lastName.length
}

Ya da:

def fullNameLength(p:Person) =       
  p.firstName.length +
  p.middleName.map{length}.getOrElse(0) +
  p.lastName.length

Seçenekler Listesi ile uğraşmaya başladığınızda, daha da iyi olur. Listenin peoplekendisinin isteğe bağlı olduğunu düşünün :

people flatMap(_ find (_.firstName == "joe")) map (fullNameLength)

Bu nasıl çalışıyor?

//convert an Option[List[Person]] to an Option[S]
//where the function f takes a List[Person] and returns an S
people map f

//find a person named "Joe" in a List[Person].
//returns Some[Person], or None if "Joe" isn't in the list
validPeopleList find (_.firstName == "joe")

//returns None if people is None
//Some(None) if people is valid but doesn't contain Joe
//Some[Some[Person]] if Joe is found
people map (_ find (_.firstName == "joe")) 

//flatten it to return None if people is None or Joe isn't found
//Some[Person] if Joe is found
people flatMap (_ find (_.firstName == "joe")) 

//return Some(length) if the list isn't None and Joe is found
//otherwise return None
people flatMap (_ find (_.firstName == "joe")) map (fullNameLength)

Boş kontrolleri (ve hatta elvis?: Operatörlerini) içeren ilgili kod acı verici bir şekilde uzun olacaktır. Buradaki gerçek hile, null değerlerin asla elde edemeyeceği şekilde Seçeneklerin ve koleksiyonların iç içe anlaşılmasını sağlayan flatMap işlemidir.


8
+1, bu vurgulamak için iyi bir nokta. Bir zeyilname: Haskell-topraklarında, monadlar için "bağlama" operatörü flatMapolarak adlandırılacaktı (>>=). Doğru, Haskeller flatMapşeylere o kadar çok ping yapıyor ki dilimizin logosuna koyduk.
CA McCann

1
+1 Umarım bir ifadesi Option<T>asla, asla boş olmaz. Ne yazık ki, Scala uhh, hala Java ile bağlantılı :-) (Öte yandan, Scala Java ile güzel oynamadıysa, kim kullanırdı? Oo)

Yapılacak kadar kolay: 'List (null) .headOption'. Bunun 'Yok' dönüş değerinden çok farklı bir şey ifade ettiğini unutmayın
Kevin Wright

4
Kompozisyon hakkında söylediklerini gerçekten sevdiğim için sana ödül verdim, diğer insanların bahsetmediği gibi.
Roman A.Taycher

Harika örneklerle mükemmel cevap!
thSoft

38

İnsanlar eksik gibi göründüğü için: nullbelirsiz.

Alice'in doğum tarihi null. Bu ne demek?

Bob'un ölüm tarihi null. Bu ne anlama geliyor?

"Makul" bir yorum, Alice'in doğum tarihinin var olduğu, ancak bilinmediği, ancak Bob'un ölüm tarihinin bulunmadığı (Bob hala hayatta) olabilir. Ama neden farklı cevaplar aldık?


Başka bir sorun: nullbir uç durum.

  • Öyle null = nullmi?
  • Öyle nan = nanmi?
  • Öyle inf = infmi?
  • Öyle +0 = -0mi?
  • Öyle +0/0 = -0/0mi?

Cevaplar genellikle sırasıyla "evet", "hayır", "evet", "evet", "hayır", "evet" dir. Çılgın "matematikçiler" NaN "hükümsüzlüğü" derler ve kendisiyle eşit olduğunu söylerler. SQL, null değerlerini hiçbir şeye eşit değil olarak değerlendirir (bu nedenle NaN'ler gibi davranırlar). Biri, ± ∞, ± 0 ve NaN'leri aynı veritabanı sütununda depolamaya çalıştığınızda ne olacağını merak eder ( yarısı "negatif" olan 2 53 NaN vardır).

Daha da kötüsü, veritabanları NULL davranışı açısından farklılık gösterir ve çoğu tutarlı değildir ( genel bakış için bkz. SQLite'de NULL İşleme ). Oldukça korkunç.


Ve şimdi zorunlu hikaye için:

Son zamanlarda beş sütunlu bir (sqlite3) veritabanı tablosu tasarladım a NOT NULL, b, id_a, id_b NOT NULL, timestamp. Oldukça keyfi uygulamalar için genel bir sorunu çözmek için tasarlanmış genel bir şema olduğundan, iki benzersiz kısıtlama vardır:

UNIQUE(a, b, id_a)
UNIQUE(a, b, id_b)

id_ayalnızca mevcut bir uygulama tasarımıyla uyumluluk için var (kısmen daha iyi bir çözüm bulamadım) ve yeni uygulamada kullanılmıyor. NULL'ın SQL'de çalışma şekli nedeniyle , ilk benzersizlik kısıtlamasını ekleyebilir (1, 2, NULL, 3, t)ve (1, 2, NULL, 4, t)ihlal edemem (çünkü (1, 2, NULL) != (1, 2, NULL)).

Bu, özellikle NULL'ın çoğu veritabanında benzersiz bir kısıtlama ile nasıl çalıştığı nedeniyle çalışır (muhtemelen "gerçek dünya" durumlarını modellemek daha kolaydır, örneğin hiçbir insan aynı Sosyal Güvenlik Numarasına sahip olamaz, ancak tüm insanların bir tane yok.


FWIW, ilk olarak tanımlanmamış davranışı çağırmadan C ++ başvuruları null değerine "işaret edemez" ve başlatılmamış başvuru üye değişkenlerine sahip bir sınıf oluşturmak mümkün değildir (bir istisna atılırsa, inşaat başarısız olur).

Sidenote: Bazen, örneğin varsayımsal bir iOS'ta, birbirini dışlayan işaretçiler (örneğin, bunlardan yalnızca biri BOş olmayabilir) isteyebilirsiniz type DialogState = NotShown | ShowingActionSheet UIActionSheet | ShowingAlertView UIAlertView | Dismissed. Bunun yerine, böyle şeyler yapmak zorunda kaldım assert((bool)actionSheet + (bool)alertView == 1).


Gerçek matematikçiler "NaN" kavramını kullanmazlar, emin olabilirsiniz.
Noldorin

@Noldorin: Kullanıyorlar ama "belirsiz form" terimini kullanıyorlar.
IJ Kennedy

@IJKennedy: Bu çok iyi bildiğim farklı bir kolej, teşekkür ederim. Bazı 'NaN'ler belirsiz formu temsil edebilir, ancak FPA sembolik akıl yürütmediğinden, belirsiz formla eşitlemek oldukça yanıltıcıdır!
Noldorin

Sorun nedir assert(actionSheet ^ alertView)? Veya XOR diliniz bools mu?
kedi

16

Referanslara / göstericilere sahip olmanın istenmeyen yönü varsayılan olarak geçersizdir.

Bunun null'larla ilgili ana sorun olduğunu düşünmüyorum, null'larla ilgili ana sorun iki anlama gelebilmeleridir:

  1. Referans / işaretçi başlatılmadı: buradaki sorun genel olarak değişebilirlikle aynı. Birincisi, kodunuzu analiz etmeyi zorlaştırır.
  2. Null olan değişken aslında bir şey ifade eder: Seçenek türlerinin aslında resmileştirdiği durum budur.

Seçenek türlerini destekleyen diller, genellikle başlatılmamış değişkenlerin kullanımını da yasaklar veya engeller.

Örüntü eşleme gibi boş durumların kontrol edilmesini kolaylaştıran stratejiler dahil seçenek türleri nasıl çalışır?

Etkili olabilmek için Seçenek türlerinin doğrudan dilde desteklenmesi gerekir. Aksi takdirde, simüle etmek için birçok kazan plakası kodu gerekir. Örüntü eşleme ve tür çıkarımı, Seçenek türlerinin kolayca çalışılmasını sağlayan iki anahtar dil özelliğidir. Örneğin:

F # 'da:

//first we create the option list, and then filter out all None Option types and 
//map all Some Option types to their values.  See how type-inference shines.
let optionList = [Some(1); Some(2); None; Some(3); None]
optionList |> List.choose id //evaluates to [1;2;3]

//here is a simple pattern-matching example
//which prints "1;2;None;3;None;".
//notice how value is extracted from op during the match
optionList 
|> List.iter (function Some(value) -> printf "%i;" value | None -> printf "None;")

Bununla birlikte, Seçenek türleri için doğrudan desteklenmeyen Java gibi bir dilde, şöyle bir şeyimiz olurdu:

//here we perform the same filter/map operation as in the F# example.
List<Option<Integer>> optionList = Arrays.asList(new Some<Integer>(1),new Some<Integer>(2),new None<Integer>(),new Some<Integer>(3),new None<Integer>());
List<Integer> filteredList = new ArrayList<Integer>();
for(Option<Integer> op : list)
    if(op instanceof Some)
        filteredList.add(((Some<Integer>)op).getValue());

Mesaj yemek nil gibi alternatif çözüm

Objective-C'nin "nil eating mesajı" null kontrolünün baş ağrısını hafifletmeye çalışmak kadar bir çözüm değildir. Temel olarak, null nesnede bir yöntemi çağırmaya çalışırken bir çalışma zamanı istisnası atmak yerine, ifade null değerini değerlendirir. İnançsızlığı askıya almak, sanki her bir örnekleme yöntemi ile başlar if (this == null) return null;. Ama sonra bilgi kaybı var: Yöntemin geçerli dönüş değeri olduğu veya nesnenin aslında boş olduğu için null döndürüp döndürmediğini bilmiyorsunuz. Bu, istisna yutma gibi bir şeydir ve daha önce belirtilen boş değerlerle ilgili herhangi bir ilerleme kaydetmez.


Bu bir evcil hayvan huşu ama c # neredeyse c benzeri bir dildir.
Roman A.Taycher

4
Burada Java için gidiyordum, çünkü C # muhtemelen daha iyi bir çözüm olurdu ... ama ben sizin huzuru takdir ediyorum, insanlar gerçekten ne demek "c-ilham sözdizimi ile bir dil". Devam ettim ve "c-like" ifadesinin yerini aldım.
Stephen Swensen

Linq ile, doğru. Ben c # düşünüyordum ve bunu fark etmedi.
Roman A.Taycher

1
Evet c çoğunlukla sözdizimi esinlenerek, ama ben de python / ruby ​​gibi zorunlu programlama dillerini, c gibi işlevsel programcılar tarafından c-benzeri olarak adlandırılan sözdizimi gibi çok az duydum.
Roman A.Taycher

11

Meclis bize el değmemiş işaretçiler olarak da bilinen adresler getirdi. C bunları doğrudan yazılan işaretçiler olarak eşleştirdi, ancak Algol'un null değerini, yazılan tüm işaretçilerle uyumlu benzersiz bir işaretçi değeri olarak tanıttı. C'deki null ile ilgili en büyük sorun, her işaretçi boş olabileceğinden, bir işaretçiyi hiçbir zaman manuel kontrol olmadan güvenli bir şekilde kullanamayacağıdır.

Daha yüksek seviyeli dillerde, null değerine sahip olmak gariptir çünkü gerçekten iki farklı görüş taşır:

  • Bir şeyin tanımsız olduğunu söylemek .
  • Bir şeyin isteğe bağlı olduğunu söylemek .

Tanımlanmamış değişkenlere sahip olmak neredeyse işe yaramaz ve gerçekleştiklerinde tanımsız davranışa neden olur. Herkesin, tanımsız şeylere sahip olmanın her ne pahasına olursa olsun önlenmesi gerektiğini kabul edeceğini düşünüyorum.

İkinci durum opsiyonalitedir ve en iyi şekilde açıkça, örneğin bir seçenek türüyle sağlanır .


Diyelim ki bir nakliye şirketindeyiz ve sürücülerimiz için bir program oluşturmamıza yardımcı olacak bir uygulama oluşturmamız gerekiyor. Her sürücü için aşağıdaki gibi birkaç bilgi saklarız: sahip oldukları sürücü ehliyetleri ve acil durumlarda aranacak telefon numaraları.

C'de olabilir:

struct PhoneNumber { ... };
struct MotorbikeLicence { ... };
struct CarLicence { ... };
struct TruckLicence { ... };

struct Driver {
  char name[32]; /* Null terminated */
  struct PhoneNumber * emergency_phone_number;
  struct MotorbikeLicence * motorbike_licence;
  struct CarLicence * car_licence;
  struct TruckLicence * truck_licence;
};

Gözlemlediğiniz gibi, sürücüler listemiz üzerindeki herhangi bir işlemde boş göstergeleri kontrol etmek zorundayız. Derleyici size yardımcı olmaz, programın güvenliği omuzlarınıza dayanır.

OCaml'de aynı kod şöyle görünür:

type phone_number = { ... }
type motorbike_licence = { ... }
type car_licence = { ... }
type truck_licence = { ... }

type driver = {
  name: string;
  emergency_phone_number: phone_number option;
  motorbike_licence: motorbike_licence option;
  car_licence: car_licence option;
  truck_licence: truck_licence option;
}

Şimdi tüm sürücülerin isimlerini kamyon lisans numaralarıyla birlikte yazdırmak istediğimizi varsayalım.

C dilinde:

#include <stdio.h>

void print_driver_with_truck_licence_number(struct Driver * driver) {
  /* Check may be redundant but better be safe than sorry */
  if (driver != NULL) {
    printf("driver %s has ", driver->name);
    if (driver->truck_licence != NULL) {
      printf("truck licence %04d-%04d-%08d\n",
        driver->truck_licence->area_code
        driver->truck_licence->year
        driver->truck_licence->num_in_year);
    } else {
      printf("no truck licence\n");
    }
  }
}

void print_drivers_with_truck_licence_numbers(struct Driver ** drivers, int nb) {
  if (drivers != NULL && nb >= 0) {
    int i;
    for (i = 0; i < nb; ++i) {
      struct Driver * driver = drivers[i];
      if (driver) {
        print_driver_with_truck_licence_number(driver);
      } else {
        /* Huh ? We got a null inside the array, meaning it probably got
           corrupt somehow, what do we do ? Ignore ? Assert ? */
      }
    }
  } else {
    /* Caller provided us with erroneous input, what do we do ?
       Ignore ? Assert ? */
  }
}

OCaml'de şöyle olur:

open Printf

(* Here we are guaranteed to have a driver instance *)
let print_driver_with_truck_licence_number driver =
  printf "driver %s has " driver.name;
  match driver.truck_licence with
    | None ->
        printf "no truck licence\n"
    | Some licence ->
        (* Here we are guaranteed to have a licence *)
        printf "truck licence %04d-%04d-%08d\n"
          licence.area_code
          licence.year
          licence.num_in_year

(* Here we are guaranteed to have a valid list of drivers *)
let print_drivers_with_truck_licence_numbers drivers =
  List.iter print_driver_with_truck_licence_number drivers

Bu önemsiz örnekte görebileceğiniz gibi, güvenli versiyonda karmaşık bir şey yok:

  • Terser.
  • Çok daha iyi garantiler alırsınız ve hiç null kontrol gerekmez.
  • Derleyici seçeneği doğru şekilde ele almanızı sağlamıştır

C ise sadece bir boş kontrol ve patlama unutmuş olabilir ...

Not: Bu kod örnekleri derlenmediği yerlerde, ancak umarım fikirleri alırsınız.


Ben hiç denemedim ama en.wikipedia.org/wiki/Cyclone_%28programming_language%29 c için null olmayan işaretçiler izin iddia ediyor.
Roman A.Taycher

1
İlk dava ile kimsenin ilgilenmediğine dair ifadenize katılmıyorum. Birçok kişi, özellikle de işlevsel dil topluluklarında olanlar, bununla son derece ilgilenir ve başlatılmamış değişkenlerin kullanımını cesaretlendirmez veya tamamen yasaklar.
Stephen Swensen

NULLBazı Algol dili için "hiçbir şeye işaret etmeyecek referans" icat edildiğine inanıyorum (Wikipedia kabul ediyor, bkz. En.wikipedia.org/wiki/Null_pointer#Null_pointer ). Ancak elbette, montaj programcıları işaretçileri geçersiz bir adrese başlatmış olabilirler (read: Null = 0).

1
@Stephen: Muhtemelen aynı şeyi kastediyorduk. Bana göre, başlatılmamış şeylerin kullanımını caydırıyor ya da yasaklıyorlar, çünkü onlarla aklı başında ya da yararlı bir şey yapamadığımız için tanımlanmamış şeyleri tartışmanın bir anlamı yok. Hiç bir ilgisi olmazdı.
bltxd

2
@tc olarak. diyor ki, null'un montajla hiçbir ilgisi yoktur. Montajda, türler genellikle geçersizdir . Genel amaçlı bir kayıt defterine yüklenen bir değer sıfır olabilir veya sıfır olmayan bir tamsayı olabilir. Ama asla boş olamaz. Bir kayda bir bellek adresi yükleseniz bile, en yaygın mimarilerde "boş gösterici" nin ayrı bir temsili yoktur. Bu, C gibi daha üst düzey dillerde tanıtılan bir kavram.
jalf

5

Microsoft Research'ün bir intersting projesi var.

spec #

Null olmayan tipte bir C # uzantısıdır ve nesnelerinizin null olmamasına karşı kontrol etmek için bazı mekanizmalar olsa da, IMHO, tasarımı sözleşme ilkesine göre uygulamak, null referansların neden olduğu birçok sıkıntılı durum için daha uygun ve daha yararlı olabilir.


4

.NET geçmişinden geliyor, her zaman null bir noktaya sahip olduğunu düşündüm, yararlı. Yapılar ve onlarla birlikte çalışmanın ne kadar kolay olduğunu anlayana kadar çok sayıda kaynak kodundan kaçındı. Tony Hoare , 2009 yılında QCon Londra'da konuşan null referansı icat ettiği için özür diledi . Ona alıntı yapmak için:

Buna milyar dolarlık hatam diyorum. 1965'teki sıfır referansının buluşuydu. O zaman, nesne yönelimli bir dilde (ALGOL W) referanslar için ilk kapsamlı tip sistemini tasarlıyordum. Amacım, derleyici tarafından otomatik olarak yapılan kontrol ile tüm referans kullanımlarının kesinlikle güvenli olmasını sağlamaktı. Ama null referansı koyma cazibesine karşı koyamadım, çünkü uygulanması çok kolaydı. Bu, son kırk yılda muhtemelen milyar dolarlık ağrı ve hasara neden olan sayısız hataya, güvenlik açığına ve sistem çökmesine neden oldu. Son yıllarda, referansları kontrol etmek ve boş kalmama riski varsa uyarılar vermek için Microsoft'taki PREfix ve PREfast gibi bir dizi program analizörü kullanılmıştır. Spec # gibi daha yeni programlama dilleri, boş olmayan başvurular için bildirimler getirmiştir. 1965'te reddettiğim çözüm bu.

Bu soruya programcılarda da bakın



1

Her zaman Null'a (ya da Nil'e) bir değerin yokluğu olarak baktım .

Bazen bunu istiyorsun, bazen istemiyorsun. Çalıştığınız alan adına bağlıdır. Devamsızlık anlamlıysa: ikinci ad yoksa, başvurunuz buna göre hareket edebilir. Öte yandan null değeri orada olmamalıdır: İlk adı null, o zaman geliştirici atasözü 2 am telefon görüşmesi alır.

Ayrıca kod aşırı ve null denetimleri ile aşırı karmaşık gördüm. Benim için bu iki şeyden biri anlamına gelir:
a) uygulama ağacında daha yüksek bir hata
b) kötü / eksik tasarım

Olumlu tarafı - Null, muhtemelen bir şeyin eksik olup olmadığını kontrol etmek için daha yararlı kavramlardan biridir ve null kavramı olmayan diller, veri doğrulaması yapma zamanı geldiğinde aşırı karmaşıklığa neden olur. Bu durumda, yeni bir değişken başlatılmazsa, söz konusu diller genellikle değişkenleri boş bir dizeye, 0 veya boş bir koleksiyona ayarlar. Ancak, boş bir dize veya 0 veya boş bir koleksiyon uygulamanız için geçerli değerler ise - o zaman bir sorununuz var demektir.

Bazen bu, alanların başlatılmamış bir durumu temsil etmesi için özel / garip değerler icat ederek atlattı. Ancak, özel değer iyi niyetli bir kullanıcı tarafından girildiğinde ne olur? Ve bunun veri doğrulama rutinlerini yapacağı karmaşaya girmeyelim. Dil null konsepti destekleseydi tüm endişeler ortadan kalkacaktır.


Merhaba @Jon, Seni burada takip etmek biraz zor. Sonunda "özel / garip" değerlerle muhtemelen Javascript'in 'tanımsız' veya IEEE'nin 'NaN' gibi bir şey demek olduğunu fark ettim. Ancak bunun yanında OP'nin sorduğu soruların hiçbirini gerçekten ele almıyorsunuz. Ve "Null muhtemelen bir şeyin olup olmadığını kontrol etmek için en yararlı kavramdır" ifadesi neredeyse kesinlikle yanlıştır. Seçenek türleri, null'a saygın, tür güvenli bir alternatiftir.
Stephen Swensen

@Stephen - Aslında mesajımı geriye bakarak, sanırım 2. yarının tamamı henüz sorulmamış bir soruya taşınmalıdır. Ama yine de null diyorum ki bir şey olup olmadığını kontrol etmek için çok faydalı.
Jon

0

Vektör dilleri bazen boş kalmamaktan kurtulabilir.

Boş vektör bu durumda yazılan bir null işlevi görür.


Ben neden bahsettiğinizi anlıyorum ama bazı örnekler verebilir misiniz? Özellikle olası bir boş değere birden fazla işlev uygulamak?
Roman A.Taycher

Boş bir vektöre bir vektör dönüşümünün uygulanması başka bir boş vektörle sonuçlanır. FYI, SQL çoğunlukla bir vektör dilidir.
Joshua

1
Tamam, açıklığa kavuşsam iyi olur. SQL, satırlar için bir vektör dilidir ve sütunlar için bir değer dilidir.
Joshua
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.