Fonksiyonel programlamada bir zaman fonksiyonu nasıl mevcut olabilir?


646

İtiraf etmeliyim ki fonksiyonel programlama hakkında fazla bir şey bilmiyorum. Buradan ve buradan okudum ve böylece fonksiyonel programlamada, bir fonksiyonun, kaç kez çağrıldığına bakılmaksızın, aynı girdi için aynı çıktıyı döndürdüğünü öğrendim. Tam olarak, fonksiyon ifadesinde yer alan giriş parametrelerinin aynı değeri için aynı çıktıyı değerlendiren matematiksel bir fonksiyon gibidir.

Örneğin, şunu düşünün:

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

Kaç kez kullanırsanız kullanın f(10,4), değeri her zaman olacaktır 104. Bu nedenle, nerede olursanız olun , tüm ifadenin değerini f(10,4)değiştirmeden onu 104değiştirebilirsiniz. Bu özelliğe, bir ifadenin referans şeffaflığı denir .

Wikipedia'nın dediği gibi ( bağlantı ),

Tersine, işlevsel kodda, bir işlevin çıktı değeri yalnızca işleve girilen bağımsız değişkenlere bağlıdır, bu nedenle x işlevinin x bağımsız değişkeni için aynı değerle iki kez çağrılması her iki seferde de aynı sonucu f (x) üretir.

Fonksiyonel programlamada ( şimdiki zamanı döndüren) bir zaman fonksiyonu mevcut olabilir mi?

  • Evet ise, o zaman nasıl var olabilir? Fonksiyonel programlama ilkesini ihlal etmiyor mu? Özellikle fonksiyonel programlamanın özelliğinden olan referans şeffaflığı ihlal eder (eğer doğru anlarsam).

  • Ya da hayır ise fonksiyonel programlamanın şimdiki zamanını nasıl bilebilirsiniz?


15
Bence çoğu (veya tümü) fonksiyonel dil o kadar katı değildir ve fonksiyonel ve zorunlu programlamayı birleştirir. En azından, bu F # benim izlenimim.
Alex F

13
@Adam: Arayan kişi şimdiki zamanı ilk etapta nasıl bilebilir?
Nawaz

29
@Adam: Aslında tamamen işlevsel dillerde yasadışı (olduğu gibi: imkansız).
sepp2k

47
@Adam: Neredeyse. Saf olan genel amaçlı bir dil, genellikle referans şeffaflığını bozmadan "dünya devletine" (yani şimdiki zaman, bir dizindeki dosyalar vb. Gibi) ulaşmak için bazı olanaklar sunar. Haskell'de bu IO monad ve Temiz'de ise dünya tipi. Dolayısıyla bu dillerde, şimdiki zamana ihtiyaç duyan bir işlev ya bir argüman olarak ya da gerçek sonucu (Haskell) yerine bir IO eylemi döndürmesi ya da argüman olarak dünya devletini alması gerekir (Temiz).
sepp2k

12
FP'yi düşünürken unutmak kolaydır: bir bilgisayar, değişebilir durumun büyük bir kısmıdır. FP bunu değiştirmez, sadece gizler.
Daniel

Yanıtlar:


176

Bunu açıklamanın başka bir yolu şudur: hiçbir işlev geçerli saati alamaz (değişmeye devam ettiğinden), ancak bir eylem geçerli saati alamaz. Bunun , şimdiki zamanı alma eyleminigetClockTime temsil eden sabit (veya isterseniz bir null fonksiyon) olduğunu varsayalım . Bu eylem , ne zaman kullanılırsa kullanılsın aynıdır, bu yüzden gerçek bir sabittir.

Benzer şekilde, printbiraz zaman temsil eden ve onu konsola yazdıran bir işlev olduğunu varsayalım. İşlev çağrıları saf işlevsel bir dilde yan etkilere sahip olamayacağından, bunun yerine zaman damgası alan ve konsola yazdırma eylemini döndüren bir işlev olduğunu hayal ederiz . Yine, bu gerçek bir işlevdir, çünkü aynı zaman damgasını verirseniz, her seferinde yazdırmanın aynı eylemini döndürür .

