Referans şeffaflık nedir?


38

Bunu zorunlu paradigmalarda gördüm

f (x) + f (x)

aynı olmayabilir:

2 * f (x)

Ancak işlevsel bir paradigmada aynı olması gerekir. Her iki vakayı da Python ve Scheme'de uygulamaya çalıştım , ama benim için de aynı şekilde basit görünüyorlar.

Verilen fonksiyonla farkı gösterebilecek bir örnek ne olurdu?


7
Python'da referans olarak saydam fonksiyonlar yazabilir ve sık sık yazabilirsiniz. Aradaki fark, dilin onu zorlamadığıdır.
Karl Bielefeldt

5
C ve benzeri yer: f(x++)+f(x++)aynı değildir olabilir 2*f(x++)(? C bu makroları içinde gizlidir gibi şeyler özellikle sevimlidir - Üzerinde burnumu kırdı oldu da bahis)
tatarcık

Anladığım kadarıyla, @ gnat'ın örneği, R gibi işlevsel yönelimli dillerin referans referansı kullanması ve açıkça argümanlarını değiştiren işlevlerden kaçınmasıdır. En azından R'de, dilin karmaşık çevre ve ad alanları ve arama yollarına girmeden bu kısıtlamaları (en azından istikrarlı ve taşınabilir bir şekilde) kullanmak aslında zor olabilir.
shadowtalker

4
@ssdecontrol: Aslına bakarsanız, referans saydamlığı, değer-by-pass ve referans-by-referans her zaman aynı sonucu verir, bu nedenle dilin hangisini kullandığı önemli değildir. İşlevsel diller, genellikle anlamsal netlik için değere göre olan bir şeyle belirtilir, ancak uygulamaları genellikle performans için (hatta verilen bağlam için hangisinin daha hızlı olduğuna bağlı olarak) performans için geçiş referansı kullanır.
Jörg W Mittag

4
@gnat: Özellikle, f(x++)+f(x++)tanımsız davranışa neden olduğu için kesinlikle her şey olabilir. Ancak bu, gerçekten referans niteliğindeki şeffaflıkla ilgili değildir - ki bu çağrı için yardımcı olmaz, referans niteliğindeki saydam işlevler için de “tanımsız” dır sin(x++)+sin(x++). 42 olabilir, sabit sürücünüzü biçimlendirebilir, kullanıcıların burnundan uçan şeytanlar olabilir…
Christopher Creutzig

Yanıtlar:


62

Bir fonksiyona atıfta bulunulan referans şeffaflığı, bu fonksiyonu uygulamanın sonucunu sadece argümanlarının değerlerine bakarak belirleyebileceğinizi belirtir. Referans olarak saydam işlevleri herhangi bir programlama dilinde yazabilirsiniz, örneğin Python, Scheme, Pascal, C

Öte yandan, çoğu dilde referans olarak saydam olmayan işlevler de yazabilirsiniz. Örneğin, bu Python işlevi:

counter = 0

def foo(x):
  global counter

  counter += 1
  return x + counter

aslında referans olarak saydam değildir, aslında

foo(x) + foo(x)

ve

2 * foo(x)

Herhangi bir argüman için farklı değerler üretecektir x. Bunun nedeni, işlevin global bir değişkeni kullanması ve değiştirmesidir, bu nedenle her çağrının sonucu, yalnızca işlevin argümanına değil, bu değişen duruma bağlıdır.

Tamamen işlevsel bir dil olan Haskell, saf işlevlerin uygulandığı ve her zaman referans olarak şeffaf olan ifade değerlendirmesini kesin olarak ayırır , eylem yürütmesinden (özel değerlerin işlenmesi), referans olarak saydam olmayan, yani aynı işlemi yürütmek her seferinde bir farklı sonuç

Yani, herhangi bir Haskell işlevi için

f :: Int -> Int

ve herhangi bir tamsayı x, her zaman doğrudur

2 * (f x) == (f x) + (f x)

Bir eylem örneği, kütüphane işlevinin sonucudur getLine:

getLine :: IO String

İfade değerlendirmesi sonucunda, bu işlev (aslında bir sabit) her şeyden önce saf türde bir değer üretir IO String. Bu türdeki değerler, diğerleri gibi değerlerdir: bunları etrafa aktarabilir, veri yapılarına koyabilir, özel fonksiyonlar kullanarak oluşturabilir ve benzeri işlemleri yapabilirsiniz. Örneğin, bunun gibi eylemlerin bir listesini yapabilirsiniz:

[getLine, getLine] :: [IO String]

