Saya memiliki metode ekstensi berikut:
public static IEnumerable<T> Apply<T>(
[NotNull] this IEnumerable<T> source,
[NotNull] Action<T> action)
where T : class
{
source.CheckArgumentNull("source");
action.CheckArgumentNull("action");
return source.ApplyIterator(action);
}
private static IEnumerable<T> ApplyIterator<T>(this IEnumerable<T> source, Action<T> action)
where T : class
{
foreach (var item in source)
{
action(item);
yield return item;
}
}
Itu hanya berlaku tindakan untuk setiap item dari urutan sebelum mengembalikannya.
Saya bertanya-tanya apakah saya harus menerapkan Pure
atribut (dari anotasi Resharper) ke metode ini, dan saya bisa melihat argumen yang mendukung dan menentangnya.
Pro:
- tegasnya, itu adalah murni; hanya menyebutnya pada urutan tidak mengubah urutan (mengembalikan urutan baru) atau membuat perubahan keadaan yang dapat diamati
- memanggilnya tanpa menggunakan hasilnya jelas merupakan kesalahan, karena itu tidak berpengaruh kecuali urutannya disebutkan, jadi saya ingin Resharper memperingatkan saya jika saya melakukannya.
Cons:
- meskipun
Apply
metode itu sendiri murni, menyebutkan urutan yang dihasilkan akan membuat perubahan keadaan yang dapat diamati (yang merupakan titik metode). Misalnya,items.Apply(i => i.Count++)
akan mengubah nilai item setiap kali itu disebutkan. Jadi menerapkan atribut Pure mungkin menyesatkan ...
Bagaimana menurut anda? Haruskah saya menerapkan atribut atau tidak?
c#
pure-function
Thomas Levesque
sumber
sumber
Jawaban:
Tidak itu tidak murni, karena memiliki efek samping. Konkret itu memanggil
action
setiap item. Juga, ini bukan threadsafe.Sifat utama dari fungsi murni adalah dapat dipanggil beberapa kali dan tidak pernah melakukan hal lain selain mengembalikan nilai yang sama. Itu bukan kasusmu. Juga, menjadi murni berarti Anda tidak menggunakan apa pun selain parameter input. Ini berarti dapat dipanggil dari utas kapan saja dan tidak menyebabkan perilaku yang tidak terduga. Sekali lagi, itu bukan kasus fungsi Anda.
Juga, Anda mungkin salah pada satu hal: kemurnian fungsi bukan masalah pro atau kontra. Bahkan satu keraguan, bahwa itu dapat memiliki efek samping, sudah cukup untuk membuatnya tidak murni.
Eric Lippert memunculkan poin yang bagus. Saya akan menggunakan http://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx sebagai bagian dari argumen balasan saya. Terutama garis
Katakanlah kita membuat metode seperti ini:
Pertama, ini menganggap itu
GetEnumerator
juga murni (saya tidak dapat menemukan sumbernya). Jika ya, maka menurut aturan di atas, kita dapat menjelaskan metode ini dengan [Murni], karena hanya memodifikasi contoh yang dibuat di dalam tubuh itu sendiri. Setelah itu kita dapat menyusun ini danApplyIterator
, yang seharusnya menghasilkan fungsi murni, kan?Tidak Komposisi ini tidak murni, bahkan ketika kedua
Count
danApplyIterator
murni. Tapi saya mungkin akan membangun argumen ini pada premis yang salah. Saya berpikir bahwa gagasan yang dibuat dalam metode ini dikecualikan dari aturan kemurnian salah atau setidaknya tidak cukup spesifik.sumber
where T : class
, tetapi jika OP sederhananyawhere T : strut
akan menjadi murni.sequence.Apply(action)
tidak memiliki efek samping; jika ya, nyatakan efek samping yang dimilikinya. Sekarang, panggilansequence.Apply(action).GetEnumerator().MoveNext()
memiliki efek samping, tetapi kita sudah tahu itu; itu bermutasi pencacah! Mengapa harussequence.Apply(action)
dianggap tidak murni karena panggilanMoveNext
itu tidak murni, tetapisequence.Where(predicate)
dianggap murni?sequence.Where(predicate).GetEnumerator().MoveNext()
setiap bit tidak murni.GetEnumerator
produk, selain mengalokasikan enumerator dalam kondisi awal?Saya tidak setuju dengan jawaban Euforia dan Robert Harvey . Tentu saja itu adalah fungsi murni; masalahnya adalah
sangat tidak jelas apa arti "itu" yang pertama. Jika "itu" berarti salah satu dari fungsi itu, maka itu tidak benar; tak satu pun dari fungsi-fungsi itu melakukan itu; yang
MoveNext
dari pencacah urutan melakukan itu, dan "kembali" item melaluiCurrent
properti, tidak dengan kembali.Urutan-urutan itu disebutkan dengan malas , tidak bersemangat sehingga tentu saja bukan kasus bahwa tindakan diterapkan sebelum urutan dikembalikan oleh
Apply
. Tindakan diterapkan setelah urutan dikembalikan, jikaMoveNext
dipanggil pada enumerator.Seperti yang Anda perhatikan, fungsi-fungsi ini mengambil tindakan dan urutan dan mengembalikan urutan; output tergantung pada input, dan tidak ada efek samping yang dihasilkan, jadi ini adalah fungsi murni ..
Sekarang, jika Anda membuat enumerator dari urutan yang dihasilkan dan kemudian memanggil MoveNext pada iterator itu maka metode MoveNext tidak murni, karena memanggil tindakan dan menghasilkan efek samping. Tapi kita sudah tahu bahwa MoveNext tidak murni karena itu memecah enumerator!
Sekarang, untuk pertanyaan Anda, sebaiknya Anda menerapkan atribut: Saya tidak akan menerapkan atribut karena saya tidak akan menulis metode ini di tempat pertama . Jika saya ingin menerapkan tindakan ke urutan maka saya menulis
yang jelas baik.
sumber
ForEach
metode ekstensi, yang sengaja bukan bagian dari Linq karena tujuannya adalah untuk menghasilkan efek samping ...Any()
dari waktu ke waktu; tindakan akan dilakukan berulang kali, tetapi hanya pada item pertama! Urutan harus merupakan urutan nilai ; jika Anda menginginkan urutan tindakan maka buatlahIEnumerable<Action>
.action
, jadi kemurnianaction
tidak relevan. Aku tahu itu terlihat seperti itu panggilanaction
, tetapi metode ini adalah sintaksis gula untuk dua metode, salah satu yang mengembalikan enumerator, dan salah satu yang merupakanMoveNext
pencacah. Yang pertama jelas murni, dan yang kedua jelas tidak. Lihatlah seperti ini: apakah Anda akan mengatakan ituIEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); }
murni? Karena itulah fungsi sebenarnya ini.ApplyIterator
method mengembalikan segera . Tidak ada kode di tubuhApplyIterator
dijalankan sampai panggilan pertama keMoveNext
pada enumerator objek yang dikembalikan. Sekarang setelah Anda tahu itu, Anda dapat menyimpulkan jawaban untuk teka-teki ini: blogs.msdn.com/b/ericlippert/archive/2007/09/05/... Jawabannya ada di sini: blogs.msdn.com/b/ericlippert/archive / 2007/09/06 / ...Itu bukan fungsi murni, jadi menerapkan atribut Murni menyesatkan.
Fungsi murni tidak mengubah koleksi asli, dan tidak masalah apakah Anda melewati tindakan yang tidak berpengaruh atau tidak; itu masih merupakan fungsi yang tidak murni karena tujuannya adalah untuk menyebabkan efek samping.
Jika Anda ingin menjadikan fungsi ini murni, salin koleksi ke koleksi baru, terapkan perubahan yang diambil Action ke koleksi baru, dan kembalikan koleksi baru, biarkan koleksi asli tidak berubah.
sumber
item
adalah tipe referensi, itu memodifikasi koleksi asli, meskipun Anda kembaliitem
dalam iterator. Lihat stackoverflow.com/questions/1538301action
mungkin memiliki efek samping selain memodifikasi item yang diteruskan ke sana.()=>{}
dapat dikonversi ke Action, dan ini adalah fungsi murni. Keluarannya semata-mata bergantung pada inputnya dan tidak memiliki efek samping yang dapat diamati.Menurut pendapat saya, fakta bahwa ia menerima Tindakan (dan bukan sesuatu seperti PureAction) membuatnya tidak murni.
Dan saya bahkan tidak setuju dengan Eric Lippert. Dia menulis ini "() => {} dapat dikonversi ke Aksi, dan ini adalah fungsi murni. Keluarannya hanya bergantung pada inputnya dan tidak memiliki efek samping yang dapat diamati".
Nah, bayangkan bahwa alih-alih menggunakan delegasi, ApplyIterator menggunakan metode bernama Action.
Jika Action murni, maka ApplyIterator juga murni. Jika Action tidak murni, maka ApplyIterator tidak boleh murni.
Mengingat jenis delegasi (bukan nilai yang diberikan sebenarnya), kami tidak memiliki jaminan bahwa itu akan murni, sehingga metode tersebut akan berperilaku sebagai metode murni hanya ketika delegasi itu murni. Jadi, untuk membuatnya benar-benar murni, ia harus menerima delegasi murni (dan yang ada, kita dapat mendeklarasikan delegasi sebagai [Murni], sehingga kita dapat memiliki PureAction).
Menjelaskannya secara berbeda, metode Murni harus selalu memberikan hasil yang sama dengan input yang sama dan tidak menghasilkan perubahan yang dapat diamati. ApplyIterator dapat diberikan sumber dan delegasi yang sama dua kali tetapi, jika delegasi mengubah tipe referensi, eksekusi selanjutnya akan memberikan hasil yang berbeda. Contoh: Delegasi melakukan sesuatu seperti item.Content + = "Berubah";
Jadi, menggunakan ApplyIterator di atas daftar "wadah string" (objek dengan properti Konten dari string tipe), kita mungkin memiliki nilai-nilai asli ini:
Setelah eksekusi pertama, daftar akan memiliki ini:
Dan ini yang ketiga kalinya:
Jadi, kami mengubah isi daftar karena delegasi tidak murni dan tidak ada optimasi dapat dilakukan untuk menghindari mengeksekusi panggilan 3 kali jika dipanggil 3 kali, karena setiap eksekusi akan menghasilkan hasil yang berbeda.
sumber