Şimdi, geçerli saati konsola nasıl yazdırabilirsiniz? İki eylemi birleştirmelisiniz. Peki bunu nasıl yapabiliriz? Biz sadece geçemediği getClockTimeiçin printbaskı bir zaman damgası, değil bir eylem beklediğini beri. Ama biz bir operatör, orada olduğunu hayal edebilirsiniz >>=, birleştirir iki eylemleri bir zaman damgası çeken benim ve argüman ve baskılar o kadar birini alır biri. Bunu daha önce bahsedilen eylemlere uygulamak, sonuç ... tadaaa ... şimdiki zamanı alan ve basan yeni bir eylemdir. Ve bu, Haskell'de tam olarak nasıl yapıldığıdır.

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

Yani, kavramsal olarak, bu şekilde görüntüleyebilirsiniz: Saf işlevsel bir program herhangi bir G / Ç gerçekleştirmez , çalışma zamanı sisteminin yürüttüğü bir eylem tanımlar . Eylem her zaman aynı, ama bunu yürütme sonucu yürütüldüğünde koşullarına bağlıdır.

Bunun diğer açıklamalardan daha açık olup olmadığını bilmiyorum, ancak bazen bu şekilde düşünmeme yardımcı oluyor.


33
Bana ikna edici değil. getClockTimeBir işlev yerine bir eylem çağırdınız . Eğer böyle çağırırsanız, o zaman her fonksiyon eylemini çağırın , o zaman zorunlu programlama bile fonksiyonel programlamaya dönüşür. Ya da belki, onu aramak istiyorum actional programmming.
Nawaz

92
@Nawaz: Burada dikkat edilmesi gereken en önemli şey, bir işlev içinden eylem gerçekleştiremeyeceğinizdir. Yalnızca yeni eylemler yapmak için eylemleri ve işlevleri bir araya getirebilirsiniz. Bir eylemi gerçekleştirmenin tek yolu eyleminizi oluşturmaktır main. Bu, saf işlevsel kodun zorunlu koddan ayrılmasına izin verir ve bu ayırma, tip sistemi tarafından zorlanır. Eylemleri birinci sınıf nesneler olarak ele almak da onları geçmenize ve kendi "kontrol yapılarınızı" oluşturmanıza izin verir.
hammar

36
Haskell'deki her şey bir işlev değildir - bu tamamen saçmalıktır. İşlev, türü a içeren bir şeydir ->- standart terimi bu şekilde tanımlar ve Haskell bağlamındaki tek mantıklı tanım budur. Kimin türüdür şey Yani IO Whateverolduğunu değil bir işlev.
sepp2k

9
@ sepp2k Peki, myList :: [a -> b] bir işlevdir? ;)
fuz

8
@ThomasEding Partiye gerçekten geç kaldım, ama bunu açıklığa kavuşturmak istiyorum: putStrLnbir eylem değil - bir eylem döndüren bir işlevdir . eylem içeren bir getLinedeğişkendir . Eylemler değerler, değişkenler ve fonksiyonlar "eylemler" / "etiketler" bu eylemleri verir.
kqr

356

Evet ve hayır.

Farklı fonksiyonel programlama dilleri bunları farklı şekilde çözer.

Haskell'de (çok saf olan) tüm bu şeyler I / O Monad denilen bir şeyde gerçekleşmelidir - buraya bakın .

Bunu, işlevinize (dünya devleti) başka bir girdi (ve çıktı) almak veya değişen zamanı elde etmek gibi "safsızlığın" gerçekleştiği bir yer olarak daha kolay düşünebilirsiniz.

F # gibi diğer diller sadece bazı safsızlıklara sahiptir ve böylece aynı giriş için farklı değerler döndüren bir işleve sahip olabilirsiniz - tıpkı normal zorunlu diller gibi .

Jeffrey Burka'nın yorumunda belirttiği gibi: İşte doğrudan Haskell wiki'sinden I / O Monad'a güzel bir giriş .


