Bagaimana fungsi waktu ada dalam pemrograman fungsional?

646

Saya harus mengakui bahwa saya tidak tahu banyak tentang pemrograman fungsional. Saya membacanya dari sana-sini, dan jadi tahu bahwa dalam pemrograman fungsional, fungsi mengembalikan output yang sama, untuk input yang sama, tidak peduli berapa kali fungsi dipanggil. Persis seperti fungsi matematika yang mengevaluasi ke output yang sama untuk nilai yang sama dari parameter input yang terlibat dalam ekspresi fungsi.

Sebagai contoh, pertimbangkan ini:

f(x,y) = x*x + y; // It is a mathematical function

Tidak peduli berapa kali Anda menggunakan f(10,4), nilainya akan selalu 104. Dengan demikian, di mana pun Anda menulis f(10,4), Anda dapat menggantinya dengan 104, tanpa mengubah nilai keseluruhan ekspresi. Properti ini disebut sebagai transparansi referensial ekspresi.

Seperti kata Wikipedia ( tautan ),

Sebaliknya, dalam kode fungsional, nilai output suatu fungsi hanya bergantung pada argumen yang merupakan input ke fungsi, sehingga memanggil fungsi f dua kali dengan nilai yang sama untuk argumen x akan menghasilkan hasil yang sama f (x) pada kedua kali.

Bisakah fungsi waktu (yang mengembalikan waktu saat ini ) ada dalam pemrograman fungsional?

  • Jika ya, lalu bagaimana itu bisa ada? Apakah itu tidak melanggar prinsip pemrograman fungsional? Ini khususnya melanggar transparansi referensial yang merupakan salah satu properti pemrograman fungsional (jika saya memahaminya dengan benar).

  • Atau jika tidak, lalu bagaimana orang bisa tahu waktu saat ini dalam pemrograman fungsional?

Nawaz
sumber
15
Saya pikir sebagian besar (atau semua) bahasa fungsional tidak begitu ketat dan menggabungkan pemrograman fungsional dan imperatif. Setidaknya, ini kesan saya dari F #.
Alex F
13
@ Adam: Bagaimana penelepon mengetahui waktu saat ini di tempat pertama?
Nawaz
29
@ Adam: Sebenarnya itu ilegal (seperti dalam: tidak mungkin) dalam bahasa yang murni fungsional.
sepp2k
47
@ Adam: Cukup banyak. Bahasa tujuan umum yang murni biasanya menawarkan beberapa fasilitas untuk mendapatkan di "negara dunia" (yaitu hal-hal seperti waktu saat ini, file dalam direktori dll) tanpa melanggar transparansi referensial. Di Haskell itulah IO monad dan di Clean itu tipe dunia. Jadi, dalam bahasa-bahasa itu, fungsi yang membutuhkan waktu saat ini akan menganggapnya sebagai argumen atau akan perlu mengembalikan tindakan IO alih-alih hasil aktualnya (Haskell) atau mengambil negara dunia sebagai argumennya (Bersih).
sepp2k
12
Ketika berpikir tentang FP, mudah untuk dilupakan: komputer adalah bagian besar dari keadaan yang bisa berubah. FP tidak mengubahnya, itu hanya menyembunyikannya.
Daniel

Jawaban:

176

Cara lain untuk menjelaskannya adalah ini: tidak ada fungsi yang bisa mendapatkan waktu saat ini (karena terus berubah), tetapi suatu tindakan bisa mendapatkan waktu saat ini. Katakanlah itu getClockTimeadalah fungsi konstan (atau fungsi nullary, jika Anda suka) yang mewakili tindakan mendapatkan waktu saat ini. Ini tindakan adalah sama setiap kali tidak peduli bila digunakan sehingga merupakan konstan nyata.

Demikian juga, katakanlah printadalah fungsi yang membutuhkan representasi waktu dan mencetaknya ke konsol. Karena panggilan fungsi tidak dapat memiliki efek samping dalam bahasa fungsional murni, kami malah membayangkan bahwa itu adalah fungsi yang mengambil stempel waktu dan mengembalikan tindakan mencetaknya ke konsol. Sekali lagi, ini adalah fungsi nyata, karena jika Anda memberikan stempel waktu yang sama, itu akan mengembalikan tindakan yang sama dengan mencetaknya setiap waktu.