Eylemler, Haskell çalışma zamanına bunları yazarak yürütmelerini söyleyebilmeniz için özeldir:

main = <some action>

Bu durumda, Haskell programınız başlatıldığında, çalışma zamanı muhtemelen yan etkiler oluşturarak, ona bağlı eylemi yürütürmain ve yürütür . Bu nedenle, eylem yürütme başvuru açısından saydam değildir, çünkü aynı eylemi iki kez yürütmek, çalışma zamanının girdi olarak aldığı duruma bağlı olarak farklı sonuçlar üretebilir.

Haskell'in tip sistemi sayesinde, hiçbir zaman başka bir tipin beklendiği bir bağlamda asla kullanılamaz ve bunun tersi de geçerlidir. Bu nedenle, bir dize uzunluğunu bulmak istiyorsanız, lengthişlevi kullanabilirsiniz :

length "Hello"

5 döndürür. Ancak terminalden okunan bir dizgenin uzunluğunu bulmak istiyorsanız, yazamazsınız.

length (getLine)

çünkü bir tür hatası alıyorsunuz: lengthbir tür listesinin girişini bekliyor (ve bir Dize, aslında bir listedir) ancak getLinetürün bir değeridir IO String(bir eylem). Bu şekilde, tip sistemi getLine(yürütülmesi çekirdek dilin dışında gerçekleştirilir ve referans olarak saydam olmayan) bir işlem değerinin, işlem dışı bir değerin içine gizlenememesini sağlar Int.

DÜZENLE

Exizt sorusuna cevap vermek için, işte konsoldan bir satır okuyan ve uzunluğunu basan küçük bir Haskell programı.

main :: IO () -- The main program is an action of type IO ()
main = do
          line <- getLine
          putStrLn (show (length line))

Ana eylem, sırayla yürütülen iki alt bölümden oluşur:

  1. getlineÇeşidi IO String,
  2. ikinci bir fonksiyonu değerlendirilerek oluşturulur putStrLnÇeşidi String -> IO ()argüman ile.

Daha doğrusu, ikinci eylem inşa

  1. lineilk eylem tarafından okunan değere bağlanma ,
  2. saf fonksiyonların değerlendirilmesi length(tamsayı olarak uzunluk hesaplamak) ve sonra show(tamsayıyı bir dizgeye çevirmek),
  3. eylemini putStrLnsonucuna fonksiyon uygulayarak oluşturmak show.

Bu noktada, ikinci eylem gerçekleştirilebilir. "Merhaba" yazdıysanız, "5" yazacaktır.

Notu kullanarak bir işlemden bir değer elde <-ederseniz, bu değeri yalnızca başka bir işlem içinde kullanabileceğinizi unutmayın, örneğin:

main = do
          line <- getLine
          show (length line) -- Error:
                             -- Expected type: IO ()
                             --   Actual type: String

çünkü show (length line)tipe Stringsahipken, gösterimde bir eylemin ( getLinetürden IO String) başka bir eylemin (örneğin putStrLn (show (length line))türden IO ()) takip edilmesini gerektirdiği halde vardır .

EDIT 2

Jörg W Mittag'ın referandum şeffaflığı tanımı benimkinden daha genel (onun cevabını aştım). Kısıtlı bir tanım kullandım çünkü sorudaki örnek fonksiyonların geri dönüş değerine odaklandı ve bu yönü açıklamak istedim. Bununla birlikte, genel olarak RT, bir ifadenin değerlendirilmesinden kaynaklanan küresel duruma yapılan değişiklikler ve çevre ile etkileşimler (IO) de dahil olmak üzere tüm programın anlamını ifade eder. Bu nedenle, doğru, genel bir tanım için, bu cevaba başvurmalısınız.


10
Redüktör bu cevabı nasıl geliştirebileceğimi önerebilir mi?
Giorgio,

Peki Haskell'deki terminalden okunan dize uzunluğu nasıl olur?
sbichenko

2
Bu son derece sersemleticidir, ancak bütünlük uğruna, eylemlerin ve saf işlevlerin karışmamasını sağlayan Haskell'in tipi sistemi değildir; dilin, doğrudan çağırabileceğiniz saf olmayan işlevler sağlamadığı gerçeğidir. Haskell'in IOtürünü lambda ve jeneriklerle herhangi bir dilde kolayca uygulayabilirsiniz , ancak herhangi bir kişi printlndoğrudan arayabildiğinden , uygulama IOsaflığı garanti etmez; sadece bir kongre olacaktı.
Doval

