Apa yang Anda sebut fungsi di mana input yang sama akan selalu mengembalikan output yang sama, tetapi juga memiliki efek samping?

43

Katakanlah kita memiliki fungsi murni normal seperti

function add(a, b) {
  return a + b
}

Dan kemudian kita mengubahnya sedemikian rupa sehingga memiliki efek samping

function add(a, b) {
  writeToDatabase(Math.random())
  return a + b;
}

Itu tidak dianggap sebagai fungsi murni sejauh yang saya tahu karena saya sering mendengar orang menyebut fungsi murni "fungsi tanpa efek samping." Namun, ia berperilaku seperti fungsi murni sejauh fakta bahwa ia akan mengembalikan output yang sama untuk input yang sama.

Apakah ada nama yang berbeda untuk jenis fungsi ini, apakah itu tidak disebutkan namanya, atau apakah masih benar-benar murni dan saya salah tentang definisi kemurnian?

m0meni
sumber
91
"Bukan fungsi murni".
Ross Patterson
2
@RossPatterson itulah yang saya pikirkan juga, tetapi dengan bertanya saya belajar tentang transparansi referensial jadi saya senang saya tidak menyimpannya untuk diri saya sendiri.
m0meni
9
Jika writeToDatabasegagal itu dapat memicu pengecualian sehingga membuat addfungsi kedua Anda menghasilkan pengecualian kadang-kadang bahkan jika dipanggil dengan argumen yang sama yang sebelumnya tidak memiliki masalah ... sebagian besar waktu memiliki efek samping memperkenalkan jenis kondisi terkait kesalahan yang memecah "kemurnian input-output".
Bakuriu
25
Sesuatu yang selalu memberikan output yang sama untuk input yang diberikan disebut deterministik .
njzk2
2
@ njzk2: Benar, tetapi juga stateless . Sebuah deterministik stateful fungsi mungkin tidak memberikan output yang sama untuk setiap masukan. Contoh: F(x)didefinisikan untuk kembali truejika dipanggil dengan argumen yang sama dengan panggilan sebelumnya. Jelas dengan urutan {1,2,2} => {undefined, false, true}ini adalah deterministik, namun memberikan hasil yang berbeda untuk F(2).
MSalters

Jawaban:

85

Saya tidak yakin tentang definisi universal tentang kemurnian, tetapi dari sudut pandang Haskell (bahasa di mana programmer cenderung peduli tentang hal-hal seperti kemurnian dan transparansi referensial), hanya yang pertama dari fungsi Anda yang "murni". Versi kedua addtidak murni . Jadi sebagai jawaban atas pertanyaan Anda, saya akan menyebutnya "tidak murni";)

Menurut definisi ini, fungsi murni adalah fungsi yang:

  1. Hanya tergantung pada inputnya. Artinya, diberi input yang sama, itu akan selalu mengembalikan output yang sama.
  2. Jelas transparan: fungsi dapat dengan bebas diganti nilainya dan "perilaku" program tidak akan berubah.

Dengan definisi ini, jelas fungsi kedua Anda tidak dapat dianggap murni, karena melanggar aturan 2. Artinya, dua program berikut ini TIDAK setara:

function f(a, b) { 
    return add(a, b) + add(a, b);
}

dan

function g(a, b) {
    c = add(a, b);
    return c + c;
}

Ini karena meskipun kedua fungsi akan mengembalikan nilai yang sama, fungsi fakan menulis ke database dua kali tetapi gakan menulis sekali! Sangat mungkin bahwa menulis ke basis data adalah bagian dari perilaku yang dapat diamati dari program Anda, dalam hal ini saya telah menunjukkan versi kedua Anda addtidak "murni".

Jika menulis ke basis data bukan bagian yang dapat diamati dari perilaku program Anda, maka kedua versi adddapat dianggap setara dan murni. Tapi saya tidak bisa memikirkan skenario di mana menulis ke database tidak masalah. Bahkan masalah logging!