Sekarang, bagaimana Anda bisa mencetak waktu saat ini ke konsol? Nah, Anda harus menggabungkan dua tindakan. Jadi bagaimana kita bisa melakukan itu? Kita tidak bisa hanya lewat getClockTimeuntuk print, karena cetak mengharapkan timestamp, bukan tindakan. Tetapi kita dapat membayangkan bahwa ada operator, >>=yang menggabungkan dua tindakan, yang mendapat cap waktu, dan yang mengambil satu sebagai argumen dan mencetaknya. Menerapkan ini ke tindakan yang disebutkan sebelumnya, hasilnya adalah ... tadaaa ... tindakan baru yang mendapatkan waktu saat ini dan mencetaknya. Dan ini kebetulan persis bagaimana hal itu dilakukan di Haskell.

Prelude> System.Time.getClockTime >>= print
Fri Sep  2 01:13:23 東京 (標準時) 2011

Jadi, secara konseptual, Anda dapat melihatnya dengan cara ini: Program fungsional murni tidak melakukan I / O, itu mendefinisikan tindakan , yang kemudian dijalankan oleh sistem runtime. The tindakan yang sama setiap waktu, tapi hasil mengeksekusi itu tergantung pada keadaan ketika dieksekusi.

Saya tidak tahu apakah ini lebih jelas daripada penjelasan lainnya, tetapi kadang-kadang membantu saya untuk berpikir seperti ini.

Dainichi
sumber
33
Itu tidak meyakinkan bagi saya. Anda dengan mudah memanggil getClockTimetindakan alih-alih fungsi. Nah, jika Anda memanggilnya, maka panggil setiap tindakan fungsi , maka bahkan pemrograman imperatif akan menjadi pemrograman fungsional. Atau mungkin, Anda ingin menyebutnya pemrograman aksial .
Nawaz
92
@Nawaz: Hal utama yang perlu diperhatikan di sini adalah Anda tidak dapat menjalankan tindakan dari dalam suatu fungsi. Anda hanya dapat menggabungkan tindakan dan fungsi bersama untuk membuat tindakan baru. Satu-satunya cara untuk mengeksekusi suatu tindakan adalah dengan menuliskannya ke dalam maintindakan Anda . Ini memungkinkan kode fungsional murni dipisahkan dari kode imperatif, dan pemisahan ini diberlakukan oleh sistem tipe. Memperlakukan aksi sebagai objek kelas satu juga memungkinkan Anda untuk menyebarkannya dan membangun "struktur kontrol" Anda sendiri.
hammar
36
Tidak semua yang ada di Haskell adalah fungsi - itu omong kosong. Fungsi adalah sesuatu yang tipenya mengandung a ->- begitulah standar mendefinisikan istilah dan itulah satu-satunya definisi yang masuk akal dalam konteks Haskell. Jadi sesuatu yang jenis IO Whateverini tidak fungsi.
sepp2k
9
@ sepp2k Jadi, myList :: [a -> b] adalah fungsi? ;)
fuz
8
@ Thomas Saya benar-benar terlambat ke pesta, tapi saya hanya ingin menjelaskan ini: putStrLnbukan tindakan - itu adalah fungsi yang mengembalikan tindakan. getLineadalah variabel yang berisi tindakan. Tindakan adalah nilai, variabel dan fungsi adalah "wadah" / "label" yang kami berikan pada tindakan itu.
kqr
356

Iya dan tidak.

Bahasa pemrograman fungsional yang berbeda menyelesaikannya secara berbeda.

Dalam Haskell (yang sangat murni) semua hal ini harus terjadi dalam sesuatu yang disebut I / O Monad - lihat di sini .

Anda dapat menganggapnya sebagai mendapatkan input lain (dan output) ke fungsi Anda (negara-dunia) atau lebih mudah sebagai tempat di mana "kenajisan" seperti mendapatkan waktu yang berubah terjadi.