(1) tüm işlevlerin saf olduğunu (elbette, saf olduklarını, çünkü dilin saf olmayan işlevler sağlamadığını, bildiğim kadarıyla bunu atlayacak bazı mekanizmalar olduğunu bildiğim halde) ve (2) saf işlevler ve saf olmayan işlemlerin farklı türleri vardır, bu yüzden karıştırılamazlar. BTW, doğrudan çağrı ile ne demek istiyorsun ?
Giorgio

6
Hakkında noktası getLinereferentially şeffaf olmama yanlıştır. Sen sunuyoruz getLineo kadar değerlendirir veya kullanıcının giriş bağlıdır belirli dize bazıları dize, için azaltır sanki. Bu yanlış. IO Stringbir Dize'den daha fazlasını Maybe Stringiçermez. IO Stringbelki de, belki bir String almak için bir reçetedir ve bir ifade olarak Haskell'deki kadar saf.
LuxuryMode

25
def f(x): return x()

from random import random
f(random) + f(random) == 2*f(random)
# => False

Bununla birlikte, Referans Şeffaflık'ın anlamı bu değildir . RT , programın anlamını değiştirmeden bu ifadeyi değerlendirmenin sonucu olarak programdaki herhangi bir ifadeyi değiştirebileceğiniz anlamına gelir.

Örneğin, aşağıdaki programı uygulayın:

def f(): return 2

print(f() + f())
print(2)

Bu program referans olarak şeffaftır. Ben birini veya her iki tekrarlarını yerini alabilir f()ile 2ve yine aynı şekilde çalışacaktır:

def f(): return 2

print(2 + f())
print(2)

veya

def f(): return 2

print(f() + 2)
print(2)

veya

def f(): return 2

print(2 + 2)
print(f())

hepsi aynı şekilde davranacak.

Aslında, hile yaptım. printProgramın anlamını değiştirmeden çağrının dönüş değeriyle (hiç bir değeri yoktur) değiştirebilmeliyim. Bununla birlikte, açıkça, eğer iki printifadeyi kaldırırsam, programın anlamı değişecektir: önce, ekrana bir şey basmaz, sonra yapmaz. G / Ç, referans olarak saydam değildir.

Basit kural şudur: Herhangi bir ifade, alt ifade veya alt yordam çağrısını, bu ifadenin dönüş değeriyle değiştirebilirseniz, alt ifade veya alt yordam çağrısını programın herhangi bir yerinde, programın anlamını değiştirmeden değiştirebilirsiniz; şeffaflık. Ve bunun anlamı, pratik olarak konuşursanız, herhangi bir G / Ç’niz olamaz, değişken bir duruma sahip olamazsınız, herhangi bir yan etkisi olamaz. Her ifadede, ifadenin değeri yalnızca ifadenin kurucu bölümlerinin değerlerine bağlı olmalıdır. Ve her alt rutin çağrısında, dönüş değeri yalnızca argümanlara bağlı olmalıdır.


4
“değişken bir duruma sahip olamaz”: Eh, gizlenmişse ve kodunuzun gözlenebilir davranışını etkilemiyorsa buna sahip olabilirsiniz. Örneğin, notlaştırma hakkında düşünün.
Giorgio,