223
Haskell'deki IO monad hakkında fark edilmesi gereken en önemli şey, bu sorunun üstesinden gelmenin sadece bir hack olmadığıdır; Monadlar, bir bağlamda bir dizi eylemin tanımlanması sorununa genel bir çözümdür. Olası bir bağlam, IO monad'ına sahip olduğumuz gerçek dünyadır. Başka bir bağlam, STM monad'ına sahip olduğumuz bir atomik işlem içinde. Yine başka bir bağlam, ST monad'ına sahip olduğumuz saf bir işlev olarak prosedürel bir algoritmanın (örn. Knuth shuffle) uygulanmasıdır. Ve kendi monad'larınızı da tanımlayabilirsiniz. Monadlar bir tür yüklenebilir noktalı virgül.
Paul Johnson

2
Ben şimdiki zaman "fonksiyonları" gibi şeyler değil, "prosedürler" gibi bir şey (Haskell çözüm bu bir istisna olsa da tartışılabilir) olarak adlandırmak için yararlı buluyorum.
singpolyma

Haskell perspektifinden bakıldığında, klasik "prosedürler" ('... -> ()' gibi türlere sahip şeyler) ... -> () ile saf bir işlev olarak hiçbir şey yapamaz.
Carsten

3
Tipik Haskell terimi "eylem" dir.
Sebastian Redl

6
"Monadlar bir tür yüklenebilir noktalı virgül." +1
user2805751

147

Haskell'de yan etkileri ele almak için monad adlı bir yapı kullanılır . Bir monad, temel olarak değerleri bir kaba kapsüllediğiniz ve işlevleri bir konteyner içindeki değerlerden değerlere zincirlemek için bazı işlevlere sahip olduğunuz anlamına gelir. Konteynerin tipi varsa:

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

ES eylemlerini güvenli bir şekilde uygulayabiliriz. Bu tür şu anlama gelir: Tür eylemi, tür belirteci alan ve sonuçla birlikte yeni bir belirteç döndüren bir IOişlevdir RealWorld.

Bunun arkasındaki fikir, her IO eyleminin büyülü belirteçle temsil edilen dış durumu değiştirmesidir RealWorld. Monadları kullanarak, gerçek dünyayı birlikte değiştiren birden fazla işlevi zincirleyebilirsiniz. Bir monadın en önemli işlevi >>=, belirgin bağlama :

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

>>=bir eylemi ve bu eylemin sonucunu alan bir işlevi alır ve bundan yeni bir eylem oluşturur. Dönüş türü yeni eylemdir. Örneğin, haydi bir işlevi yoktur gibi yapalım now :: IO Stringgeçerli zamanı temsil eden bir dize döndürür. putStrLnYazdırmak için fonksiyonla zincirleyebiliriz :

now >>= putStrLn

Veya dozorunlu bir programcıya daha aşina olan -Notation ile yazılmış :

do currTime <- now
   putStrLn currTime

Bütün bunlar saf, dış dünya ile ilgili mutasyonu ve bilgiyi jetonla eşleştirdik RealWorld. Yani her seferinde bu eylemi çalıştırdığınızda, elbette farklı bir çıktı elde edersiniz, ancak giriş aynı değildir: RealWorldsimge farklıdır.


3
-1: RealWorldDuman perdesinden memnun değilim . Yine de, en önemli şey, bu iddia edilen nesnenin bir zincirde nasıl aktarıldığıdır. Eksik parça, başladığı yer, gerçek dünyaya kaynak veya bağlantının olduğu yerdir - IO monadında çalışan ana işlevle başlar.
u0b34a0f6ae

2
@ kaizer.se Başladığında RealWorldprograma aktarılan küresel bir nesneyi düşünebilirsiniz .
fuz

6
Temel olarak, mainişleviniz bir RealWorldargüman alır . Sadece infaz geçtikten sonra.
Louis Wasserman

13
Görüyorsunuz, neden gizlemek RealWorldve sadece değiştirmek için cılız fonksiyonlar sağlamanın nedeni putStrLn, bazı Haskell programcılarının programlarından RealWorldbiriyle değişmemesi , Haskell Curry'in adresi ve doğum tarihi, komşu komşular haline gelecek şekilde (Haskell programlama diline zarar verecek şekilde zaman
uzaması