Bahasa lain seperti F # hanya memiliki beberapa ketidakmurnian bawaan sehingga Anda dapat memiliki fungsi yang mengembalikan nilai yang berbeda untuk input yang sama - seperti bahasa imperatif normal .

Seperti yang Jeffrey Burka katakan dalam komentarnya: Ini adalah pengantar bagus untuk I / O Monad langsung dari wiki Haskell.

Carsten
sumber
223
Hal penting yang harus disadari tentang IO monad di Haskell adalah bukan hanya peretasan untuk mengatasi masalah ini; Monad adalah solusi umum untuk masalah menentukan urutan tindakan dalam beberapa konteks. Satu konteks yang mungkin adalah dunia nyata, di mana kita memiliki monad IO. Konteks lain adalah dalam transaksi atom, di mana kita memiliki monad STM. Namun konteks lain adalah dalam penerapan algoritma prosedural (misalnya Knuth shuffle) sebagai fungsi murni, yang untuknya kita memiliki ST monad. Dan Anda juga bisa mendefinisikan monad Anda sendiri. Monad adalah sejenis titik koma yang kelebihan beban.
Paul Johnson
2
Saya merasa berguna untuk tidak menyebut hal-hal seperti mendapatkan waktu saat ini "fungsi" tetapi sesuatu seperti "prosedur" (meskipun bisa dibilang solusi Haskell adalah pengecualian untuk ini).
singpolyma
dari perspektif Haskell klasik "prosedur" (hal-hal yang memiliki tipe seperti '... -> ()') agak sepele sebagai fungsi murni dengan ... -> () tidak dapat melakukan apa-apa sama sekali.
Carsten
3
Istilah khas Haskell adalah "aksi".
Sebastian Redl
6
"Monad adalah semacam titik koma yang kelebihan beban." +1
user2805751
147

Dalam Haskell kita menggunakan konstruksi yang disebut monad untuk menangani efek samping. Monad pada dasarnya berarti bahwa Anda merangkum nilai ke dalam wadah dan memiliki beberapa fungsi untuk mengaitkan fungsi dari nilai ke nilai di dalam wadah. Jika wadah kami memiliki jenis:

data IO a = IO (RealWorld -> (a,RealWorld))

kita dapat dengan aman menerapkan tindakan IO. Tipe ini berarti: Tindakan tipe IOadalah fungsi, yang mengambil token tipe RealWorlddan mengembalikan token baru, bersama dengan hasilnya.

Gagasan di balik ini adalah bahwa setiap tindakan IO mengubah keadaan luar, diwakili oleh token ajaib RealWorld. Dengan menggunakan monads, seseorang dapat menghubungkan berbagai fungsi yang bermutasi bersama di dunia nyata. Fungsi paling penting dari monad adalah >>=, diikat mengikat :

(>>=) :: IO a -> (a -> IO b) -> IO b

>>=mengambil satu tindakan dan fungsi yang mengambil hasil dari tindakan ini dan membuat tindakan baru dari ini. Jenis kembali adalah tindakan baru. Misalnya, mari kita berpura-pura ada fungsi now :: IO String, yang mengembalikan sebuah String yang mewakili waktu saat ini. Kita dapat menghubungkannya dengan fungsi putStrLnuntuk mencetaknya:

now >>= putStrLn

Atau ditulis dalam do-Notasi, yang lebih dikenal oleh programmer imperatif:

do currTime <- now
   putStrLn currTime

Semua ini murni, karena kami memetakan mutasi dan informasi tentang dunia di luar RealWorldtoken. Jadi setiap kali Anda menjalankan tindakan ini, tentu saja Anda mendapatkan output yang berbeda, tetapi inputnya tidak sama: RealWorldtokennya berbeda.

