Bagaimana saya harus menyimpan nilai "tidak diketahui" dan "hilang" dalam sebuah variabel, sementara masih mempertahankan perbedaan antara "tidak diketahui" dan "hilang"?


57

Anggap ini pertanyaan "akademis". Saya telah bertanya-tanya tentang menghindari NULL dari waktu ke waktu dan ini adalah contoh di mana saya tidak dapat menemukan solusi yang memuaskan.


Mari kita asumsikan saya menyimpan pengukuran di mana kadang-kadang pengukuran diketahui tidak mungkin (atau hilang). Saya ingin menyimpan nilai "kosong" dalam variabel sambil menghindari NULL. Lain kali nilainya tidak diketahui. Jadi, memiliki pengukuran untuk kerangka waktu tertentu, kueri tentang pengukuran dalam periode waktu itu dapat mengembalikan 3 jenis respons:

  • Pengukuran aktual pada waktu itu (misalnya, semua nilai numerik termasuk 0)
  • Nilai "hilang" / "kosong" (yaitu, pengukuran dilakukan, dan nilainya diketahui kosong pada saat itu).
  • Nilai yang tidak diketahui (yaitu, tidak ada pengukuran yang dilakukan pada saat itu. Bisa kosong, tetapi bisa juga nilai lainnya).

Klarifikasi Penting:

Dengan asumsi Anda memiliki fungsi get_measurement()mengembalikan salah satu dari "kosong", "tidak dikenal" dan nilai tipe "integer". Memiliki nilai numerik menyiratkan bahwa operasi tertentu dapat dilakukan pada nilai kembali (perkalian, pembagian, ...) tetapi menggunakan operasi tersebut pada NULLs akan merusak aplikasi jika tidak tertangkap.

Saya ingin dapat menulis kode, menghindari cek NULL, misalnya (pseudocode):

>>> value = get_measurement()  # returns `2`
>>> print(value * 2)
4

>>> value = get_measurement()  # returns `Empty()`
>>> print(value * 2)
Empty()

>>> value = get_measurement()  # returns `Unknown()`
>>> print(value * 2)
Unknown()

Perhatikan bahwa tidak ada printpernyataan yang menyebabkan pengecualian (karena tidak ada NULL yang digunakan). Jadi nilai kosong & tidak dikenal akan menyebar seperlunya dan memeriksa apakah nilai sebenarnya "tidak dikenal" atau "kosong" dapat ditunda hingga benar-benar diperlukan (seperti menyimpan / membuat serialisasi nilai di suatu tempat).


Catatan Sampingan: Alasan saya ingin menghindari NULLs, terutama adalah pemikat otak. Jika saya ingin menyelesaikan pekerjaan saya tidak menentang menggunakan NULLs, tetapi saya menemukan bahwa menghindari mereka dapat membuat kode jauh lebih kuat dalam beberapa kasus.


19
Mengapa Anda ingin membedakan "pengukuran dilakukan tetapi nilai kosong" vs. "tidak ada pengukuran"? Sebenarnya, apa arti "pengukuran dilakukan tetapi nilai kosong"? Apakah sensor gagal menghasilkan nilai yang valid? Dalam hal itu, bagaimana bedanya dengan "tidak dikenal"? Anda tidak akan dapat kembali ke masa lalu dan mendapatkan nilai yang benar.
DaveG

3
@DaveG Asumsikan mengambil jumlah CPU di server. Jika server dimatikan, atau telah dihapus, nilai itu tidak ada. Ini akan menjadi pengukuran yang tidak masuk akal (mungkin "hilang" / "kosong" bukan istilah terbaik). Tetapi nilainya "diketahui" tidak masuk akal. Jika server ada, tetapi proses pengambilan nilai macet, mengukurnya valid, tetapi gagal menghasilkan nilai "tidak dikenal".
exhuma

2
@exhuma saya akan menggambarkannya sebagai "tidak berlaku", kalau begitu.
Vincent

6
Karena penasaran, pengukuran seperti apa yang Anda lakukan di mana "kosong" tidak hanya sama dengan nol skala apa pun? "Tidak dikenal" / "hilang" Saya bisa melihat yang berguna misalnya jika sensor tidak terhubung atau jika output mentah sensor adalah sampah karena satu dan lain alasan, tetapi "kosong" dalam setiap kasus yang dapat saya pikirkan dapat lebih konsisten diwakili oleh 0,, []atau {}(skalar 0, daftar kosong, dan peta kosong, masing-masing). Juga, bahwa nilai "hilang" / "tidak diketahui" pada dasarnya adalah tepat untuk apa null- ini menyatakan bahwa mungkin ada objek di sana, tetapi tidak ada.
Nic Hartley

7
Solusi apa pun yang Anda gunakan untuk ini, pastikan untuk bertanya pada diri sendiri apakah itu mengalami masalah yang sama dengan yang membuat Anda ingin menghilangkan NULL di tempat pertama.
Ray

Jawaban:


85

Cara umum untuk melakukan ini, setidaknya dengan bahasa fungsional adalah dengan menggunakan serikat yang didiskriminasi. Ini kemudian nilai yang merupakan salah satu int yang valid, nilai yang menunjukkan "hilang" atau nilai yang menunjukkan "tidak diketahui". Dalam F #, itu mungkin terlihat seperti:

type Measurement =
    | Reading of value : int
    | Missing
    | Unknown of value : RawData

Sebuah Measurementnilai maka akan menjadi Reading, dengan nilai int, atau Missing, atau Unknowndengan data mentah sebagai value(jika diperlukan).

Namun, jika Anda tidak menggunakan bahasa yang mendukung serikat yang didiskriminasi, atau yang setara, pola ini tidak akan banyak berguna bagi Anda. Jadi di sana, Anda bisa mis menggunakan kelas dengan bidang enum yang menunjukkan yang mana dari ketiganya yang berisi data yang benar.


7
Anda dapat melakukan penjumlahan jenis dalam bahasa OO tetapi ada sedikit pelat ketel yang cukup untuk membuatnya bekerja stackoverflow.com/questions/3151702/…
jk.

11
“[Dalam bahasa non-fungsional] pola ini sepertinya tidak banyak berguna bagi Anda” - Ini adalah pola yang cukup umum di OOP. GOF memiliki variasi pola ini, dan bahasa seperti C ++ menawarkan konstruksi asli untuk menyandikannya.
Konrad Rudolph

14
@jk. Ya, mereka tidak menghitung (yah saya kira mereka lakukan; mereka hanya sangat buruk dalam skenario ini karena kurangnya keamanan). Maksud saya std::variant(dan pendahulunya spiritual).
Konrad Rudolph

2
@ Ewan Tidak, itu mengatakan "Pengukuran adalah tipe data yang baik ... atau ...".
Konrad Rudolph

2
@DavidArno Yah bahkan tanpa DU ada solusi "kanonik" untuk ini di OOP, yaitu memiliki superclass nilai dengan subclass untuk nilai yang valid dan tidak valid. Tapi itu mungkin terlalu jauh (dan dalam praktiknya tampaknya sebagian besar basis kode menghindari polimorfisme subkelas demi sebuah bendera untuk ini, seperti yang ditunjukkan dalam jawaban lain).
Konrad Rudolph

58

Jika Anda belum tahu apa itu monad, hari ini akan menjadi hari yang baik untuk belajar. Saya memiliki pengantar yang lembut untuk programmer OO di sini:

https://ericlippert.com/2013/02/21/monads-part-one/

Skenario Anda adalah ekstensi kecil ke "mungkin monad", juga dikenal sebagai Nullable<T>dalam C # dan Optional<T>dalam bahasa lain.

Misalkan Anda memiliki tipe abstrak untuk mewakili monad:

abstract class Measurement<T> { ... }

dan kemudian tiga subclass:

final class Unknown<T> : Measurement<T> { ... a singleton ...}
final class Empty<T> : Measurement<T> { ... a singleton ... }
final class Actual<T> : Measurement<T> { ... a wrapper around a T ...}

Kami membutuhkan implementasi Bind:

abstract class Measurement<T>
{ 
    public Measurement<R> Bind(Func<T, Measurement<R>> f)
  {
    if (this is Unknown<T>) return Unknown<R>.Singleton;
    if (this is Empty<T>) return Empty<R>.Singleton;
    if (this is Actual<T>) return f(((Actual<T>)this).Value);
    throw ...
  }

Dari sini Anda dapat menulis versi Bind yang disederhanakan ini:

public Measurement<R> Bind(Func<A, R> f) 
{
  return this.Bind(a => new Actual<R>(f(a));
}

Dan sekarang kamu sudah selesai. Anda ada Measurement<int>di tangan. Anda ingin menggandakannya:

Measurement<int> m = whatever;
Measurement<int> doubled = m.Bind(a => a * 2);
Measurement<string> asString = m.Bind(a => a.ToString());

Dan ikuti logikanya; jika mini Empty<int>kemudian asStringadalah Empty<String>, sangat baik.

Begitu pula kalau kita punya

Measurement<int> First()

dan

Measurement<double> Second(int i);

maka kita dapat menggabungkan dua pengukuran:

Measurement<double> d = First().Bind(Second);

dan sekali lagi, jika First()ini Empty<int>maka dadalah Empty<double>dan sebagainya.

Langkah kuncinya adalah untuk mendapatkan operasi ikatan yang benar . Pikirkan baik-baik tentang hal itu.


4
Monads (untungnya) jauh lebih mudah digunakan daripada memahami. :)
Guran

11
@ Leftaroundabout: Justru karena saya tidak ingin masuk ke dalam perbedaan yang memecah-belah; seperti dicatat oleh poster aslinya, banyak orang kurang percaya diri ketika berurusan dengan monad. Teori kategori jargon-sarat penokohan operasi sederhana bekerja melawan pengembangan rasa percaya diri dan pemahaman.
Eric Lippert

2
Jadi saran Anda adalah mengganti Nulldengan Nullable+ beberapa kode boilerplate? :)
Eric Duminil

3
@Claude: Anda harus membaca tutorial saya. Monad adalah tipe generik yang mengikuti aturan tertentu dan menyediakan kemampuan untuk mengikat rantai operasi, jadi dalam hal ini, Measurement<T>adalah tipe monadik.
Eric Lippert