2
RealWorld -> (a, RealWorld) gerçek dünyanın her zaman işlevinizin (veya mevcut sürecinizin) dışındaki evrenin diğer bölümleri tarafından değiştirilebileceğini aklınızda bulundurduğunuz sürece, eşzamanlılık altında bile metafor parçalanmaz. Yani, (a) metaforu yıkmak ve yok (b) olan bir değer her zaman RealWorldonun tipi bir işleve geçirilir geçirilmez, fonksiyon gerçek dünya, çünkü yeniden değerlendirilmelidir olmak zorunda olacak bu arada değişti ( @fuz açıklandığı gibi modellenir ve gerçek dünyayla her etkileşimimizde farklı bir 'jeton değeri' verir).
Qqwy

73

Çoğu fonksiyonel programlama dili saf değildir, yani fonksiyonların sadece değerlerine bağlı kalmasına izin vermezler. Bu dillerde şimdiki zamanı döndüren bir işleve sahip olmak mükemmel bir şekilde mümkündür. Bu soruyu etiketlediğiniz dillerden Scala ve F # (ve diğer ML modellerinin çoğu ) için geçerlidir.

Saf olan Haskell ve Clean gibi dillerde durum farklıdır. Haskell'de şimdiki zaman bir işlevle değil, Haskell'in yan etkileri kapsülleme yolu olan IO eylemi ile kullanılabilirdi.

Clean'te bir işlev olurdu, ancak işlev argüman olarak bir dünya değerini alacak ve sonuç olarak (şimdiki zamana ek olarak) taze bir dünya değeri döndürecektir. Tür sistemi, her dünya değerinin yalnızca bir kez kullanılabilmesini sağlar (ve dünya değerini tüketen her işlev yeni bir değer üretir). Bu şekilde, zaman fonksiyonunun her seferinde farklı bir argümanla çağrılması gerekir ve böylece her seferinde farklı bir zamana geri dönmesine izin verilir.


2
Bu, Haskell ve Clean'in farklı şeyler yaptığı gibi ses çıkarır. Anladığım kadarıyla, aynısını yapıyorlar, Haskell bunu başarmak için daha iyi bir sözdizimi (?) Sunuyor.
Konrad Rudolph

27
@Konrad: Her ikisini de soyut yan etkiler için tip sistem özelliklerini kullandıkları için aynı şeyi yapıyorlar, ama hepsi bu. IO monadını bir dünya türü açısından açıklamanın çok iyi olduğunu unutmayın, ancak Haskell standardı aslında bir dünya türü tanımlamıyor ve Haskell'de Dünya türünün bir değerini elde etmek aslında mümkün değil (çok mümkün ve gerçekten de temiz gerekli). Ayrıca Haskell, bir tür sistem özelliği olarak benzersiz yazım özelliğine sahip değildir, bu yüzden size bir Dünyaya erişim izni verdiyse, onu Temiz'in yaptığı gibi saf bir şekilde kullanmanız sağlanamazdı.
sepp2k

51

"Geçerli saat" bir işlev değildir. Bu bir parametredir. Kodunuz geçerli saate bağlıysa, kodunuzun zamana göre parametrelendirildiği anlamına gelir.


22

Kesinlikle tamamen işlevsel bir şekilde yapılabilir. Bunu yapmanın birkaç yolu vardır, ancak en basit olanı zaman fonksiyonunun sadece zamanı değil, aynı zamanda bir sonraki zaman ölçümünü almak için aramanız gereken fonksiyonu geri getirmektir .

C # 'da şöyle uygulayabilirsiniz:

// 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;
    }
}

(Bunun basit olması gereken, pratik olmayan bir örnek olduğunu unutmayın. Özellikle, liste düğümleri ProgramStartTime tarafından köklendiği için toplanamaz.)

Bu 'ClockStamp' sınıfı değişmez bir bağlantılı liste gibi davranır, ancak gerçekten düğümler istek üzerine üretilir, böylece 'geçerli' zamanı içerebilirler. Saati ölçmek isteyen herhangi bir fonksiyonun bir 'clockStamp' parametresi olmalı ve aynı zamanda sonuçta son kez ölçümünü döndürmelidir (böylece arayan eski ölçümleri görmez):

// 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
}

Tabii ki, bu son ölçümü içeri ve dışarı, içeri ve dışarı, içeri ve dışarı geçirmek zorunda kalmak biraz sakıncalıdır. Isıtıcı plakayı, özellikle dil tasarımı düzeyinde gizlemenin birçok yolu vardır. Bence Haskell bu tür bir numara kullanıyor ve sonra çirkin kısımları monad kullanarak gizliyor.