fuz
sumber
3
-1: Saya tidak senang dengan RealWorldlayar asap. Namun, yang paling penting adalah bagaimana benda yang diklaim ini diteruskan dalam sebuah rantai. Bagian yang hilang adalah tempat dimulainya, di mana sumber atau koneksi ke dunia nyata adalah - dimulai dengan fungsi utama yang berjalan di monad IO.
u0b34a0f6ae
2
@ kaizer.se Anda dapat memikirkan RealWorldobjek global yang dilewatkan ke dalam program ketika dimulai.
fuz
6
Pada dasarnya, mainfungsi Anda membutuhkan RealWorldargumen. Hanya setelah eksekusi itu diteruskan.
Louis Wasserman
13
Anda tahu, alasan mengapa mereka menyembunyikan RealWorlddan hanya menyediakan fungsi kecil untuk mengubahnya seperti putStrLn, adalah karena beberapa programmer Haskell tidak berubah RealWorlddengan salah satu program mereka sehingga alamat dan tanggal lahir Haskell Curry sedemikian rupa sehingga mereka menjadi tetangga sebelah. tumbuh dewasa (ini dapat merusak kontinum ruang-waktu sedemikian rupa hingga melukai bahasa pemrograman Haskell.)
PyRulez
2
RealWorld -> (a, RealWorld) tidak memecah sebagai metafora bahkan di bawah konkurensi, selama Anda ingat bahwa dunia nyata mungkin diubah oleh bagian lain dari alam semesta di luar fungsi Anda (atau proses Anda saat ini) setiap saat. Jadi (a) metafora tidak rusak, dan (b) setiap kali nilai yang memiliki RealWorldjenisnya diteruskan ke suatu fungsi, fungsi tersebut harus dievaluasi kembali, karena dunia nyata akan berubah sementara itu ( yang dimodelkan sebagai @fuz menjelaskan, memberikan kembali 'nilai token' yang berbeda setiap kali kita berinteraksi dengan dunia nyata).
Qqwy
73

Sebagian besar bahasa pemrograman fungsional tidak murni, yaitu mereka memungkinkan fungsi tidak hanya bergantung pada nilai-nilai mereka. Dalam bahasa-bahasa itu sangat mungkin untuk memiliki fungsi mengembalikan waktu saat ini. Dari bahasa yang Anda beri tag pertanyaan ini berlaku untuk Scala dan F # (serta sebagian besar varian ML lainnya ).

Dalam bahasa seperti Haskell dan Clean , yang murni, situasinya berbeda. Dalam Haskell waktu saat ini tidak akan tersedia melalui fungsi, tetapi apa yang disebut tindakan IO, yang merupakan cara Haskell untuk mengenkapsulasi efek samping.

Dalam Bersihkan itu akan menjadi fungsi, tetapi fungsi akan mengambil nilai dunia sebagai argumennya dan mengembalikan nilai dunia yang baru (selain waktu saat ini) sebagai hasilnya. Sistem tipe akan memastikan bahwa setiap nilai dunia dapat digunakan hanya sekali (dan setiap fungsi yang mengkonsumsi nilai dunia akan menghasilkan yang baru). Dengan cara ini fungsi waktu harus dipanggil dengan argumen yang berbeda setiap kali dan dengan demikian akan diizinkan untuk mengembalikan waktu yang berbeda setiap kali.

sepp2k
sumber
2
Ini membuatnya terdengar seolah-olah Haskell dan Clean melakukan hal yang berbeda. Dari apa yang saya mengerti, mereka melakukan hal yang sama, hanya saja Haskell menawarkan sintaks yang lebih baik (?) Untuk mencapai ini.
Konrad Rudolph
27
@ Konrad: Mereka melakukan hal yang sama dalam arti bahwa keduanya menggunakan fitur tipe sistem untuk efek samping abstrak, tapi hanya itu saja. Perhatikan bahwa sangat baik untuk menjelaskan mono IO dalam hal tipe dunia, tetapi standar Haskell tidak benar-benar mendefinisikan tipe dunia dan itu sebenarnya tidak mungkin untuk mendapatkan nilai tipe Dunia di Haskell (sementara itu sangat mungkin dan memang perlu di bersihkan). Haskell lebih lanjut tidak memiliki keunikan mengetik sebagai fitur sistem tipe, jadi jika itu memberi Anda akses ke Dunia, itu tidak dapat memastikan bahwa Anda menggunakannya dengan cara yang murni seperti cara Clean.
sepp2k
51

"Waktu sekarang" bukan fungsi. Itu adalah parameter. Jika kode Anda tergantung pada waktu sekarang, itu berarti kode Anda diparameterisasi berdasarkan waktu.