Andres F.
sumber
1
Bukankah "hanya tergantung pada inputnya" yang berlebihan diberikan transparansi referensial? Yang akan menyiratkan RT identik dengan kemurnian? (Saya semakin bingung tentang ini semakin banyak sumber yang saya cari)
Ixrec
Saya setuju itu membingungkan. Saya hanya bisa memikirkan contoh-contoh yang dibuat-buat. Say f(x)tidak hanya bergantung pada x, tetapi juga pada beberapa variabel global eksternal y. Kemudian, jika fmemiliki properti RT Anda dapat dengan bebas menukar semua kemunculannya dengan nilai pengembaliannya selama Anda tidak menyentuh y . Ya, contoh saya meragukan. Tetapi yang penting adalah: jika fmenulis ke database (atau menulis ke log) itu kehilangan properti RT: sekarang tidak masalah apakah Anda meninggalkan global yang ytidak tersentuh, Anda tahu arti dari perubahan program Anda tergantung pada apakah Anda benar-benar hubungi fatau cukup gunakan nilai pengembaliannya.
Andres F.
Huh Katakanlah kami memiliki fungsi yang murni kecuali efek samping dan juga dijamin memiliki perilaku di mana dua sampel Anda setara. (Sebenarnya kasus saya muncul sehingga tidak hipotetis.) Saya pikir kita belum selesai.
Yosua
2
Saya berpendapat fungsi kedua bisa melanggar aturan # 1 juga. Bergantung pada bahasa dan praktik penanganan kesalahan dari API basis data yang digunakan, fungsi tersebut mungkin tidak mengembalikan apa-apa sama sekali jika basis data tidak tersedia atau penulisan db gagal karena alasan tertentu.
Zach Lipton
1
Karena Haskell disebutkan: Di Haskell menambahkan efek samping seperti itu membutuhkan mengubah tanda tangan fungsi . (anggap saja seperti memberikan database asli sebagai input tambahan dan mendapatkan database yang dimodifikasi sebagai nilai balik fungsi tambahan). Sebenarnya dimungkinkan untuk memodelkan efek samping dengan cukup elegan dalam sistem tipe seperti itu, hanya saja bahasa arus utama saat ini tidak cukup peduli dengan efek samping dan kemurnian untuk melakukan ini.
ComicSansMS
19

Apa yang Anda sebut fungsi [yang] input yang sama akan selalu mengembalikan output yang sama, tetapi juga memiliki efek samping?

Fungsi seperti ini disebut

deterministik

Algoritma yang perilakunya dapat diprediksi sepenuhnya dari input.

termwiki.com

Tentang negara:

Bergantung pada definisi fungsi yang Anda gunakan, fungsi tidak memiliki status. Jika Anda berasal dari dunia berorientasi objek, ingatlah itu x.f(y)adalah metode. Sebagai fungsi akan terlihat seperti f(x,y). Dan jika Anda ingin menutup dengan lingkup leksikal tertutup ingat bahwa keadaan tidak berubah mungkin juga menjadi bagian dari ekspresi fungsi. Hanya keadaan yang bisa berubah yang akan memengaruhi fungsi yang sifatnya deterministik. Jadi f (x) = x + 1 adalah deterministik selama 1 tidak berubah. Tidak masalah di mana 1 disimpan.

Fungsi Anda keduanya deterministik. Fungsi pertama Anda juga murni. Kedua Anda tidak murni.

Fungsi murni

  1. Fungsi selalu mengevaluasi nilai hasil yang sama dengan nilai argumen yang sama. Nilai hasil fungsi tidak dapat bergantung pada informasi atau keadaan tersembunyi yang dapat berubah saat eksekusi program berlangsung atau antara berbagai eksekusi program, juga tidak dapat bergantung pada input eksternal dari perangkat I / O.

  2. Evaluasi hasil tidak menyebabkan efek samping atau keluaran yang dapat diamati secara semantik, seperti mutasi objek yang dapat berubah atau keluaran ke perangkat I / O.

wikipedia.org

Poin 1 berarti deterministik . Angka 2 berarti transparansi referensial . Bersama-sama mereka maksudkan fungsi murni hanya memungkinkan argumennya dan nilai yang dikembalikannya berubah. Tidak ada yang menyebabkan perubahan. Tidak ada yang berubah.