İlginç, ancak i++for döngüsünde referans olarak şeffaf değil;)
snim2

@ snim2 Mükemmel değilim. : P Kirli mutabakatın sonucun referans şeffaflığını etkilememesine dikkat edin. Aynı 'lastMeasurement'i iki kez geçerseniz, eski bir sonraki ölçüm alırsınız ve aynı sonucu döndürürsünüz.
Craig Gidney

@Strilanc Bunun için teşekkürler. Zorunlu kodda düşünüyorum, bu yüzden fonksiyonel kavramların bu şekilde açıklandığını görmek ilginç. Daha sonra bu doğal ve sözdizimsel olarak daha temiz bir dil hayal edebiliyorum.
WW.

Aslında C # 'daki monad yoluna gidebilir, böylece zaman damgalarının açıkça geçmesini önleyebilirsiniz. Gibi bir şeye ihtiyacın var struct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }. Ancak bununla kod hala dosözdizimi ile Haskell kadar güzel görünmüyordu .
leftaroundabout

@ leftaroundabout, bind işlevini SelectManysorgu anlama sözdizimini etkinleştiren bir yöntem olarak uygulayarak C # 'da bir monad varmış gibi davranabilirsiniz . Yine de monadlar üzerinde polimorfik olarak programlayamazsınız, bu yüzden zayıf tip sisteme karşı yokuş yukarı bir savaş var :(
sara

16

Cevapların veya yorumların hiçbirinin kömürge veya koindüksiyondan bahsetmediğine şaşırdım. Genellikle, sonsuz veri yapıları hakkında akıl yürütmede koindüksiyondan bahsedilir, ancak aynı zamanda bir CPU üzerindeki zaman kaydı gibi sonsuz bir gözlem akışı için de geçerlidir. Bir kömürgebra gizli devleti modeller; ve bu durumu gözlemleyen coinduction modelleri . (Normal indüksiyon modelleri inşa devlet.)

Bu, Reaktif Fonksiyonel Programlama'da gündemde olan bir konudur. Bu tür şeylerle ilgileniyorsanız, şunu okuyun: http://digitalcommons.ohsu.edu/csetech/91/ (28 s.)


3
Ve bunun bu soru ile nasıl bir ilişkisi var?
Nawaz

5
Sorunuz zamana bağlı davranışı tamamen işlevsel bir şekilde, örneğin geçerli sistem saatini döndüren bir işlevi modellemekle ilgiliydi. Bu duruma erişmek için tüm işlevler ve bağımlılık ağacı aracılığıyla bir IO monad'a eşdeğer bir şey işleyebilirsiniz; ya da yapıcı kurallar yerine gözlem kurallarını tanımlayarak durumu modelleyebilirsiniz. Bu yüzden karmaşık durumun fonksiyonel programlamada endüktif olarak modellenmesi çok doğal görünmemektedir, çünkü gizli durum gerçekten bir koindüktiftir özelliktir.
Jeffrey Aguilera

Harika bir kaynak! Daha yeni bir şey var mı? JS topluluğu hala veri akışı soyutlamalarıyla mücadele ediyor gibi görünüyor.
Dmitri Zaitsev

12

Evet, saf bir işlevin parametre olarak verildiği takdirde zamanı döndürmesi mümkündür. Farklı zaman argümanı, farklı zaman sonucu. Daha sonra zamanın diğer fonksiyonlarını da oluşturun ve basit bir fonksiyon (-of-zaman) -transforming (yüksek dereceli) fonksiyon kelime dağarcığı ile birleştirin. Yaklaşım vatansız olduğundan, buradaki zaman ayrık olmaktan ziyade sürekli (çözünürlükten bağımsız) olabilir ve modülerliği büyük ölçüde artırır . Bu sezgi Fonksiyonel Reaktif Programlamanın (FRP) temelidir.


11

Evet! Haklısın! Now () veya CurrentTime () veya bu tür bir aromanın herhangi bir yöntem imzası, bir şekilde referans şeffaflığı göstermemektedir. Ancak derleyiciye talimat vererek bir sistem saat girişi tarafından parametrelendirilir.