Vlad Patryshev
sumber
22

Ini benar-benar dapat dilakukan dengan cara yang murni fungsional. Ada beberapa cara untuk melakukannya, tetapi yang paling sederhana adalah mengembalikan fungsi waktu tidak hanya waktu tetapi juga fungsi yang harus Anda panggil untuk mendapatkan pengukuran waktu berikutnya .

Di C # Anda bisa menerapkannya seperti ini:

// Exposes mutable time as immutable time (poorly, to illustrate by example)
// Although the insides are mutable, the exposed surface is immutable.
public class ClockStamp {
    public static readonly ClockStamp ProgramStartTime = new ClockStamp();
    public readonly DateTime Time;
    private ClockStamp _next;

    private ClockStamp() {
        this.Time = DateTime.Now;
    }
    public ClockStamp NextMeasurement() {
        if (this._next == null) this._next = new ClockStamp();
        return this._next;
    }
}

(Perlu diingat bahwa ini adalah contoh yang dimaksudkan untuk menjadi sederhana, tidak praktis. Secara khusus, daftar node tidak dapat berupa sampah yang dikumpulkan karena mereka di-root oleh ProgramStartTime.)

Kelas 'ClockStamp' ini bertindak seperti daftar tertaut yang tidak dapat diubah, tetapi sebenarnya node dihasilkan sesuai permintaan sehingga dapat berisi waktu 'saat ini'. Setiap fungsi yang ingin mengukur waktu harus memiliki parameter 'clockStamp' dan juga harus mengembalikan pengukuran waktu terakhir dalam hasilnya (sehingga penelepon tidak melihat pengukuran lama), seperti ini:

// Immutable. A result accompanied by a clockstamp
public struct TimeStampedValue<T> {
    public readonly ClockStamp Time;
    public readonly T Value;
    public TimeStampedValue(ClockStamp time, T value) {
        this.Time = time;
        this.Value = value;
    }
}

// Times an empty loop.
public static TimeStampedValue<TimeSpan> TimeALoop(ClockStamp lastMeasurement) {
    var start = lastMeasurement.NextMeasurement();
    for (var i = 0; i < 10000000; i++) {
    }
    var end = start.NextMeasurement();
    var duration = end.Time - start.Time;
    return new TimeStampedValue<TimeSpan>(end, duration);
}

public static void Main(String[] args) {
    var clock = ClockStamp.ProgramStartTime;
    var r = TimeALoop(clock);
    var duration = r.Value; //the result
    clock = r.Time; //must now use returned clock, to avoid seeing old measurements
}

Tentu saja, agak merepotkan harus melewati pengukuran terakhir masuk dan keluar, masuk dan keluar, masuk dan keluar. Ada banyak cara untuk menyembunyikan pelat tungku, terutama pada tingkat desain bahasa. Saya pikir Haskell menggunakan trik semacam ini dan kemudian menyembunyikan bagian yang jelek dengan menggunakan monads.

Craig Gidney
sumber
Menarik, tetapi i++di dalam for loop tidak transparan transparan;)
snim2
@ snim2 aku tidak sempurna. : P Bersenang-senang dalam kenyataan bahwa mutableness kotor tidak mempengaruhi transparansi referensial dari hasil. Jika Anda melewati 'pengukuran terakhir' yang sama dalam dua kali, Anda mendapatkan pengukuran basi berikutnya dan mengembalikan hasil yang sama.
Craig Gidney
@ Strilanc Terima kasih untuk ini. Saya pikir dalam kode imperatif, jadi menarik untuk melihat konsep fungsional dijelaskan dengan cara ini. Saya kemudian bisa membayangkan bahasa di mana ini alami dan secara sintaksis bersih.
WW.
Anda sebenarnya bisa menggunakan cara monad di C # juga, sehingga menghindari berlalunya prangko waktu yang eksplisit. Anda butuh sesuatu seperti struct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }. Tetapi kode dengan ini masih tidak akan sebagus Haskell dengan dosintaksis.
leftaroundabout
@leftaroundabout Anda bisa mengurutkan berpura-pura bahwa Anda memiliki monad di C # dengan menerapkan fungsi bind sebagai metode yang disebut SelectMany, yang memungkinkan sintaks pemahaman kueri. Anda masih tidak dapat memprogram secara polimorfik di atas monad, jadi semua ini merupakan perjuangan berat melawan sistem tipe lemah :(
sara
16