4
@Giorgio: Bu belki de özneldir, ancak önbelleğe alınan sonuçların, gizlenmiş ve gözlemlenebilir bir etkisi yoksa, önbelleğe alınan sonuçların gerçekten "değişebilir durum" olmadığını iddia ediyorum. Takılabilirlik daima değişken donanımın üstüne uygulanan bir soyutlamadır; sık sık dil tarafından sağlanır (yürütme sırasında değer kayıtlar ve bellek konumları arasında hareket edebilse ve "bir daha asla kullanılamayacağı bilinen bir kez ortadan kalkabilir"; Bir kütüphane tarafından sağlanan ya da değil. (Tabii doğru uygulandığını varsayarsak.)
ruakh

1
+1 printÖrneği gerçekten beğendim . Belki bunu görmenin bir yolu, ekranda basılanın "dönüş değerinin" bir parçası olmasıdır. printİşlev dönüş değeri ve terminaldeki eşdeğer yazıyla değiştirebiliyorsanız , örnek işe yarar.
Pierre Arlaud

1
@Giorgio Uzay / zaman kullanımı, referans şeffaflığı amacıyla yan etki olarak kabul edilemez. Bu , farklı çalışma sürelerine sahip oldukları için birbirinin yerine geçecek 4ve 2 + 2değiştirilemez hale gelir ve referans saydamlığının tüm noktası, değerlendirdiği değer ile bir ifadeyi ikame etmenizdir. Önemli husus iplik güvenliği olacaktır.
Doval

1
@overexchange: Referans Şeffaflık, programın anlamını değiştirmeden her alt ifadeyi değeri ile değiştirebileceğiniz anlamına gelir. listOfSequence.append(n)döner None, böylece her çağrıyı yerine gerekir listOfSequence.append(n)ile Noneprogramınızın anlamını değiştirmeden. Bunu yapabilir misin? Eğer değilse, o zaman referans olarak şeffaf değildir.
Jörg W Mittag

1

Bu cevabın bazı kısımları GitHub hesabımda barındırılan fonksiyonel programlama konusunda bitmemiş bir eğiticiden doğrudan alınmıştır :

Bir işlevin, aynı girdi parametreleri verildiğinde, her zaman aynı çıktıyı (dönüş değeri) üretmesi durumunda referans olarak şeffaf olduğu söylenir. Birisi saf fonksiyonel programlama için bir yükseliş arıyorsa, referans şeffaflığı iyi bir adaydır. Cebir, aritmetik ve mantıktaki formüllerle mantık yürütürken, bu özellik - eşittir için eşdeğerlerin ikame edilebilirliği olarak da adlandırılır - temelde çok önemlidir.

Basit bir örnek düşünün:

x = 42

Saf bir işlevsel dilde, eşittir işaretinin sol ve sağ yanları her iki yönde de ikame edilebilir. Yani, C gibi bir dilin aksine, yukarıdaki gösterim gerçekten bir eşitlik ifade eder. Bunun bir sonucu olarak, matematiksel denklemler gibi program koduyla ilgili sebep olabiliriz.

Gönderen Haskell wiki :

Saf hesaplamalar her çağrıldığında aynı değeri verir. Bu özelliğe referans şeffaflığı denir ve kod üzerinde eşit bir akıl yürütme yapmayı mümkün kılar ...

Bunun aksine, C benzeri diller tarafından gerçekleştirilen operasyon tipine bazen yıkıcı bir atama denir .

Terim saf genellikle bu tartışma ile ilgili ifadelerin bir özelliğini tanımlamak için kullanılır. Saf sayılacak bir fonksiyon için,

  • herhangi bir yan etki sergilemesi yasaktır ve
  • referans olarak şeffaf olmalıdır.

Çok sayıda matematik ders kitabında bulunan kara kutu metaforuna göre, bir işlevin iç dünyası tamamen dış dünyadan mühürlenir. Bir yan etki, bir işlev veya ifadenin bu prensibi ihlal etmesidir - yani, prosedürün başka birimlerle bir şekilde iletişim kurmasına izin verilir (örn. Bilgi paylaşımı ve alışverişi).

Özet olarak, referans saydamlığı, programlama dilleri anlamında da fonksiyonların gerçek , matematiksel fonksiyonlar gibi davranması için bir zorunluluktur .


buradan alınacak kelimeden kelimeye kopya ile açılıyor gibi görünüyor : "Eğer aynı girdi parametreleri göz önüne alındığında, her zaman aynı çıktıyı verirse, bir fonksiyonun referans olarak şeffaf olduğu söylenir ..." Stack Exchange intihal için kuralları vardır. bunların farkında mısın? "İntihal, başkasının eserinin parçalarını kopyalamak, isminizi koymak ve orijinal yazar olarak kendinizden geçmek ruhsuz eylemdir ..."
gnat

3
Bu sayfayı ben yazdım.
yesthisisuser,

eğer öyleyse, intihalden daha az görünmesini sağlayın - çünkü okuyucuların anlatacak yolu yoktur. Bunu SE'de nasıl yapacağını biliyor musun? 1) Orijinal kaynaklara, "Yazdığım gibi [here](link to source)..." ve ardından 2) uygun fiyat teklifi biçimlendirmesi (bunun için fiyat teklifi işaretleri veya daha iyisi, > bunun için sembol kullanın). Ayrıca, genel rehberlik vermenin yanı sıra, sorulan cevap adreslerine sorulan, eğer bu durumda f(x)+f(x)/ hakkında 2*f(x), ' Nasıl Cevap Verilir?' Sorusuna da zarar vermez, aksi halde sadece sayfanızı
tanıtıyor

1
Teorik olarak, bu cevabı anladım. Ancak, pratik olarak bu kuralları takip ederek, bu programda dolu sıra dizisini geri göndermem gerekiyor . Bunu nasıl yaparım?
aboneyi değiştirme
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.