Çıktı olarak, Now () referans şeffaflığını takip etmiyor gibi görünebilir. Ancak sistem saatinin gerçek davranışı ve üstündeki işlev referans şeffaflığına bağlıdır.


11

Evet, fonksiyonel programlamada saf olmayan fonksiyonel programlama olarak bilinen biraz değiştirilmiş bir sürüm kullanılarak fonksiyonel programlamada bir alma süresi fonksiyonu bulunabilir (varsayılan veya ana işlev saf fonksiyonel programlamadır).

Zamanın alınması (veya dosya okunması veya füze fırlatılması) durumunda, işin yapılabilmesi için kodun dış dünya ile etkileşime girmesi gerekir ve bu dış dünya işlevsel programlamanın saf temellerine dayanmaz. Saf bir fonksiyonel programlama dünyasının bu saf olmayan dünyayla etkileşime girmesine izin vermek için, insanlar saf olmayan fonksiyonel programlama başlattı. Sonuçta, dış dünya ile etkileşime girmeyen yazılımlar, bazı matematiksel hesaplamalar yapmaktan başka yararlı değildir.

Birkaç fonksiyonel programlama programlama dili, içlerinde hangi safsızlık ve hangisinin saf olduğunu (F #, vb.) Ayırmak kolay olmayacak şekilde bu safsızlık özelliğine sahiptir ve bazı fonksiyonel programlama dilleri, bazı saf olmayan şeyler yaptığınızda bu kod, Haskell gibi saf kodla karşılaştırıldığında açıkça göze çarpıyor.

Bunu görmenin bir başka ilginç yolu, fonksiyonel programlamada zaman alma fonksiyonunuzun, zaman, dünyada yaşayan insan sayısı vb. Gibi dünyanın mevcut durumuna sahip bir "dünya" nesnesini almasıdır. nesne her zaman saf olurdu yani her zaman aynı zaman alacak aynı dünya devleti geçmek.


1
"Sonuçta, dış dünya ile etkileşime girmeyen bir yazılım, bazı matematiksel hesaplamalar yapmaktan başka yararlı değil." Anladığım kadarıyla, bu durumda bile, hesaplamaların girdisi programda sabit kodlanmış olacaktı, aynı zamanda çok yararlı değil. Matematiksel hesaplamanıza giriş verilerini dosya veya terminalden okumak istediğiniz anda saf olmayan kod gerekir.
Giorgio

1
@Ankur: Bu aynı şey. Program sadece kendisinden başka bir şeyle etkileşime giriyorsa (örneğin, klavye aracılığıyla dünya, tabiri caizse) hala saf değildir.
kimlik

1
@Ankur: Evet, bence haklısın! Komut satırına büyük giriş verilerini iletmek çok pratik olmasa da, bunu yapmanın saf bir yolu olabilir.
Giorgio

2
Dünyada yaşayan insanların da dahil olduğu "dünya nesnesine" sahip olmak, yürütme bilgisayarını her şeyi bilen bir seviyeye yükseltir. Bence normal durum, HD'nizde kaç dosya olduğu ve geçerli kullanıcının ana dizini nedir gibi şeyler içermesidir.
ziggystar

4
@ziggystar - "dünya nesnesi" aslında hiçbir şey içermez - sadece programın dışındaki dünyanın değişen durumu için bir vekildir. Tek amacı, değişebilir durumu tür sisteminin tanımlayabileceği bir şekilde açıkça işaretlemektir.
Kris Nuttycombe

7

Sorunuz bir bilgisayar dilinin ilgili iki ölçümünü kapsamaktadır: işlevsel / zorunlu ve saf / saf olmayan.

İşlevsel bir dil, işlevlerin giriş ve çıkışları arasındaki ilişkileri tanımlar ve zorunlu bir dil, belirli işlemleri gerçekleştirilecek belirli bir sırayla tanımlar.

Saf bir dil yan etkilere neden olmaz veya yan etkilere bağlı değildir ve saf olmayan bir dil bunları baştan sona kullanır.

Yüzde yüz saf program temelde işe yaramaz. İlginç bir hesaplama yapabilirler, ancak yan etkileri olmadığı için girdi veya çıktıları yoktur, böylece ne hesapladıklarını asla bilemezsiniz.

Hiç faydalı olmak için, bir program en azından keskin bir safsızlık olmalıdır. Saf bir programı kullanışlı hale getirmenin bir yolu, onu ince bir saf olmayan sargıya koymaktır. Bu denenmemiş Haskell programı gibi:

-- 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

4
Zaman alma konusundaki özel sorunu ele alıp IOdeğerleri ve sonuçları ne ölçüde saf olarak değerlendirdiğimizi biraz açıklamanız yararlı olacaktır .
AndrewC

Aslında,% 100 saf programlar bile bir yan etki olan CPU'yu ısıtır.
Jörg W Mittag

3

Fonksiyonel programlamada çok önemli bir konuyu, yani G / Ç işlemini gerçekleştiriyorsunuz. Pek çok saf dilin gidişatı, gömülü alana özgü diller kullanmaktır, örneğin, görevi eylemleri kodlamak olan bir alt dil olabilecek olan ve sonuçları olabilen .

Örneğin Haskell çalışma zamanı, şöyle bir eylem tanımlamamı bekliyor: main programımı oluşturan tüm eylemlerden oluşan . Çalışma zamanı daha sonra bu eylemi yürütür. Çoğu zaman, bunu yaparken saf kod yürütür. Çalışma zamanı zaman zaman G / Ç gerçekleştirmek için hesaplanan verileri kullanır ve verileri tekrar saf koda geri gönderir.

Bunun hile gibi göründüğünden şikayet edebilirsiniz ve bir şekilde: eylemleri tanımlayarak ve çalışma zamanının bunları yürütmesini bekleyerek, programcı normal bir programın yapabileceği her şeyi yapabilir. Ancak Haskell'in güçlü tip sistemi, programın saf ve "saf olmayan" parçaları arasında güçlü bir bariyer oluşturur: mevcut CPU saatine iki saniye ekleyemezsiniz, diyelim ki yazdırabilirsiniz, akımla sonuçlanan bir eylem tanımlamanız gerekir. CPU zamanını seçin ve sonucu iki saniye ekleyen ve sonucu basan başka bir eyleme geçirin. Bir programın çok fazla yazılması kötü bir stil olarak kabul edilir, çünkü bize her şeyi anlatan Haskell türlerine kıyasla hangi etkilerin neden olduğunu ortaya çıkarmayı zorlaştırır bir değerin ne olduğu hakkında bildiğimiz .

Örnek: clock_t c = time(NULL); printf("%d\n", c + 2);C'de, main = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)Haskell'de. Operatör >>=, ilk işlemin sonucunu ikinci eylemle sonuçlanan bir işleve geçirerek eylemler oluşturmak için kullanılır. Bu oldukça gizemli görünen Haskell derleyicileri, ikinci kodu aşağıdaki gibi yazmamızı sağlayan sözdizimsel şekeri destekler:

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 ()

İkincisi oldukça zorunlu görünüyor, değil mi?


1

Evet ise, o zaman nasıl var olabilir? Fonksiyonel programlama ilkesini ihlal etmiyor mu? Özellikle referans şeffaflığını ihlal ediyor

Tamamen işlevsel bir anlamda mevcut değildir.

Ya da hayır ise fonksiyonel programlamanın şimdiki zamanını nasıl bilebilirsiniz?

İlk olarak bir bilgisayarda zamanın nasıl alındığını bilmek faydalı olabilir. Esasen, zamanı takip eden yerleşik devre vardır (bu, bir bilgisayarın genellikle küçük bir hücreli pile ihtiyaç duymasının sebebidir). Sonra belirli bir bellek kaydında zamanın değerini ayarlayan bazı dahili işlemler olabilir. Hangi aslında CPU tarafından alınabilecek bir değere kaynar.


Haskell için, bazı IO sürecini gerçekleştirmek için yapılabilecek bir türü temsil eden bir 'IO eylemi' kavramı vardır. Dolayısıyla bir timedeğere başvurmak yerine bir değere başvuruyoruz IO Time. Bütün bunlar tamamen işlevsel olacaktır. Referans vermiyoruz, timeancak 'zaman kaydının değerini okuyun' çizgisinde bir şey var .

Haskell programını gerçekten yürüttüğümüzde, aslında ES eylemi gerçekleşecekti.

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.