Bu cevabın amaçları için, "tamamen işlevsel dili" tanımlarım, işlevlerin referans olarak şeffaf olduğu, yani aynı işlevi aynı argümanlarla birden çok kez çağırmak her zaman aynı sonuçları verir. Bu, tamamen işlevsel bir dilin genel tanımının olduğuna inanıyorum.
Saf işlevsel programlama dilleri, yan etkilere izin vermez (ve bu nedenle pratikte çok az kullanışlıdır, çünkü faydalı bir programın, örneğin dış dünyayla etkileşime girdiğinde, yan etkileri vardır).
Referans şeffaflığı elde etmenin en kolay yolu gerçekten de yan etkilere izin vermemektir ve aslında durumun olduğu diller vardır (çoğunlukla alana özgü olanlar). Ancak kesinlikle tek yol değildir ve en genel amaç tamamen işlevsel dillerdir (Haskell, Clean, ...) yan etkiye izin vermektedir.
Ayrıca, yan etkisi olmayan bir programlama dilinin pratikte çok az kullanımı olduğunu söyleyerek bence gerçekten adil değil - kesinlikle alana özgü diller için değil, fakat genel amaçlı diller için bile, bir dilin yan etkiler sağlamadan oldukça yararlı olabileceğini hayal ediyorum. . Belki konsol uygulamaları için değil, ama GUI uygulamalarının işlevsel reaktif paradigmada yan etkiler olmadan güzel bir şekilde uygulanabileceğini düşünüyorum.
1. noktaya gelince, çevre ile tamamen işlevsel dillerde etkileşime girebilirsiniz, ancak bunları tanıtan kodu (fonksiyonlar) açıkça işaretlemelisiniz (örneğin monadik tiplerle Haskell'de).
Bu basitleştirmenin biraz üzerinde. Sadece yan etkileme fonksiyonlarının bu şekilde işaretlenmesi gereken bir sisteme sahip olmak (C ++ 'daki yapı-doğruluğuna benzer, ancak genel yan etkilere sahip) referans saydamlığını sağlamak için yeterli değildir. Bir programın aynı argümanlarla hiçbir zaman bir işlevi asla çağıramayacağından ve farklı sonuçlar alacağından emin olmanız gerekir. Bunu ya da böyle şeyler yaparak yapabilirsin.readLine
bir işlev olmayan bir şey (Haskell'in IO monad ile yaptığı şeydir) ya da yan etkili işlevleri aynı argümanla birden fazla kez çağırmayı imkansız hale getirebilirsiniz (Clean ne yapar). İkinci durumda, derleyici yan etkileyici bir işlev çağırdığınızda, bunu yeni bir argümanla yaptığınızdan ve aynı argümanı iki kez yan etkileyici işlevine geçirdiğiniz herhangi bir programı reddedeceğinizi garanti eder.
Saf işlevsel programlama dilleri, durumu koruyan bir program yazmanıza izin vermez (çoğu programda duruma ihtiyaç duyduğunuz için programlamayı çok zorlaştırır).
Yine, tamamen işlevsel bir dil, değişken duruma çok iyi bir şekilde izin vermeyebilir; ancak, yukarıda belirtilen yan etkiler ile anlattığım gibi uygularsanız, saf ve hala değişken duruma sahip olmak kesinlikle mümkündür. Gerçekten değişken durum, başka bir yan etki şeklidir.
Bununla birlikte, fonksiyonel programlama dilleri kesinlikle değişken durumları - özellikle de saf olanları - vazgeçiriyor. Ve bunun programlamanın tuhaflaştığını düşünmüyorum - tam tersi. Bazen (ancak çoğu zaman değil) değişken durum, performans veya açıklık kaybı olmadan (Haskell gibi dillerin değişken durum için olanaklara sahip olmasının nedeni budur) önlenemez.
Kavram yanılgıları ise, nasıl ortaya çıktılar?
Bence pek çok insan sadece "bir fonksiyon aynı argümanlar ile çağrıldığında aynı sonucu vermelidir" ifadesini okuyor ve readLine
değişebilir durumu koruyan bir kod ya da kod uygulamasının mümkün olmadığı sonucuna varıyor . Dolayısıyla, tamamen işlevsel dillerin bu şeyleri referans şeffaflığı bozmadan tanıtmak için kullanabilecekleri "hilelerin" farkında değiller.
Ayrıca değişken durum, fonksiyonel dillerde ağır biçimde cesaret kırıcıdır, bu nedenle, tamamen işlevsel olanlarda hiç izin verilmediğini varsaymak, o kadar da bir sıçrama değildir.
(1) yan etkileri uygulamak ve (2) devlet ile bir hesaplama yapmak için Haskell deyimsel yolunu gösteren (muhtemelen küçük) bir kod pasajı yazabilir misiniz?
İşte Pseudo-Haskell'de, kullanıcıdan bir isim isteyen ve onu selamlayan bir uygulama. Sözde Haskell, henüz icat ettiğim, Haskell'in IO sistemine sahip, ancak daha geleneksel sözdizimi, daha açıklayıcı fonksiyon isimleri kullanan ve no- do
notasyonuna sahip olmayan (IO monadının tam olarak nasıl çalıştığından dikkatini dağıtacağı) bir dildir:
greet(name) = print("Hello, " ++ name ++ "!")
main = composeMonad(readLine, greet)
İpucu burada yani readLine
tipte bir değerdir IO<String>
ve composeMonad
tipi bir argüman alır fonksiyonudur IO<T>
(bir tür için T
) ve tip bir bağımsız değişken alan bir fonksiyonu olan bir bağımsız değişken T
ve tip bir değer verir IO<U>
(bir tür için U
). print
bir dize alan ve türün değerini döndüren bir işlevdir IO<void>
.
Bir tür değeri, bir tür IO<A>
değeri üreten belirli bir işlemi "kodlayan" bir değerdir A
. eylemini izleyen eylemi kodlayan composeMonad(m, f)
yeni bir değer üretir, eylemini gerçekleştirerek üretilen değerin nerede olduğunu .IO
m
f(x)
x
m
Değişken devlet şöyle görünürdü:
counter = mutableVariable(0)
increaseCounter(cnt) =
setIncreasedValue(oldValue) = setValue(cnt, oldValue + 1)
composeMonad(getValue(cnt), setIncreasedValue)
printCounter(cnt) = composeMonad( getValue(cnt), print )
main = composeVoidMonad( increaseCounter(counter), printCounter(counter) )
İşte mutableVariable
her türün değerini alan T
ve üreten bir işlev MutableVariable<T>
. İşlev , geçerli değerini üreten birini getValue
alır MutableVariable
ve döndürür IO<T>
. a ve a'yı setValue
alır MutableVariable<T>
ve değeri ayarlayanı T
döndürür IO<void>
. ilk argümanın mantıklı bir değer üretmeyen ve ikinci argümanın bir monad döndüren bir işlev olmadığı dışında olduğu composeVoidMonad
gibi composeMonad
aynıdır IO
.
Haskell'de, bütün bu sıkıntıyı daha az acı verici kılan bazı sözdizimsel şeker var, ancak değişken durumun, dilin gerçekten yapmanızı istemediği bir şey olduğu açık.