Sebuah fungsi yang menerima beberapa nilai dan mengembalikan beberapa nilai lainnya, dan tidak mengganggu apa pun di luar fungsi tersebut, tidak memiliki efek samping, dan karenanya aman untuk digunakan. Jika Anda ingin mempertimbangkan hal-hal seperti bagaimana cara menjalankan fungsi mempengaruhi konsumsi daya, itu masalah yang berbeda.
Saya berasumsi bahwa Anda merujuk ke mesin Turing-lengkap yang mengeksekusi semacam bahasa pemrograman yang terdefinisi dengan baik, di mana detail implementasi tidak relevan. Dengan kata lain, tidak masalah apa yang dilakukan stack, jika fungsi yang saya tulis dalam bahasa pemrograman pilihan saya dapat menjamin kekekalan dalam batas-batas bahasa. Saya tidak berpikir tentang tumpukan ketika saya memprogram dalam bahasa tingkat tinggi, saya juga tidak harus.
Untuk menggambarkan bagaimana ini bekerja, saya akan menawarkan beberapa contoh sederhana dalam C #. Agar contoh-contoh ini benar, kita harus membuat beberapa asumsi. Pertama, bahwa kompiler mengikuti spesifikasi C # tanpa kesalahan, dan kedua, bahwa ia menghasilkan program yang benar.
Katakanlah saya ingin fungsi sederhana yang menerima koleksi string, dan mengembalikan string yang merupakan gabungan dari semua string dalam koleksi yang dipisahkan oleh koma. Implementasi sederhana dan naif dalam C # mungkin terlihat seperti ini:
public string ConcatenateWithCommas(ImmutableList<string> list)
{
string result = string.Empty;
bool isFirst = false;
foreach (string s in list)
{
if (isFirst)
result += s;
else
result += ", " + s;
}
return result;
}
Contoh ini tidak berubah, prima facie. Bagaimana saya tahu itu? Karena string
objeknya tidak berubah. Namun, implementasinya tidak ideal. Karena result
tidak dapat diubah, objek string baru harus dibuat setiap kali melalui loop, menggantikan objek asli yang result
menunjuk. Ini dapat secara negatif mempengaruhi kecepatan dan memberi tekanan pada pengumpul sampah, karena harus membersihkan semua string ekstra.
Sekarang, katakanlah saya melakukan ini:
public string ConcatenateWithCommas(ImmutableList<string> list)
{
var result = new StringBuilder();
bool isFirst = false;
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
Perhatikan bahwa saya telah diganti string
result
dengan objek yang bisa berubah StringBuilder
,. Ini jauh lebih cepat daripada contoh pertama, karena string baru tidak dibuat setiap kali melalui loop. Sebagai gantinya, objek StringBuilder hanya menambahkan karakter dari setiap string ke kumpulan karakter, dan menampilkan semuanya di akhir.
Apakah fungsi ini tidak berubah, meskipun StringBuilder bisa berubah?
Ya itu. Mengapa? Karena setiap kali fungsi ini dipanggil, StringBuilder baru dibuat, hanya untuk panggilan itu. Jadi sekarang kita memiliki fungsi murni yang aman-utas, tetapi mengandung komponen yang dapat berubah.
Tetapi bagaimana jika saya melakukan ini?
public class Concatenate
{
private StringBuilder result = new StringBuilder();
bool isFirst = false;
public string ConcatenateWithCommas(ImmutableList<string> list)
{
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
}
Apakah metode ini aman untuk digunakan? Tidak. Mengapa? Karena kelas sekarang memegang keadaan di mana metode saya tergantung. Suatu kondisi lomba sekarang hadir dalam metode: satu utas dapat memodifikasi IsFirst
, tetapi utas lain dapat melakukan yang pertama Append()
, dalam hal ini saya sekarang memiliki koma di awal string saya yang tidak seharusnya ada di sana.
Mengapa saya ingin melakukannya seperti ini? Yah, saya mungkin ingin utas menumpuk string ke saya result
tanpa memperhatikan urutan, atau dalam urutan bahwa utas masuk. Mungkin itu adalah penebang, siapa tahu?
Pokoknya, untuk memperbaikinya, saya menaruh lock
pernyataan di sekitar jeroan metode ini.
public class Concatenate
{
private StringBuilder result = new StringBuilder();
bool isFirst = false;
private static object locker = new object();
public string AppendWithCommas(ImmutableList<string> list)
{
lock (locker)
{
foreach (string s in list)
{
if (isFirst)
result.Append(s);
else
result.Append(", " + s);
}
return result.ToString();
}
}
}
Sekarang aman-utas lagi.
Satu-satunya cara metode abadi saya mungkin gagal menjadi aman-thread adalah jika metode entah bagaimana bocor bagian dari implementasinya. Bisakah ini terjadi? Tidak jika kompilernya benar dan programnya benar. Akankah saya membutuhkan kunci pada metode seperti itu? Tidak.
Untuk contoh bagaimana implementasi dapat dibocorkan dalam skenario konkurensi, lihat di sini .
but everything has a side effect
- Eh, tidak, tidak. Sebuah fungsi yang menerima beberapa nilai dan mengembalikan beberapa nilai lainnya, dan tidak mengganggu apa pun di luar fungsi tersebut, tidak memiliki efek samping, dan karenanya aman untuk digunakan. Tidak masalah bahwa komputer menggunakan listrik. Kita dapat berbicara tentang sinar kosmik yang mengenai sel memori juga, jika Anda mau, tetapi mari kita tetap gunakan argumen praktis. Jika Anda ingin mempertimbangkan hal-hal seperti bagaimana cara menjalankan fungsi mempengaruhi konsumsi daya, itu masalah yang berbeda dari pemrograman threadsafe.