Saya terkejut bahwa tidak ada jawaban atau komentar yang menyebutkan coalgebras atau coinduction. Biasanya, coinduction disebutkan ketika beralasan tentang struktur data yang tak terbatas, tetapi juga berlaku untuk aliran pengamatan tanpa akhir, seperti register waktu pada CPU. Sebuah model coalgebra menyembunyikan keadaan; dan model coinduction mengamati keadaan itu. (Model induksi normal membangun kondisi.)

Ini adalah topik hangat dalam Pemrograman Fungsional Reaktif. Jika Anda tertarik pada hal-hal semacam ini, baca ini: http://digitalcommons.ohsu.edu/csetech/91/ (28 hal.)

Jeffrey Aguilera
sumber
3
Dan bagaimana hubungannya dengan pertanyaan ini?
Nawaz
5
Pertanyaan Anda adalah tentang memodelkan perilaku yang tergantung waktu dengan cara yang murni fungsional, misalnya, fungsi yang mengembalikan jam sistem saat ini. Anda dapat mengaitkan sesuatu yang setara dengan monad IO melalui semua fungsi dan pohon dependensinya untuk mendapatkan akses ke status itu; atau Anda bisa memodelkan negara bagian dengan mendefinisikan aturan observasi daripada aturan konstruktif. Inilah sebabnya mengapa memodelkan keadaan kompleks secara induktif dalam pemrograman fungsional tampaknya sangat tidak wajar, karena keadaan tersembunyi benar-benar merupakan sifat koinduktif .
Jeffrey Aguilera
Sumber hebat! Adakah yang lebih baru? Komunitas JS tampaknya masih berjuang dengan abstraksi data stream.
Dmitri Zaitsev
12

Ya, mungkin fungsi murni mengembalikan waktu, jika diberikan waktu itu sebagai parameter. Argumen waktu berbeda, hasil waktu berbeda. Kemudian bentuklah fungsi waktu lainnya juga dan gabungkan dengan kosakata fungsi sederhana (-waktu) -transformasi (orde tinggi). Karena pendekatan ini tidak memiliki kewarganegaraan, waktu di sini dapat berkelanjutan (resolusi-independen) daripada diskrit, sangat meningkatkan modularitas . Intuisi ini adalah dasar dari Pemrograman Fungsional Reaktif (FRP).

Conal
sumber
11

Iya! Anda benar! Now () atau CurrentTime () atau metode tanda tangan apa pun dari cita rasa tersebut tidak menunjukkan transparansi referensial dalam satu cara. Tetapi dengan instruksi ke kompiler itu diparameterisasi oleh input jam sistem.

Dengan output, Now () mungkin terlihat tidak mengikuti transparansi referensial. Tetapi perilaku aktual dari jam sistem dan fungsi di atasnya mematuhi transparansi referensial.

MduSenthil
sumber
11

Ya, fungsi mendapatkan waktu dapat ada dalam pemrograman fungsional menggunakan versi yang sedikit dimodifikasi pada pemrograman fungsional yang dikenal sebagai pemrograman fungsional tidak murni (default atau yang utama adalah pemrograman fungsional murni).

Dalam hal mendapatkan waktu (atau membaca file, atau meluncurkan rudal) kode perlu berinteraksi dengan dunia luar untuk menyelesaikan pekerjaan dan dunia luar ini tidak didasarkan pada dasar murni pemrograman fungsional. Untuk memungkinkan dunia pemrograman fungsional murni untuk berinteraksi dengan dunia luar tidak murni ini, orang-orang telah memperkenalkan pemrograman fungsional tidak murni. Bagaimanapun, perangkat lunak yang tidak berinteraksi dengan dunia luar tidak ada gunanya selain melakukan beberapa perhitungan matematis.