5
@daboross: Meskipun saya setuju bahwa monad stateful adalah cara yang baik untuk memperkenalkan monad, saya tidak berpikir membawa negara sebagai hal yang menjadi ciri monad. Saya memikirkan fakta bahwa Anda dapat mengikat urutan fungsi adalah hal yang menarik; statusnya hanyalah detail implementasi.
Eric Lippert

18

Saya pikir dalam hal ini variasi pada Pola Objek Null akan berguna:

public class Measurement
{
    private int value;
    private bool isUnknown = false;
    private bool isMissing = false;

    private Measurement() { }
    public Measurement(int value) { this.value = value; }

    public int Value {
        get {
            if (!isUnknown && !isMissing)
            {
                return this.value;
            }
            throw new SomeException("...");
        }                   
    }

    public static readonly Measurement Unknown = new Measurement
    {
        isUnknown = true
    };

    public static readonly Measurement Missing = new Measurement
    {
        isMissing = true
    };
}

Anda dapat mengubahnya menjadi struct, menimpa Equals / GetHashCode / ToString, menambahkan konversi implisit dari atau ke int, dan jika Anda ingin perilaku seperti NaN, Anda juga dapat menerapkan operator aritmatika Anda sendiri sehingga misalnya. Measurement.Unknown * 2 == Measurement.Unknown.

Yang mengatakan, C # Nullable<int>mengimplementasikan semua itu, dengan satu-satunya peringatan adalah bahwa Anda tidak dapat membedakan antara berbagai jenis nulls. Saya bukan orang Jawa, tetapi pemahaman saya adalah bahwa bahasa Jawa OptionalIntmirip, dan bahasa lain mungkin memiliki fasilitas mereka sendiri untuk mewakili suatu Optionaljenis.


6
Implementasi paling umum yang saya lihat dari pola ini melibatkan pewarisan. Mungkin ada kasus untuk dua sub kelas: MissingMeasurement dan UnknownMeasurement. Mereka bisa mengimplementasikan atau mengganti metode di kelas Pengukuran induk. +1
Greg Burghardt

2
Bukankah poin dari Pola Objek Null bahwa Anda tidak gagal pada nilai yang tidak valid, tetapi tidak melakukan apa-apa?
Chris Wohlert

2
@ ChrisWohlert dalam hal ini objek tidak benar-benar memiliki metode kecuali Valuepengambil, yang benar-benar harus gagal karena Anda tidak dapat mengubah Unknownkembali menjadi int. Jika pengukuran memiliki, katakanlah, SaveToDatabase()metode, maka implementasi yang baik mungkin tidak akan melakukan transaksi jika objek saat ini adalah objek nol (baik melalui perbandingan dengan singleton, atau metode override).
Maciej Stachowski

3
@ MaciejStachowski Ya, saya tidak mengatakan itu tidak boleh melakukan apa-apa, saya katakan Null Object Pattern tidak cocok. Solusi Anda mungkin baik-baik saja, tetapi saya tidak akan menyebutnya Pola Obyek Null .
Chris Wohlert

14

Jika Anda benar-benar HARUS menggunakan integer maka hanya ada satu solusi yang mungkin. Gunakan beberapa nilai yang mungkin sebagai 'angka ajaib' yang berarti 'hilang' dan 'tidak diketahui'

mis. 2.147.483.647 dan 2.147.483.646

Jika Anda hanya perlu int untuk pengukuran 'nyata', maka buat struktur data yang lebih rumit

class Measurement {
    public bool IsEmpty;
    public bool IsKnown;
    public int Value {
        get {
            if(!IsEmpty && IsKnown) return _value;
            throw new Exception("NaN");
            }
        }
}

Klarifikasi Penting:

Anda dapat mencapai persyaratan matematika dengan membebani operator untuk kelas

public static Measurement operator+ (Measurement a, Measurement b) {
    if(a.IsEmpty) { return b; }
    ...etc
}

10
@KakturusOption<Option<Int>>
Bergi

5
@Bergi Anda tidak mungkin berpikir itu bahkan dapat diterima dari jauh ..
BlueRaja - Danny Pflughoeft

8
@ BlueRaja-DannyPflughoeft Sebenarnya sangat cocok dengan deskripsi OP, yang memiliki struktur bersarang juga. Agar dapat diterima kami akan memperkenalkan jenis alias yang tepat (atau "tipe baru") tentu saja - tetapi type Measurement = Option<Int>untuk hasil yang bilangan bulat atau bacaan kosong ok, dan juga Option<Measurement>untuk pengukuran yang mungkin telah diambil atau tidak .
Bergi

7
@arp "Integer dekat NaN"? Bisakah Anda menjelaskan apa yang Anda maksud dengan itu? Tampaknya agak berlawanan dengan intuisi untuk mengatakan bahwa angka "mendekati" konsep sesuatu yang bukan angka.
Nic Hartley