candied_orange
sumber
-1. Menulis ke basis data tergantung pada keadaan eksternal yang umumnya tidak dapat ditentukan dengan melihat pada input. Basis data mungkin tidak tersedia karena sejumlah alasan dan apakah operasi akan berhasil tidak dapat diprediksi. Ini bukan perilaku deterministik.
Frax
@Frax Memori sistem mungkin tidak tersedia. CPU mungkin tidak tersedia. Menjadi deterministik tidak menjamin kesuksesan. Ini menjamin bahwa perilaku sukses dapat diprediksi.
candied_orange
OOMing tidak spesifik untuk fungsi apa pun, ini adalah kategori masalah yang berbeda. Sekarang, mari kita lihat poin 1. definisi "fungsi murni" Anda (yang memang berarti "deterministik"): "Nilai hasil fungsi tidak dapat bergantung pada informasi atau keadaan tersembunyi yang dapat berubah saat eksekusi program berlangsung atau antara eksekusi yang berbeda dari program , juga tidak dapat bergantung pada input eksternal dari perangkat I / O ". Database adalah keadaan seperti itu, jadi fungsi OPs jelas tidak memenuhi kondisi ini - itu tidak deterministik.
Frax
@candied_orange Saya setuju jika penulisan ke DB hanya bergantung pada input. Tapi itu Math.random(). Jadi tidak, kecuali kita mengasumsikan PRNG (bukan RNG fisik) DAN menganggap bahwa PRNG menyatakan bagian dari input (yang bukan, rujukannya adalah hardcoded), itu tidak deterministik.
marstato
1
@candied_orange kutipan status deterministik Anda "sebuah algoritma yang perilakunya dapat diprediksi sepenuhnya dari input." Menulis kepada IO, bagi saya, jelas merupakan perilaku, bukan hasil.
marstato
9

Jika Anda tidak peduli dengan efek sampingnya, maka itu transparan secara referensi. Tentu saja mungkin bahwa Anda tidak peduli tetapi orang lain tidak, sehingga penerapan istilah ini tergantung pada konteks.

Saya tidak tahu istilah umum untuk properti yang Anda jelaskan, tetapi bagian penting adalah yang idempoten . Dalam ilmu komputer, sedikit berbeda dengan matematika *, fungsi idempoten adalah fungsi yang dapat diulangi dengan efek yang sama; artinya efek samping nett dari melakukannya berkali-kali sama dengan melakukannya sekali.

Jadi, jika efek samping Anda adalah memperbarui database dengan nilai tertentu di baris tertentu, atau membuat file dengan konten yang persis konsisten, maka itu akan menjadi idempoten , tetapi jika itu ditambahkan ke database, atau ditambahkan ke file , maka tidak akan.

Kombinasi fungsi idempoten mungkin atau mungkin juga tidak idempoten secara keseluruhan.

* Penggunaan idempoten secara berbeda dalam ilmu komputer daripada matematika tampaknya berasal dari penggunaan istilah matematika yang salah yang kemudian diadopsi karena konsep ini berguna.

Jon Hanna
sumber
3
istilah "transparan referensial" lebih tegas ditentukan daripada "ada yang peduli" atau tidak. bahkan jika kita mengabaikan masalah IO seperti masalah koneksi, hilang string koneksi, timeout, dll., maka masih mudah untuk menunjukkan bahwa program yang diganti (f x, f x)dengan let y = f x in (y, y)akan berjalan keluar dari ruang disk-pengecualian dua kali lebih cepat Anda dapat berargumen bahwa ini adalah kasus tepi Anda tidak peduli, tetapi dengan definisi fuzzy seperti itu, kami mungkin juga menyebut secara new Random().Next()transparan transparan karena, saya tidak peduli berapa nomor yang saya dapatkan.
sara
@ Kai: Tergantung pada konteksnya, Anda dapat mengabaikan efek samping. Di sisi lain, nilai kembali fungsi seperti acak bukanlah efek samping: itu adalah efek utamanya.
Giorgio
@Iorgio Random.Nextdi. NET memang memiliki efek samping. Sangat banyak sehingga. Jika Anda bisa Next, tetapkan ke variabel lalu panggil Nextlagi dan tetapkan ke variabel lain, kemungkinan mereka tidak akan sama. Mengapa? Karena memohon Nextmengubah beberapa keadaan internal tersembunyi di Randomobjek. Ini adalah kebalikan dari transparansi referensial. Saya tidak mengerti klaim Anda bahwa "efek utama" tidak dapat menjadi efek samping. Dalam kode imperatif lebih umum daripada tidak bahwa efek utama adalah efek samping, karena program imperatif pada dasarnya bersifat stateful.
sara
3

