Contoh kode berikut memberikan konteks untuk pertanyaan saya.
Kelas Kamar diinisialisasi dengan delegasi. Dalam implementasi pertama dari kelas Ruangan, tidak ada penjaga terhadap delegasi yang melempar pengecualian. Pengecualian seperti itu akan muncul ke properti North, di mana delegasi dievaluasi (catatan: Main () metode menunjukkan bagaimana instance Kamar digunakan dalam kode klien):
public sealed class Room
{
private readonly Func<Room> north;
public Room(Func<Room> north)
{
this.north = north;
}
public Room North
{
get
{
return this.north();
}
}
public static void Main(string[] args)
{
Func<Room> evilDelegate = () => { throw new Exception(); };
var kitchen = new Room(north: evilDelegate);
var room = kitchen.North; //<----this will throw
}
}
Menjadi bahwa saya lebih suka gagal pada pembuatan objek daripada ketika membaca properti Utara, saya mengubah konstruktor menjadi pribadi, dan memperkenalkan metode pabrik statis bernama Buat (). Metode ini menangkap pengecualian yang dilemparkan oleh delegasi, dan melemparkan pengecualian pembungkus, memiliki pesan pengecualian yang bermakna:
public sealed class Room
{
private readonly Func<Room> north;
private Room(Func<Room> north)
{
this.north = north;
}
public Room North
{
get
{
return this.north();
}
}
public static Room Create(Func<Room> north)
{
try
{
north?.Invoke();
}
catch (Exception e)
{
throw new Exception(
message: "Initialized with an evil delegate!", innerException: e);
}
return new Room(north);
}
public static void Main(string[] args)
{
Func<Room> evilDelegate = () => { throw new Exception(); };
var kitchen = Room.Create(north: evilDelegate); //<----this will throw
var room = kitchen.North;
}
}
Apakah blok try-catch membuat metode Create () tidak murni?
sumber
Create
juga tidak murni, karena ia memanggilnya.Create
fungsi tidak melindungi Anda dari mendapatkan pengecualian ketika mendapatkan properti. Jika delegasi Anda melempar, dalam kehidupan nyata sangat mungkin bahwa itu akan dilemparkan hanya dalam beberapa kondisi. Kemungkinannya adalah kondisi untuk melempar tidak ada selama konstruksi, tetapi mereka ada saat mendapatkan properti.Jawaban:
Iya nih. Itu secara efektif merupakan fungsi yang tidak murni. Itu menciptakan efek samping: eksekusi program berlanjut di suatu tempat selain dari tempat fungsi diharapkan kembali.
Untuk menjadikannya fungsi murni,
return
objek aktual yang merangkum nilai yang diharapkan dari fungsi dan nilai yang menunjukkan kemungkinan kondisi kesalahan, sepertiMaybe
objek atau objek Unit Kerja.sumber
Ya, ya ... dan tidak.
Fungsi murni harus memiliki Transparansi Referensial - yaitu, Anda harus dapat mengganti panggilan yang mungkin ke fungsi murni dengan nilai yang dikembalikan, tanpa mengubah perilaku program. * Fungsi Anda dijamin untuk selalu melemparkan argumen tertentu, jadi tidak ada nilai balik untuk mengganti panggilan fungsi dengan, jadi alih-alih mari kita abaikan saja. Pertimbangkan kode ini:
Jika
func
murni, pengoptimal dapat memutuskan yangfunc(arg)
dapat diganti dengan hasilnya. Belum tahu apa hasilnya, tetapi dapat mengatakan bahwa itu tidak digunakan - jadi hanya dapat menyimpulkan pernyataan ini tidak memiliki efek dan menghapusnya.Tetapi jika
func(arg)
kebetulan melempar, pernyataan ini memang melakukan sesuatu - itu melempar pengecualian! Jadi pengoptimal tidak dapat menghapusnya - tidak masalah jika fungsi dipanggil atau tidak.Tapi...
Dalam praktiknya, ini sangat kecil artinya. Pengecualian - setidaknya dalam C # - adalah sesuatu yang luar biasa. Anda tidak seharusnya menggunakannya sebagai bagian dari aliran kontrol reguler Anda - Anda seharusnya mencoba dan menangkapnya, dan jika Anda menangkap sesuatu menangani kesalahan untuk mengembalikan apa yang Anda lakukan atau entah bagaimana masih menyelesaikannya. Jika program Anda tidak berfungsi sebagaimana mestinya karena kode yang gagal gagal telah dioptimalkan, Anda menggunakan pengecualian yang salah (kecuali jika itu adalah kode pengujian, dan ketika Anda membuat pengecualian untuk pengujian tidak boleh dioptimalkan).
Yang telah dibilang...
Jangan membuang pengecualian dari fungsi murni dengan maksud bahwa mereka akan ditangkap - ada alasan bagus mengapa bahasa fungsional lebih memilih untuk menggunakan monad daripada pengecualian stack-unwinding-exception.
Jika C # memiliki
Error
kelas seperti Java (dan banyak bahasa lainnya), saya akan menyarankan untuk melemparError
bukanException
. Ini menunjukkan bahwa pengguna fungsi melakukan sesuatu yang salah (melewati fungsi yang melempar), dan hal-hal seperti itu diperbolehkan dalam fungsi murni. Tetapi C # tidak memilikiError
kelas, dan pengecualian kesalahan penggunaan tampaknya berasal dariException
. Saran saya adalah melemparArgumentException
, memperjelas bahwa fungsi itu dipanggil dengan argumen yang buruk.* Berbicara secara komputasi. Fungsi Fibonacci yang diimplementasikan menggunakan rekursi naif akan membutuhkan waktu lama untuk jumlah besar, dan dapat menghabiskan sumber daya mesin, tetapi ini karena dengan waktu dan memori tanpa batas fungsi akan selalu mengembalikan nilai yang sama dan tidak akan memiliki efek samping (selain mengalokasikan memori dan mengubah memori itu) - itu masih dianggap murni.
sumber
(value, exception) = func(arg)
dan menghapus kemungkinan melempar pengecualian (dalam beberapa bahasa dan untuk beberapa jenis pengecualian Anda benar-benar perlu mencantumkannya dengan definisi, sama seperti argumen atau nilai pengembalian). Sekarang akan sama murni seperti sebelumnya (asalkan selalu kembali alias melempar pengecualian yang diberikan argumen yang sama). Melempar pengecualian bukanlah efek samping, jika Anda menggunakan interpretasi ini.func
seperti itu, blok yang saya berikan bisa saja dioptimalkan, karena alih-alih membuka tumpukan, pengecualian yang dilempar akan dikodekanignoreThis
, yang kita abaikan. Tidak seperti pengecualian yang melepaskan tumpukan, pengecualian yang dikodekan dalam jenis kembali tidak melanggar kemurnian - dan itulah sebabnya banyak bahasa fungsional lebih suka menggunakannya.func(arg)
akan mengembalikan tuple(<junk>, TheException())
, dan jugaignoreThis
akan berisi tuple(<junk>, TheException())
, tetapi karena kita tidak pernah menggunakanignoreThis
pengecualian tidak akan berpengaruh pada apa pun.Salah satu pertimbangannya adalah bahwa blok try-catch bukanlah masalahnya. (Berdasarkan komentar saya untuk pertanyaan di atas).
Masalah utama adalah bahwa properti Utara adalah panggilan I / O .
Pada titik itu dalam eksekusi kode, program perlu memeriksa I / O yang disediakan oleh kode klien. (Tidak akan relevan bahwa input tersebut dalam bentuk delegasi, atau bahwa inputnya, secara nominal, sudah masuk).
Setelah Anda kehilangan kendali atas input, Anda tidak dapat memastikan fungsinya murni. (Apalagi jika fungsinya bisa melempar).
Saya tidak jelas mengapa Anda tidak ingin memeriksa panggilan untuk Pindah Kamar [Periksa]? Sesuai komentar saya untuk pertanyaan:
Seperti yang dikatakan Bart van Ingen Schenau di atas,
Secara umum, segala jenis pemuatan malas secara implisit akan mempertahankan kesalahan hingga saat itu.
Saya akan menyarankan menggunakan metode Pindah [Periksa] Kamar. Ini akan memungkinkan Anda untuk memisahkan aspek I / O yang tidak murni menjadi satu tempat.
Mirip dengan jawaban Robert Harvey :
Akan tergantung pada penulis kode untuk menentukan bagaimana menangani pengecualian (mungkin) dari input. Kemudian metode ini dapat mengembalikan objek Kamar, atau objek Ruang Null, atau mungkin mengeluarkan pengecualian.
Pada titik ini tergantung pada:
sumber
Hmm ... Saya merasa tidak benar tentang ini. Saya pikir apa yang Anda coba lakukan adalah memastikan orang lain melakukan pekerjaan mereka dengan benar, tanpa mengetahui apa yang mereka lakukan.
Karena fungsi ini dilewatkan oleh konsumen, yang dapat ditulis oleh Anda atau oleh orang lain.
Bagaimana jika fungsi pass in tidak valid untuk dijalankan pada saat objek Room dibuat?
Dari kode itu, saya tidak bisa memastikan apa yang dilakukan Utara.
Katakanlah, jika fungsinya untuk memesan kamar, dan itu membutuhkan waktu dan periode pemesanan, maka Anda akan mendapatkan pengecualian jika Anda belum memiliki informasi yang siap.
Bagaimana jika itu adalah fungsi yang berjalan lama? Anda tidak akan ingin memblokir program Anda saat membuat objek Kamar, bagaimana jika Anda perlu membuat objek 1000 Kamar?
Jika Anda adalah orang yang akan menulis konsumen yang mengkonsumsi kelas ini, Anda akan memastikan fungsi yang diteruskan ditulis dengan benar, bukan?
Jika ada orang lain yang menulis konsumen, dia mungkin tidak tahu kondisi "khusus" membuat objek Kamar, yang dapat menyebabkan eksekusi dan mereka akan menggaruk-garuk kepala mereka mencoba mencari tahu mengapa.
Hal lain adalah, tidak selalu merupakan hal yang baik untuk melemparkan pengecualian baru. Alasannya adalah tidak akan berisi informasi pengecualian asli. Anda tahu Anda mendapatkan kesalahan (mesaage ramah untuk pengecualian umum), tetapi Anda tidak akan tahu apa kesalahannya (referensi nol, indeks di luar batas, bagi dengan nol dll). Informasi ini sangat penting ketika kita perlu melakukan penyelidikan.
Anda harus menangani pengecualian hanya ketika Anda tahu apa dan bagaimana menanganinya.
Saya pikir kode asli Anda cukup baik, satu-satunya hal yang Anda mungkin ingin menangani pengecualian pada "Main" (atau lapisan apa pun yang tahu cara menanganinya).
sumber
Saya akan tidak setuju dengan jawaban lain dan mengatakan TIDAK . Pengecualian tidak menyebabkan fungsi murni menjadi tidak murni.
Setiap penggunaan pengecualian dapat ditulis ulang untuk menggunakan pemeriksaan eksplisit untuk hasil kesalahan. Pengecualian hanya bisa dianggap sebagai gula sintaksis di atas ini. Gula sintaksis yang mudah digunakan tidak membuat kode murni tidak murni.
sumber
f
dang
keduanya merupakan fungsi murni, makaf(x) + g(x)
harus memiliki hasil yang sama terlepas dari urutan yang Anda evaluasif(x)
dang(x)
. Tetapi jikaf(x)
melemparF
dang(x)
melemparG
, maka pengecualian yang dilemparkan dari ungkapan ini adalahF
jikaf(x)
dievaluasi terlebih dahulu danG
jikag(x)
dievaluasi dulu - ini bukan bagaimana fungsi murni seharusnya berperilaku! Ketika pengecualian dikodekan dalam tipe kembali, hasil itu akan berakhir seperti sesuatu yangError(F) + Error(G)
terlepas dari urutan evaluasi.