3
@Nic Hartley Dalam sistem kami, sekelompok apa yang "secara alami" akan menjadi bilangan bulat negatif serendah mungkin dicadangkan sebagai NaN. Kami menggunakan ruang itu untuk mengkode berbagai alasan mengapa byte tersebut mewakili sesuatu selain data yang sah. (Itu beberapa dekade yang lalu dan saya mungkin telah mengaburkan beberapa detail, tetapi pasti ada satu set bit yang dapat Anda masukkan ke dalam nilai integer untuk membuatnya melempar NaN jika Anda mencoba melakukan matematika dengan itu.
arp

11

Jika variabel Anda adalah angka floating-point, IEEE754 (standar angka floating point yang didukung oleh sebagian besar prosesor dan bahasa modern) mendukung Anda: ini adalah fitur yang sedikit diketahui, tetapi standar tidak mendefinisikan satu, tetapi seluruh keluarga dari Nilai NaN (bukan angka), yang dapat digunakan untuk arti yang ditentukan aplikasi secara arbitrer. Dalam float presisi tunggal, misalnya, Anda memiliki 22 bit gratis yang dapat Anda gunakan untuk membedakan antara 2 ^ {22} jenis nilai yang tidak valid.

Biasanya, antarmuka pemrograman hanya mengekspos salah satu dari mereka (misalnya, Numpy nan); Saya tidak tahu apakah ada cara built-in untuk menghasilkan yang lain selain manipulasi bit eksplisit, tapi itu hanya masalah menulis beberapa rutinitas tingkat rendah. (Anda juga perlu satu untuk membedakan mereka, karena, dengan desain, a == bselalu mengembalikan false ketika salah satu dari mereka adalah NaN.)

Menggunakannya lebih baik daripada menciptakan kembali "angka ajaib" Anda sendiri untuk memberi sinyal data yang tidak valid, karena mereka menyebar dengan benar dan memberi sinyal tidak valid: misalnya, Anda tidak mengambil risiko menembak diri sendiri jika Anda menggunakan suatu average()fungsi dan lupa memeriksa nilai-nilai khusus Anda.

Satu-satunya risiko adalah perpustakaan tidak mendukung mereka dengan benar, karena mereka adalah fitur yang cukup jelas: misalnya, perpustakaan serialisasi dapat 'meratakan' mereka semua ke yang sama nan(yang terlihat setara dengan itu untuk sebagian besar tujuan).


6

Mengikuti jawaban David Arno , Anda dapat melakukan sesuatu seperti penyatuan yang didiskriminasi dalam OOP, dan dalam gaya fungsional-objek seperti yang diberikan oleh Scala, dengan tipe fungsi Java 8, atau perpustakaan Java FP seperti Vavr atau Fugue rasanya cukup adil. alami untuk menulis sesuatu seperti:

var value = Measurement.of(2);
out.println(value.map(x -> x * 2));

var empty = Measurement.empty();
out.println(empty.map(x -> x * 2));

var unknown = Measurement.unknown();
out.println(unknown.map(x -> x * 2));

pencetakan

Value(4)
Empty()
Unknown()

( Implementasi penuh sebagai intisari .)

Bahasa atau pustaka FP menyediakan alat lain seperti Try(alias Maybe) (objek yang berisi nilai, atau kesalahan) dan Either(objek yang berisi nilai keberhasilan atau nilai kegagalan) yang juga bisa digunakan di sini.


2

Solusi ideal untuk masalah Anda akan bergantung pada mengapa Anda peduli tentang perbedaan antara kegagalan yang diketahui dan pengukuran yang tidak dapat diandalkan yang diketahui, dan proses hilir apa yang ingin Anda dukung. Catatan, 'proses hilir' untuk kasus ini tidak mengecualikan operator manusia atau sesama pengembang.

Hanya muncul dengan "citarasa kedua" dari nol tidak memberikan rangkaian proses hilir informasi yang cukup untuk menurunkan serangkaian perilaku yang masuk akal.

Jika Anda mengandalkan asumsi kontekstual tentang sumber perilaku buruk yang dibuat oleh kode hilir, saya akan menyebutnya arsitektur buruk.

Jika Anda cukup tahu untuk membedakan antara alasan kegagalan dan kegagalan tanpa alasan yang diketahui, dan bahwa informasi akan menginformasikan perilaku di masa depan, Anda harus mengkomunikasikan pengetahuan itu ke hilir, atau menanganinya secara inline.

Beberapa pola untuk menangani ini:

  • Jumlah jenis
  • Serikat yang didiskriminasi
  • Objek atau struct yang berisi enum yang mewakili hasil operasi dan bidang untuk hasil
  • String ajaib atau angka ajaib yang tidak mungkin dicapai melalui operasi normal
  • Pengecualian, dalam bahasa yang penggunaannya idiomatik
  • Menyadari bahwa sebenarnya tidak ada nilai dalam membedakan antara dua skenario ini dan hanya menggunakan null

2

Jika saya khawatir dengan "menyelesaikan sesuatu" daripada solusi yang elegan, peretasan yang cepat dan kotor adalah dengan menggunakan string "tidak dikenal", "hilang", dan 'representasi string dari nilai numerik saya', yang kemudian akan menjadi dikonversi dari string dan digunakan sesuai kebutuhan. Diimplementasikan lebih cepat daripada menulis ini, dan setidaknya dalam beberapa keadaan, sepenuhnya memadai. (Saya sekarang membentuk kumpulan taruhan pada jumlah downvotes ...)


Terpilih karena menyebutkan "menyelesaikan sesuatu."
Barbekyu

4
Beberapa orang mungkin mencatat bahwa ini menderita sebagian besar masalah yang sama seperti menggunakan NULL, yaitu bahwa ia hanya beralih dari membutuhkan cek NULL ke membutuhkan cek "tidak dikenal" dan "hilang", tetapi tetap membuat waktu run time crash untuk korupsi data diam yang beruntung untuk yang sial sebagai satu-satunya indikator bahwa Anda lupa cek. Bahkan cek NULL yang hilang memiliki keuntungan yang dapat ditangkap oleh linter, tetapi ini kehilangan itu. Itu memang menambah perbedaan antara "tidak diketahui" dan "hilang", jadi itu mengalahkan NULL di sana ...
8bittree

2

Inti pertanyaannya adalah "Bagaimana cara mengembalikan dua informasi yang tidak terkait dari metode yang mengembalikan satu int? Saya tidak pernah ingin memeriksa nilai pengembalian saya, dan nol itu buruk, jangan gunakan mereka."

Mari kita lihat apa yang ingin Anda lewati. Anda lewat baik int, atau non-int alasan mengapa Anda tidak bisa memberikan int. Pertanyaan itu menegaskan bahwa hanya akan ada dua alasan, tetapi siapa pun yang pernah membuat enum tahu bahwa daftar apa pun akan tumbuh. Cakupan untuk menentukan alasan lain masuk akal.

Awalnya, kemudian, ini sepertinya kasus yang baik untuk melempar pengecualian.

Ketika Anda ingin memberi tahu penelepon sesuatu yang istimewa yang tidak ada dalam tipe pengembalian, pengecualian seringkali merupakan sistem yang sesuai: pengecualian tidak hanya untuk status kesalahan, dan memungkinkan Anda untuk mengembalikan banyak konteks dan alasan untuk menjelaskan mengapa Anda hanya bisa hari ini.

Dan ini adalah sistem HANYA yang memungkinkan Anda untuk mengembalikan int yang dijamin-valid, dan menjamin bahwa setiap operator int dan metode yang mengambil int dapat menerima nilai pengembalian metode ini tanpa perlu memeriksa nilai yang tidak valid seperti nilai null, atau nilai sihir.

Tetapi pengecualian benar-benar hanya solusi yang valid jika, seperti namanya, ini adalah kasus luar biasa , bukan bisnis normal.

Dan coba / tangkap dan pawang sama seperti boilerplate sebagai cek nol, yang adalah apa yang pertama kali keberatan.

Dan jika penelepon tidak berisi try / catch, maka penelepon harus, dan seterusnya.


Lulus kedua yang naif adalah mengatakan "Ini pengukuran. Pengukuran jarak negatif tidak mungkin." Jadi untuk beberapa pengukuran Y, Anda bisa memiliki konstanta untuk

  • -1 = tidak diketahui,
  • -2 = tidak mungkin untuk diukur,
  • -3 = menolak untuk menjawab,
  • -4 = diketahui tetapi rahasia,
  • -5 = bervariasi tergantung pada fase bulan, lihat tabel 5a,
  • -6 = empat dimensi, pengukuran diberikan dalam judul,
  • -7 = kesalahan pembacaan sistem file,
  • -8 = dicadangkan untuk penggunaan di masa mendatang,
  • -9 = kuadrat / kubik jadi Y sama dengan X,
  • -10 = adalah layar monitor jadi tidak menggunakan pengukuran X, Y: gunakan X sebagai layar diagonal,
  • -11 = menulis pengukuran di belakang tanda terima dan dicuci menjadi tidak terbaca tetapi saya pikir itu 5 atau 17,
  • -12 = ... Anda mendapatkan ide.

Ini adalah cara yang dilakukan dalam banyak sistem C lama, dan bahkan dalam sistem modern di mana ada kendala asli untuk int, dan Anda tidak dapat membungkusnya dengan struct atau monad dari beberapa jenis.

Jika pengukuran bisa negatif, maka Anda hanya membuat tipe data Anda lebih besar (mis. Int panjang) dan memiliki nilai ajaib lebih tinggi dari kisaran int, dan idealnya dimulai dengan beberapa nilai yang akan muncul dengan jelas dalam debugger.

Ada alasan bagus untuk menjadikannya sebagai variabel terpisah, alih-alih hanya memiliki angka ajaib. Misalnya, pengetikan yang ketat, rawatan, dan sesuai dengan harapan.


Maka, dalam upaya ketiga kami, kami melihat kasus-kasus di mana bisnis normal memiliki nilai non-int. Misalnya, jika kumpulan nilai-nilai ini dapat berisi beberapa entri non-integer. Ini berarti penangan pengecualian mungkin pendekatan yang salah.

Dalam hal itu, terlihat kasus yang bagus untuk struktur yang melewati int, dan alasannya. Sekali lagi, alasan ini bisa saja merupakan konstelasi seperti di atas, tetapi alih-alih memegang keduanya di int yang sama, Anda menyimpannya sebagai bagian yang berbeda dari suatu struktur. Awalnya, kami memiliki aturan bahwa jika alasannya diatur, int tidak akan ditetapkan. Tetapi kita tidak lagi terikat pada aturan ini; kami dapat memberikan alasan untuk angka yang valid juga, jika perlu.

Either way, setiap kali Anda menyebutnya, Anda masih memerlukan boilerplate, untuk menguji alasan untuk melihat apakah int itu valid, lalu tarik keluar dan gunakan bagian int jika alasannya memungkinkan kami.

Di sinilah Anda perlu menyelidiki alasan Anda di balik "jangan gunakan null".

Seperti halnya pengecualian, null dimaksudkan untuk menandakan keadaan luar biasa.

Jika penelepon memanggil metode ini dan mengabaikan bagian "rasional" dari struktur sepenuhnya, mengharapkan nomor tanpa penanganan kesalahan, dan mendapat nol, maka itu akan menangani nol sebagai angka, dan salah. Jika mendapat nomor ajaib, itu akan memperlakukannya sebagai angka, dan salah. Tetapi jika mendapat nol, itu akan jatuh , seperti yang seharusnya dilakukan.

Jadi setiap kali Anda memanggil metode ini, Anda harus memasukkan cek untuk nilai pengembaliannya, namun Anda menangani nilai-nilai yang tidak valid, baik dalam-band atau keluar dari band, mencoba / menangkap, memeriksa struct untuk komponen "rasional", memeriksa int untuk nomor ajaib, atau memeriksa int untuk null ...

Alternatifnya, untuk menangani multiplikasi output yang mungkin mengandung int tidak valid dan alasan seperti "Anjing saya memakan pengukuran ini", adalah membebani operator multiplikasi untuk struktur itu.

... Dan kemudian membebani setiap operator lain dalam aplikasi Anda yang mungkin diterapkan pada data ini.

... Dan kemudian membebani semua metode yang mungkin memerlukan int.

... Dan semua kelebihan itu masih harus mengandung cek untuk int yang tidak valid, hanya agar Anda dapat memperlakukan jenis pengembalian metode yang satu ini seolah-olah itu selalu int yang valid pada saat Anda meneleponnya.

Jadi premis aslinya salah dalam berbagai cara:

  1. Jika Anda memiliki nilai yang tidak valid, Anda tidak dapat menghindari memeriksa nilai-nilai yang tidak valid di setiap titik dalam kode tempat Anda menangani nilai-nilai tersebut.
  2. Jika Anda mengembalikan sesuatu selain int, Anda tidak mengembalikan int, sehingga Anda tidak dapat memperlakukannya seperti int. Kelebihan operator memungkinkan Anda berpura - pura , tapi itu hanya pura-pura.
  3. Int dengan angka ajaib (termasuk NULL, NAN, Inf ...) tidak lagi benar-benar int, ini adalah struct orang miskin.
  4. Menghindari nulls tidak akan membuat kode lebih kuat, itu hanya akan menyembunyikan masalah dengan ints, atau memindahkannya ke dalam struktur penanganan pengecualian yang kompleks.

1

Saya tidak mengerti alasan pertanyaan Anda, tetapi inilah jawaban yang sebenarnya. Untuk Hilang atau Kosong, Anda bisa melakukannya math.nan(Bukan Angka). Anda dapat melakukan operasi matematika apa pun pada math.nandan itu akan tetap math.nan.

Anda dapat menggunakan None(null Python) untuk nilai yang tidak diketahui. Anda tidak boleh memanipulasi nilai yang tidak diketahui, dan beberapa bahasa (Python bukan salah satu dari mereka) memiliki operator null khusus sehingga operasi hanya dilakukan jika nilainya nonnull, jika nilainya tetap nol.

Bahasa lain memiliki klausa penjaga (seperti Swift atau Ruby), dan Ruby memiliki pengembalian awal bersyarat.

Saya telah melihat ini diselesaikan dengan Python dalam beberapa cara berbeda:

  • dengan struktur data pembungkus, karena informasi numerik biasanya mengenai entitas dan memiliki waktu pengukuran. Wrapper dapat mengesampingkan metode ajaib seperti __mult__sehingga tidak ada pengecualian yang muncul ketika nilai Tidak Diketahui atau Hilang Anda muncul. Numpy dan panda mungkin memiliki kemampuan seperti itu di dalamnya.
  • dengan nilai sentinel (seperti Anda Unknownatau -1 / -2) dan pernyataan if
  • dengan bendera boolean yang terpisah
  • dengan struktur data yang malas- fungsi Anda melakukan beberapa operasi pada struktur, kemudian kembali, fungsi terluar yang membutuhkan hasil aktual mengevaluasi struktur data yang malas
  • dengan pipeline lazy operasi- mirip dengan yang sebelumnya, tetapi yang ini dapat digunakan pada set data atau database

1

Bagaimana nilai disimpan dalam memori tergantung pada bahasa dan detail implementasi. Saya pikir apa yang Anda maksud adalah bagaimana objek harus bersikap terhadap programmer. (Ini adalah bagaimana saya membaca pertanyaan, katakan kalau saya salah.)

Anda telah mengajukan jawaban untuk itu dalam pertanyaan Anda: gunakan kelas Anda sendiri yang menerima operasi matematika apa pun dan mengembalikannya sendiri tanpa memunculkan pengecualian. Anda mengatakan Anda menginginkan ini karena Anda ingin menghindari cek nol.

Solusi 1: jangan menghindari cek nol

Missingdapat direpresentasikan sebagaimana math.nan
Unknowndapat direpresentasikan sebagaiNone

Jika Anda memiliki lebih dari satu nilai, Anda filter()hanya dapat menerapkan operasi pada nilai yang tidak Unknownatau Missing, atau nilai apa pun yang ingin Anda abaikan untuk fungsi tersebut.

Saya tidak bisa membayangkan skenario di mana Anda memerlukan pemeriksaan nol pada fungsi yang bekerja pada skalar tunggal. Dalam hal ini, bagus untuk memaksa cek-nol.


Solusi 2: gunakan dekorator yang menangkap pengecualian

Dalam hal ini, Missingbisa naik MissingExceptiondan Unknownnaik UnknownExceptionketika operasi dilakukan di atasnya.

@suppressUnknown(value=Unknown) # if an UnknownException is raised, return this value instead
@suppressMissing(value=Missing)
def sigmoid(value):
    ...

Keuntungan dari pendekatan ini adalah bahwa properti Missingdan Unknownhanya ditekan ketika Anda secara eksplisit meminta mereka untuk ditekan. Keuntungan lain adalah bahwa pendekatan ini mendokumentasikan diri: setiap fungsi menunjukkan apakah ia mengharapkan sesuatu yang tidak diketahui atau tidak ada dan bagaimana fungsinya.

Saat Anda memanggil suatu fungsi tidak mengharapkan Hilang mendapat Hilang, fungsi tersebut akan segera muncul, menunjukkan dengan tepat di mana kesalahan terjadi alih-alih secara diam-diam gagal dan menyebarkan rantai panggilan Hilang ke atas. Hal yang sama berlaku untuk Tidak Diketahui.

sigmoidmasih dapat menelepon sin, meskipun tidak mengharapkan Missingatau Unknown, karena sigmoiddekorator akan menangkap pengecualian.


1
bertanya-tanya apa gunanya memposting dua jawaban untuk pertanyaan yang sama (ini adalah jawaban Anda sebelumnya , ada yang salah dengan itu?)
agas

@gnat Jawaban ini memberikan alasan mengapa seharusnya tidak dilakukan seperti yang ditunjukkan penulis, dan saya tidak ingin melalui kerumitan mengintegrasikan dua jawaban dengan ide yang berbeda - hanya lebih mudah untuk menulis dua jawaban yang dapat dibaca secara mandiri . Saya tidak mengerti mengapa Anda begitu peduli dengan alasan orang lain yang tidak berbahaya.
noɥʇʎԀʎzɐɹƆ

0

Asumsikan mengambil jumlah CPU di server. Jika server dimatikan, atau telah dihapus, nilai itu tidak ada. Ini akan menjadi pengukuran yang tidak masuk akal (mungkin "hilang" / "kosong" bukan istilah terbaik). Tetapi nilainya "diketahui" tidak masuk akal. Jika server ada, tetapi proses pengambilan nilai macet, mengukurnya valid, tetapi gagal menghasilkan nilai "tidak dikenal".

Kedua hal ini kedengarannya seperti kondisi kesalahan, jadi saya akan menilai bahwa opsi terbaik di sini adalah dengan langsung get_measurement()membuang keduanya sebagai pengecualian (seperti DataSourceUnavailableExceptionatau SpectacularFailureToGetDataException, masing-masing). Kemudian, jika salah satu dari masalah ini terjadi, kode pengumpulan data dapat langsung bereaksi terhadapnya (seperti dengan mencoba lagi dalam kasus terakhir), dan get_measurement()hanya perlu mengembalikan sebuah intjika ia berhasil mendapatkan data dari data tersebut. sumber - dan Anda tahu bahwa intitu valid.

Jika situasi Anda tidak mendukung pengecualian atau tidak dapat memanfaatkannya, maka alternatif yang baik adalah menggunakan kode kesalahan, mungkin dikembalikan melalui output terpisah get_measurement(). Ini adalah pola idiomatik dalam C, di mana output aktual disimpan dalam pointer input dan kode kesalahan dilewatkan kembali sebagai nilai balik.


0

Jawaban yang diberikan baik-baik saja, tetapi masih tidak mencerminkan hubungan hierarkis antara nilai, kosong dan tidak diketahui.

  • Tertinggi tidak diketahui .
  • Maka sebelum menggunakan nilai pertama yang kosong harus diklarifikasi.
  • Terakhir adalah nilai untuk menghitung.

Jelek (karena abstraksi yang gagal), tetapi akan beroperasi penuh (di Jawa):

Optional<Optional<Integer>> unknowableValue;

unknowableValue.ifPresent(emptiableValue -> ...);
Optional<Integer> emptiableValue = unknowableValue.orElse(Optional.empty());

emptiableValue.ifPresent(value -> ...);
int value = emptiableValue.orElse(0);

Di sini bahasa fungsional dengan sistem tipe yang bagus lebih baik.

Faktanya: Nilai-nilai kosong / hilang dan tidak dikenal * agaknya tampak sebagai bagian dari suatu proses, beberapa jalur produksi. Seperti sel spread sheet Excel dengan rumus referensi sel lain. Di sana orang akan berpikir mungkin menyimpan lambda kontekstual. Mengubah sel akan mengevaluasi kembali semua sel yang bergantung secara rekursif.

Dalam hal ini nilai int akan didapatkan oleh pemasok int. Nilai kosong akan memberikan pemasok int melemparkan pengecualian kosong, atau mengevaluasi menjadi kosong (secara rekursif ke atas). Formula utama Anda akan menghubungkan semua nilai dan mungkin juga mengembalikan yang kosong (nilai / pengecualian). Nilai yang tidak diketahui akan menonaktifkan evaluasi dengan melemparkan pengecualian.

Nilai mungkin dapat diamati, seperti properti terikat java, memberi tahu pendengar tentang perubahan.

Singkatnya: Pola berulang yang membutuhkan nilai dengan status tambahan kosong dan tidak dikenal tampaknya menunjukkan bahwa lembar yang lebih menyebar seperti model data properti terikat mungkin lebih baik.


0

Ya, konsep beberapa jenis NA berbeda ada dalam beberapa bahasa; lebih dari itu dalam statistik, di mana itu lebih bermakna (yaitu perbedaan besar antara Hilang-At-Acak, Hilang-Sepenuhnya-At-Acak, Hilang-Tidak-Secara-Acak ).

  • jika kita hanya mengukur panjang widget, maka tidak penting untuk membedakan antara 'kegagalan sensor' atau 'pemadaman listrik' atau 'kegagalan jaringan' (meskipun 'numerical overflow' memang menyampaikan informasi)

  • tetapi dalam mis. penambangan data atau survei, meminta responden untuk misalnya penghasilan atau status HIV mereka, hasil dari 'Tidak Diketahui' berbeda dengan 'Tolak untuk menjawab', dan Anda dapat melihat bahwa asumsi kami sebelumnya tentang bagaimana cara menyalahkan yang terakhir akan cenderung berbeda dengan yang pertama. Jadi bahasa seperti SAS mendukung banyak jenis NA yang berbeda; bahasa R tidak tetapi pengguna sangat sering harus meretas itu; NAS pada titik yang berbeda dalam suatu pipa dapat digunakan untuk menunjukkan hal yang sangat berbeda.

  • ada juga kasus di mana kami memiliki beberapa variabel NA untuk entri tunggal ("imputasi ganda"). Contoh: jika saya tidak tahu usia, kode pos, tingkat pendidikan, atau penghasilan seseorang, lebih sulit untuk menghitung pendapatan mereka.

Mengenai bagaimana Anda mewakili berbagai jenis NA dalam bahasa serba guna yang tidak mendukungnya, umumnya orang meretas hal-hal seperti floating-point-NaN (memerlukan pengonversi bilangan bulat), enum atau sentinel (misalnya 999 atau -1000) untuk bilangan bulat atau nilai kategorikal. Biasanya tidak ada jawaban yang sangat bersih, maaf.


0

R memiliki dukungan nilai hilang bawaan. https://medium.com/coinmonks/dealing-with-missing-data-using-r-3ae428da2d17

Sunting: karena saya downvoted saya akan menjelaskan sedikit.

Jika Anda akan berurusan dengan statistik, saya sarankan Anda untuk menggunakan bahasa statistik seperti R karena R ditulis oleh ahli statistik untuk ahli statistik. Nilai-nilai yang hilang adalah topik besar yang mengajarkan Anda satu semester penuh. Dan ada buku besar hanya tentang nilai-nilai yang hilang.

Namun Anda dapat menandai Anda data yang hilang, seperti titik atau "hilang" atau apa pun. Dalam R Anda dapat mendefinisikan apa yang Anda maksud dengan melewatkan. Anda tidak perlu mengubahnya.

Cara normal untuk mendefinisikan nilai yang hilang adalah dengan menandainya sebagai NA.

x <- c(1, 2, NA, 4, "")

Lalu Anda bisa melihat nilai apa yang hilang;

is.na(x)

Dan hasilnya adalah;

FALSE FALSE  TRUE FALSE FALSE

Seperti yang Anda lihat ""tidak ada yang hilang. Anda dapat mengancam ""sebagai tidak dikenal. Dan NAhilang.


@ Hulk, bahasa fungsional apa yang mendukung nilai yang hilang? Bahkan jika mereka mendukung nilai yang hilang, saya yakin Anda tidak dapat mengisinya dengan metode statistik hanya dalam satu baris kode.
ilhan

-1

Apakah ada alasan mengapa fungsi *operator tidak dapat diubah?

Sebagian besar jawaban melibatkan nilai pencarian semacam, tetapi mungkin lebih mudah untuk mengubah operator matematika dalam kasus ini.

Anda kemudian akan dapat memiliki sejenis empty()/ unknown()fungsi di seluruh proyek Anda.


4
Ini berarti Anda harus membebani semua operator
pipa
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.