Beberapa bahasa pemrograman pemrograman fungsional memiliki fitur pengotor ini bawaan di dalamnya sehingga tidak mudah untuk memisahkan kode mana yang tidak murni dan mana yang murni (seperti F #, dll.) Dan beberapa bahasa pemrograman fungsional memastikan bahwa ketika Anda melakukan beberapa hal yang tidak murni kode itu jelas menonjol dibandingkan dengan kode murni, seperti Haskell.

Cara lain yang menarik untuk melihat ini adalah bahwa fungsi waktu Anda dalam pemrograman fungsional akan mengambil objek "dunia" yang memiliki keadaan dunia saat ini seperti waktu, jumlah orang yang hidup di dunia, dll. Lalu dapatkan waktu dari dunia mana objek akan selalu murni yaitu Anda lulus dalam keadaan dunia yang sama Anda akan selalu mendapatkan waktu yang sama.

Ankur
sumber
1
"Bagaimanapun, sebuah perangkat lunak yang tidak berinteraksi dengan dunia luar tidak ada gunanya selain melakukan beberapa perhitungan matematis." Sejauh yang saya mengerti, bahkan dalam hal ini input ke perhitungan akan dikodekan dalam program, juga tidak terlalu berguna. Segera setelah Anda ingin membaca input data ke perhitungan matematis Anda dari file atau terminal, Anda memerlukan kode yang tidak murni.
Giorgio
1
@Ankur: Itu adalah hal yang persis sama. Jika program berinteraksi dengan sesuatu yang lain dari pada itu sendiri (mis. Dunia melalui keyboard mereka, bisa dikatakan) itu masih tidak murni.
identitas
1
@Ankur: Ya, saya pikir Anda benar! Meskipun mungkin tidak terlalu praktis untuk melewatkan data input besar pada baris perintah, ini bisa menjadi cara murni untuk melakukannya.
Giorgio
2
Memiliki "objek dunia" termasuk jumlah orang yang hidup di dunia meningkatkan komputer pelaksana ke tingkat yang hampir mahatahu. Saya pikir kasus normalnya adalah itu termasuk hal-hal seperti berapa banyak file pada HD Anda dan apa direktori home dari pengguna saat ini.
ziggystar
4
@ziggystar - "objek dunia" sebenarnya tidak termasuk apa pun - itu hanya proxy untuk perubahan keadaan dunia di luar program. Satu-satunya tujuan adalah untuk secara eksplisit menandai keadaan bisa berubah dengan cara yang dapat dikenali oleh sistem tipe itu.
Kris Nuttycombe
7

Pertanyaan Anda mengonfigurasi dua ukuran terkait bahasa komputer: fungsional / imperatif dan murni / tidak murni.

Bahasa fungsional mendefinisikan hubungan antara input dan output fungsi, dan bahasa imperatif menjelaskan operasi spesifik dalam urutan tertentu untuk dilakukan.

Bahasa murni tidak membuat atau bergantung pada efek samping, dan bahasa yang tidak murni menggunakannya di seluruh.

Seratus persen program murni pada dasarnya tidak berguna. Mereka mungkin melakukan perhitungan yang menarik, tetapi karena mereka tidak dapat memiliki efek samping mereka tidak memiliki input atau output sehingga Anda tidak akan pernah tahu apa yang mereka hitung.

Agar bermanfaat sama sekali, suatu program harus setidaknya tidak murni. Salah satu cara untuk membuat program murni bermanfaat adalah dengan meletakkannya di dalam pembungkus tipis yang tidak murni. Seperti program Haskell yang belum teruji ini:

-- this is a pure function, written in functional style.
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

-- This is an impure wrapper around the pure function, written in imperative style
-- It depends on inputs and produces outputs.
main = do
    putStrLn "Please enter the input parameter"
    inputStr <- readLine
    putStrLn "Starting time:"
    getCurrentTime >>= print
    let inputInt = read inputStr    -- this line is pure
    let result = fib inputInt       -- this is also pure
    putStrLn "Result:"
    print result
    putStrLn "Ending time:"
    getCurrentTime >>= print
NovaDenizen
sumber
4
Akan sangat membantu jika Anda dapat mengatasi masalah spesifik tentang mendapatkan waktu, dan menjelaskan sedikit tentang sejauh mana kami menganggap IOnilai dan hasil murni.
AndrewC
Bahkan, bahkan 100% program murni memanaskan CPU, yang merupakan efek samping.
Jörg W Mittag
3

Anda sedang membicarakan topik yang sangat penting dalam pemrograman fungsional, yaitu melakukan I / O. Cara banyak bahasa murni mengatasinya adalah dengan menggunakan bahasa khusus domain yang disematkan, misalnya, subbahasa yang tugasnya menyandikan tindakan , yang dapat memberikan hasil.

Runtime Haskell misalnya mengharapkan saya untuk mendefinisikan tindakan yang disebut mainyang terdiri dari semua tindakan yang membentuk program saya. Runtime kemudian menjalankan tindakan ini. Sebagian besar waktu, dalam melakukan itu mengeksekusi kode murni. Dari waktu ke waktu runtime akan menggunakan data yang dikomputasi untuk melakukan I / O dan mengembalikan data kembali ke kode murni.

Anda mungkin mengeluh bahwa ini kedengarannya seperti curang, dan memang demikian: dengan mendefinisikan tindakan dan mengharapkan runtime untuk mengeksekusinya, pemrogram dapat melakukan semua yang dapat dilakukan oleh program normal. Tetapi sistem tipe Haskell yang kuat menciptakan penghalang yang kuat antara bagian murni dan "tidak murni" dari program: Anda tidak bisa hanya menambahkan, katakanlah, dua detik ke waktu CPU saat ini, dan mencetaknya, Anda harus menentukan tindakan yang menghasilkan saat ini Waktu CPU, dan meneruskan hasilnya ke tindakan lain yang menambahkan dua detik dan mencetak hasilnya. Menulis terlalu banyak program dianggap sebagai gaya yang buruk, karena itu membuatnya sulit untuk menyimpulkan efek yang ditimbulkan, dibandingkan dengan jenis Haskell yang memberi tahu kita semua yang dapat kita ketahui tentang nilai.

Contoh: clock_t c = time(NULL); printf("%d\n", c + 2);dalam C, vs. main = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)di Haskell. Operator >>=digunakan untuk menyusun tindakan, meneruskan hasil dari yang pertama ke fungsi yang menghasilkan tindakan kedua. Ini terlihat sangat misterius, kompiler Haskell mendukung sintaksis gula yang memungkinkan kita untuk menulis kode yang terakhir sebagai berikut:

type Clock = Integer -- To make it more similar to the C code

-- An action that returns nothing, but might do something
main :: IO ()
main = do
    -- An action that returns an Integer, which we view as CPU Clock values
    c <- getCPUTime :: IO Clock
    -- An action that prints data, but returns nothing
    print (c + 2*1000*1000*1000*1000) :: IO ()

Yang terakhir terlihat cukup penting, bukan?

MauganRa
sumber
1

Jika ya, lalu bagaimana itu bisa ada? Apakah itu tidak melanggar prinsip pemrograman fungsional? Ini khususnya melanggar transparansi referensial

Itu tidak ada dalam arti fungsional murni.

Atau jika tidak, lalu bagaimana orang bisa tahu waktu saat ini dalam pemrograman fungsional?

Pertama-tama mungkin berguna untuk mengetahui bagaimana suatu waktu diambil pada komputer. Pada dasarnya ada sirkuit onboard yang melacak waktu (yang merupakan alasan komputer biasanya membutuhkan baterai sel kecil). Kemudian mungkin ada beberapa proses internal yang menetapkan nilai waktu pada register memori tertentu. Yang pada dasarnya bermuara pada nilai yang dapat diambil oleh CPU.


Untuk Haskell, ada konsep 'tindakan IO' yang mewakili jenis yang dapat dibuat untuk melakukan beberapa proses IO. Jadi alih-alih mereferensikan timenilai, kami mereferensikan IO Timenilai. Semua ini akan berfungsi murni. Kami tidak mereferensikan timetetapi sesuatu di sepanjang baris 'baca nilai register waktu' .

Ketika kita benar-benar menjalankan program Haskell, tindakan IO sebenarnya akan terjadi.

Chris Stryczynski
sumber