Saya tidak tahu bagaimana fungsi-fungsi seperti itu disebut (atau apakah bahkan ada beberapa nama sistematis), tetapi saya akan memanggil fungsi yang tidak murni (karena jawaban lain meringkuk) tetapi selalu mengembalikan hasil yang sama jika disertakan dengan parameter yang sama "fungsinya parameter "(dibandingkan dengan fungsi parameternya dan beberapa keadaan lainnya). Saya menyebutnya fungsi saja, tetapi sayangnya ketika kita mengatakan "fungsi" dalam konteks pemrograman, kita bermaksud sesuatu yang tidak harus menjadi fungsi yang sebenarnya sama sekali.

pengguna470365
sumber
1
Sepakat! Ini (secara informal) definisi matematis dari "fungsi", tetapi seperti yang Anda katakan, sayangnya "fungsi" berarti sesuatu yang berbeda dalam bahasa pemrograman, di mana lebih dekat dengan "prosedur langkah-demi-langkah yang diperlukan untuk mendapatkan nilai".
Andres F.
2

Ini pada dasarnya tergantung pada apakah Anda peduli dengan kenajisan atau tidak. Jika semantik dari tabel ini adalah bahwa Anda tidak peduli berapa banyak entri yang ada, maka itu murni. Lain, itu tidak murni.

Atau dengan kata lain, itu baik-baik saja asalkan optimasi berdasarkan kemurnian tidak merusak semantik program.

Contoh yang lebih realistis adalah jika Anda mencoba men-debug fungsi ini dan menambahkan pernyataan logging. Secara teknis, penebangan adalah efek samping. Apakah log membuatnya tidak murni? Tidak.

DeadMG
sumber
Yah, itu tergantung. Mungkin log membuatnya tidak murni, misalnya jika Anda peduli berapa kali, dan pada waktu apa, "INFO f () disebut" muncul di log Anda. Yang sering Anda lakukan :)
Andres F.
8
-1 Log memang penting. Pada sebagian besar platform, keluaran apa pun secara tersirat mensinkronkan utas eksekusi. Perilaku program Anda menjadi tergantung pada utas-utas lain menulis, pada penulis log eksternal, kadang-kadang pada membaca log, pada keadaan deskriptor file. Ini semurni ember kotoran.
Basilevs
@AndresF. Yah, Anda mungkin tidak peduli tentang berapa kali secara literal. Anda mungkin hanya peduli bahwa itu dicatat sebanyak fungsi dipanggil.
DeadMG
@ Basilevs Perilaku fungsi tidak bergantung pada mereka sama sekali. Jika penulisan log gagal, Anda cukup melanjutkan.
DeadMG
2
Ini masalah apakah Anda memilih untuk mendefinisikan logger untuk menjadi bagian dari lingkungan eksekusi atau tidak. Sebagai contoh lain, apakah fungsi murni saya masih murni jika saya melampirkan debugger ke proses dan menetapkan breakpoint di atasnya? Dari POV orang yang menggunakan debugger, jelas fungsi tersebut memiliki efek samping, tetapi biasanya kami menganalisis sebuah program dengan konvensi bahwa ini "tidak masuk hitungan". Hal yang sama dapat (tetapi tidak perlu) digunakan untuk logging yang digunakan untuk debugging, yang saya duga adalah mengapa jejak menyembunyikan pengotornya. Pencatatan misi-kritis, misalnya untuk audit, tentu saja merupakan efek samping yang signifikan.
Steve Jessop
1

Saya akan mengatakan bahwa hal terbaik untuk ditanyakan bukanlah bagaimana kita menyebutnya, tetapi bagaimana kita akan menganalisis sepotong kode. Dan pertanyaan kunci pertama saya dalam analisis semacam itu adalah:

  • Apakah efek samping tergantung pada argumen ke fungsi, atau hasil pada efek samping?
    • Tidak: "fungsi yang efektif" dapat direactored menjadi fungsi murni, tindakan yang efektif, dan mekanisme untuk menggabungkannya.
    • Ya: "fungsi efektif" adalah fungsi yang menghasilkan hasil monadik.

Ini mudah untuk diilustrasikan dalam Haskell (dan kalimat ini hanya setengah lelucon). Contoh kasus "tidak" akan menjadi sesuatu seperti ini:

double :: Num a => a -> IO a
double x = do
  putStrLn "I'm doubling some number"
  return (x*2)

Dalam contoh ini tindakan yang kita ambil (mencetak garis "I'm doubling some number") tidak berdampak pada hubungan antara xdan hasilnya. Ini berarti kita dapat melakukan refactor dengan cara ini (menggunakan Applicativekelas dan *>operatornya), yang menunjukkan bahwa fungsi dan efeknya sebenarnya ortogonal:

double :: Num a => a -> IO a
double x = action *> pure (function x)
  where
    -- The pure function 
    function x = x*2  
    -- The side effect
    action = putStrLn "I'm doubling some number"

Jadi dalam kasus ini saya, secara pribadi, akan mengatakan bahwa ini adalah kasus di mana Anda dapat memfaktorkan fungsi murni. Banyak pemrograman Haskell tentang hal ini — mempelajari cara memfaktorkan bagian murni dari kode yang efektif.

Contoh dari jenis "ya", di mana bagian-bagian yang murni dan berpengaruh tidak ortogonal:

double :: Num a => a -> IO a
double x = do
  putStrLn ("I'm doubling the number " ++ show x)
  return (x*2)

Sekarang, string yang Anda cetak tergantung pada nilai x. Namun, bagian fungsi (dikalikan xdua), tidak bergantung pada efek sama sekali, jadi kita masih bisa memfaktorkannya:

logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
  logger a
  return (function a)

double x = logged function logger
  where function = (*2) 
        logger x putStrLn ("I'm doubling the number " ++ show x)

Saya bisa terus menguraikan contoh lain, tetapi saya harap ini cukup untuk menggambarkan poin yang saya mulai dengan: Anda tidak "menyebutnya" sesuatu, Anda menganalisis bagaimana bagian-bagian yang murni dan berpengaruh berhubungan dan faktor mereka ketika itu untuk keuntungan Anda.

Ini adalah salah satu alasan Haskell menggunakan Monadkelasnya secara luas. Monads (antara lain) adalah alat untuk melakukan analisis dan refactoring semacam ini.

sakundim
sumber
-2

Fungsi yang dimaksudkan untuk menyebabkan efek samping sering disebut efektif . Contoh https://slpopejoy.github.io/posts/Effectful01.html

Ben Hutchison
sumber
Hanya jawaban untuk menyebutkan istilah yang dikenal luas yang efektif dan turun memilih .... Ketidaktahuan adalah kebahagiaan kurasa. ..
Ben Hutchison
"effectful" adalah kata yang dipilih oleh penulis postingan itu untuk berarti "memiliki efek samping." Dia mengatakannya sendiri.
Robert Harvey
Fungsi efektif Googling mengungkapkan banyak bukti yang merupakan istilah yang banyak digunakan. Posting blog diberikan sebagai salah satu dari banyak contoh, bukan sebagai definisi. Di lingkaran pemrograman fungsional di mana fungsi murni adalah default, ada kebutuhan untuk istilah positif untuk menggambarkan fungsi efek samping yang disengaja. Yaitu lebih dari sekadar tidak adanya kemurnian . Efektif adalah istilah itu. Sekarang, anggap diri Anda berpendidikan.
Ben Hutchison
Ah.
Robert Harvey