Jak może istnieć funkcja czasu w programowaniu funkcyjnym?

Muszę przyznać, że niewiele wiem o programowaniu funkcyjnym. Czytałem o tym tu i ówdzie, i tak dowiedziałem się, że w programowaniu funkcyjnym, funkcja zwraca to samo wyjście, dla tego samego wejścia, bez względu na to, ile razy funkcja jest wywoływana. To jest dokładnie jak funkcja matematyczna, która ocenia do tego samego wyjścia dla tej samej wartości parametrów wejściowych, które obejmuje w wyrażeniu funkcji.

Na przykład, rozważ to:

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

Nieważne ile razy użyjesz f(10,4), jego wartością zawsze będzie 104. W związku z tym, gdziekolwiek napisałeś f(10,4), możesz zastąpić go 104, bez zmiany wartości całego wyrażenia. Właściwość ta jest określana jako referential transparency wyrażenia.

Jak mówi Wikipedia (link),

Odwrotnie, w kodzie funkcyjnym, wartość wyjściowa funkcji zależy tylko od argumentów, które są wejściowe do funkcji, więc wywołanie funkcji f dwa razy z tą samą wartością dla argumentu x da ten sam wynik F(x) za każdym razem.

Czy funkcja czasu (która zwraca bieżący czas) może istnieć w programowaniu funkcyjnym?

  • Jeśli tak, to jak może istnieć? Czy nie narusza to zasady programowania funkcyjnego? W szczególności narusza referential transparency, która jest jedną z właściwości programowania funkcyjnego(jeśli dobrze rozumiem).

  • Lub jeśli nie, więc jak można znać aktualny czas w programowaniu funkcyjnym?

Author: Peter Mortensen, 2011-09-01

13 answers

Inny sposób na wyjaśnienie tego jest następujący: Żadna Funkcja nie może pobrać bieżącego czasu( ponieważ ciągle się zmienia), ale akcja może pobrać bieżący czas. Załóżmy, że getClockTime jest stałą (lub funkcją zerową, jeśli chcesz), która reprezentuje działanie uzyskiwania bieżącego czasu. Ta akcja jest taka sama za każdym razem, bez względu na to, kiedy jest używana, więc jest rzeczywistą stałą.

Podobnie, powiedzmy {[2] } jest funkcją, która zajmuje trochę czasu i drukuje na konsoli. Ponieważ wywołania funkcji nie mogą mieć skutków ubocznych w czystym języku funkcyjnym, zamiast tego wyobrażamy sobie, że jest to funkcja, która pobiera znacznik czasu i zwraca akcję wydrukowania jej na konsoli. Ponownie, jest to prawdziwa funkcja, ponieważ jeśli podasz jej ten sam znacznik czasu, zwróci ona tę samą akcję wydrukowania jej za każdym razem.

Jak możesz wydrukować bieżący czas na konsoli? Musisz połączyć te dwa działania. Więc jak można zrobimy to? Nie możemy po prostu przekazać getClockTime do print, ponieważ print oczekuje znacznika czasu, a nie akcji. Ale możemy sobie wyobrazić, że istnieje operator >>=, który łączy dwie akcje, jedną, która otrzymuje znacznik czasu, a drugą, która pobiera jeden argument i drukuje go. Stosując to do działań wcześniej wymienionych, wynik jest... tadaaa... nowa akcja, która Pobiera bieżący czas i drukuje go. I tak właśnie dzieje się w Haskell.
Prelude> System.Time.getClockTime >>= print
Fri Sep  2 01:13:23 東京 (標準時) 2011

Więc koncepcyjnie, można to zobaczyć w ten sposób: czysty program funkcjonalny nie wykonuje żadnych operacji We/Wy, definiuje akcję, którą następnie wykonuje runtime system. Akcja jest zawsze taka sama, ale wynik jej wykonania zależy od okoliczności, kiedy zostanie wykonana.

Nie wiem, czy to było jaśniejsze niż inne wyjaśnienia, ale czasami pomaga mi myśleć o tym w ten sposób.

 134
Author: dainichi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-03 04:21:12

Tak i nie.

Różne języki programowania funkcyjnego rozwiązują je inaczej.

W Haskell (bardzo czystym) wszystko to musi się wydarzyć w czymś, co nazywa się I/O Monad - patrz tutaj .

Możesz myśleć o tym jako o kolejnym wejściu (i wyjściu) do swojej funkcji (stanu świata) lub łatwiejszym jako o miejscu, w którym dzieje się "bezkarność", taka jak zmiana czasu.

Inne języki, takie jak F#, mają wbudowaną bezczelność i dlatego może mieć funkcję, która zwraca różne wartości dla tego samego wejścia-tak jak normalne języki imperatywne.

Jak wspomniał Jeffrey Burka w swoim komentarzu: Oto ładne wprowadzenie do I/O Monad prosto z Haskell wiki.

 343
Author: Carsten,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-03 04:23:39

W Haskell jeden używa konstruktu o nazwie monad do obsługi skutków ubocznych. Monada oznacza w zasadzie, że można hermetyzować wartości w kontenerze i mieć pewne funkcje do łańcucha funkcji od wartości do wartości wewnątrz kontenera. Jeśli nasz kontener ma typ:

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

Możemy bezpiecznie realizować działania IO. Ten typ oznacza: akcja typu IO jest funkcją, która pobiera token typu RealWorld i zwraca nowy token wraz z wynikiem.

Idea stojąca za każda akcja IO mutuje stan zewnętrzny, reprezentowany przez magiczny token RealWorld. Za pomocą monad można łączyć wiele funkcji, które mutują świat rzeczywisty. Najważniejszą funkcją monady jest >>=, wymawiane bind :

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

>>= wykonuje jedną akcję i funkcję, która bierze wynik tej akcji i tworzy z niej nową akcję. Typem zwracanym jest nowa akcja. Na przykład, załóżmy, że istnieje funkcja now :: IO String, która zwraca Łańcuch reprezentujący bieżący czas. Możemy go połączyć za pomocą funkcji putStrLn, aby wydrukować:

now >>= putStrLn

Lub zapisany w do - notacji, która jest bardziej znana programiście imperatywnemu:

do currTime <- now
   putStrLn currTime

Wszystko to jest czyste, ponieważ mapujemy mutację i informacje o świecie zewnętrznym na token RealWorld. Więc za każdym razem, gdy uruchamiasz tę akcję, otrzymujesz oczywiście inne wyjście, ale wejście nie jest takie samo: token RealWorld jest inny.

 136
Author: fuz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-12 02:38:17

Większość funkcjonalnych języków programowania nie jest czysta, tzn. pozwalają funkcjom nie tylko zależeć od ich wartości. W tych językach jest całkowicie możliwe, aby funkcja zwracająca bieżący czas. Z języków, które otagowałeś to pytanie dotyczy Scala i F # (jak również większości innych wariantów ML).

W językach takich jak Haskell i Clean , które są czyste, sytuacja jest inna. W Haskell prąd czas nie byłby dostępny przez funkcję, ale tzw. działanie IO, które jest sposobem Haskella na enkapsulację efektów ubocznych.

W Clean byłaby to funkcja, ale funkcja przyjmowałaby wartość światową jako swój argument i zwracałaby wartość świeży świat (oprócz bieżącego czasu) jako swój wynik. System typów upewniałby się, że każda wartość światowa może być użyta tylko raz (a każda funkcja, która zużywa wartość światową, tworzy nową). W ten sposób funkcja czasu muszą być wywołane z innym argumentem za każdym razem, a tym samym będą mogły zwracać inny czas za każdym razem.

 66
Author: sepp2k,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-03 04:27:25

"bieżący czas" nie jest funkcją. Jest to parametr. Jeśli kod zależy od bieżącego czasu, oznacza to, że kod jest parametryzowany przez czas.

 44
Author: Vlad Patryshev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-01 17:46:46

Można to zrobić w sposób czysto funkcjonalny. Istnieje kilka sposobów, aby to zrobić, ale najprostszym jest, aby funkcja czasu zwracała nie tylko czas, ale także funkcję, którą musisz wywołać, aby uzyskać następny pomiar czasu .

W C# można zaimplementować to tak:

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

(Należy pamiętać, że jest to przykład mający być prosty, a nie praktyczny. W szczególności węzły listy nie mogą być zbierane, ponieważ są zakorzenione przez ProgramStartTime.)

Ta klasa 'ClockStamp' działa jak niezmienna, powiązana lista, ale tak naprawdę węzły są generowane na żądanie, więc mogą zawierać 'bieżący' czas. Każda funkcja, która chce mierzyć czas, powinna mieć parametr 'clockStamp' i musi również zwrócić swój ostatni pomiar czasu w swoim wyniku( aby wywołujący nie widział starych pomiarów), tak:

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

Oczywiście, to trochę niewygodne, aby przejść ten ostatni pomiar w I Na zewnątrz, w I Na zewnątrz, w I Wynocha. Istnieje wiele sposobów na ukrycie kotła, szczególnie na poziomie projektowania języka. Myślę, że Haskell używa tego rodzaju sztuczek, a następnie ukrywa brzydkie części za pomocą monad.

 20
Author: Craig Gidney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-13 13:39:14

Dziwię się, że żadna z odpowiedzi lub komentarzy nie wspomina o węglu lub coinduction. Zwykle coindukcja jest wspomniana przy rozumowaniu o nieskończonych strukturach danych, ale ma również zastosowanie do niekończącego się strumienia obserwacji, takiego jak rejestr czasu na procesorze. Coalgebra modeluje stan ukryty; i modele coindukcji obserwując ten stan. (Normalne modele indukcyjne konstruowanie stanu.)

Jest to gorący temat w reaktywnym programowaniu funkcyjnym. Jeśli jesteś interesują Cię takie rzeczy, przeczytaj to: http://digitalcommons.ohsu.edu/csetech/91 / (28 str.))

 13
Author: Jeffrey Aguilera,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-19 00:11:01

Tak, jest możliwe, aby czysta funkcja zwracała czas, jeśli podano ten czas jako parametr. Inny argument czasowy, inny wynik czasowy. Następnie formuj również inne funkcje czasu i łącz je z prostym słownikiem funkcji (- of-time) - przekształcających (wyższego rzędu) funkcje. Ponieważ podejście jest bezpaństwowe, czas tutaj może być ciągły (niezależny od rozdzielczości), a nie dyskretny, znacznie zwiększenie modułowości . Intuicja ta jest podstawą funkcjonalnej reaktywności Programowanie (FRP).

 11
Author: Conal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-14 23:22:43

Tak! Masz rację! Now() lub CurrentTime() lub jakakolwiek metoda podpisu takiego smaku nie wykazuje referencyjnej przezroczystości w jeden sposób. Jednak zgodnie z instrukcją dla kompilatora jest on parametryzowany przez wejście zegara systemowego.

Przez wyjście, Now() może wyglądać tak, jakby nie podążała za referencyjną przezroczystością. Jednak rzeczywiste zachowanie zegara systemowego i jego funkcji jest zgodne z przejrzystość odniesienia.

 10
Author: MduSenthil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-01 18:02:36

Tak, Funkcja getting time może istnieć w programowaniu funkcyjnym przy użyciu nieco zmodyfikowanej wersji programowania funkcyjnego znanej jako nieczyste Programowanie funkcyjne (domyślna lub Główna to czyste Programowanie funkcyjne).

W przypadku uzyskania czasu (lub odczytania pliku lub wystrzelenia rakiety) kod musi wejść w interakcję ze światem zewnętrznym, aby wykonać zadanie i ten świat zewnętrzny nie jest oparty na czystych fundamentach programowania funkcyjnego. Aby umożliwić czysty funkcjonalny świat programowania aby wejść w interakcję z tym nieczystym światem zewnętrznym, ludzie wprowadzili nieczyste Programowanie funkcyjne. W końcu oprogramowanie, które nie wchodzi w interakcję ze światem zewnętrznym, nie jest użyteczne poza wykonywaniem obliczeń matematycznych.

Kilka języków programowania funkcyjnego ma wbudowaną w nie funkcję nieczystości, tak że nie jest łatwo oddzielić, który kod jest nieczysty, a który czysty (jak F#, itd.), a niektóre funkcjonalne języki programowania zapewniają że kiedy robisz nieczyste rzeczy, kod wyraźnie wyróżnia się w porównaniu z czystym kodem, takim jak Haskell.

Innym ciekawym sposobem, aby to zobaczyć, byłoby to, że funkcja get time w programowaniu funkcyjnym zajęłaby obiekt "świata", który ma aktualny stan świata, taki jak czas, liczba ludzi żyjących na świecie, itp. Następnie uzyskanie czasu, z którego obiekt świata będzie zawsze czysty, tzn. przechodzisz w tym samym stanie świata, zawsze otrzymasz ten sam czas.

 10
Author: Ankur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-22 21:55:40

Twoje pytanie łączy dwie powiązane miary języka komputerowego: funkcjonalny / imperatywny i czysty / nieczysty.

Język funkcyjny definiuje relacje między wejściami i wyjściami funkcji, a język imperatywny opisuje określone operacje w określonej kolejności do wykonania.

Czysty język nie tworzy ani nie zależy od efektów ubocznych, a nieczysty język używa ich w całym procesie.

W stu procentach czyste programy są w zasadzie bezużyteczne. Mogą wykonaj interesujące obliczenia, ale ponieważ nie mogą mieć skutków ubocznych, nie mają wejścia ani wyjścia, więc nigdy nie wiesz, co obliczyli.

Aby program był w ogóle użyteczny, musi być przynajmniej odrobiną nieczystości. Jednym ze sposobów na uczynienie czystego programu użytecznym jest umieszczenie go w cienkim nieczystym opakowaniu. Jak ten niesprawdzony program Haskell:

-- 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
 7
Author: NovaDenizen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-02 05:26:48
Jeśli tak, to jak może istnieć? Czy nie narusza to Zasady programowanie funkcjonalne? W szczególności narusza ona odnośne przejrzystość
Nie istnieje w sensie czysto funkcjonalnym.

Lub jeśli nie, to jak można znać aktualny czas w funkcjonalnym programowanie?

Może być najpierw przydatne wiedzieć, jak czas jest pobierany na komputerze. Zasadniczo istnieje układ pokładowy, który śledzi czas (który jest powód komputer zwykle potrzebuje małej baterii ogniwowej). Wtedy może istnieć jakiś wewnętrzny proces, który ustawia wartość czasu w określonym rejestrze pamięci. Co zasadniczo sprowadza się do wartości, która może być pobrana przez procesor.


Dla Haskella istnieje pojęcie "działania IO", które reprezentuje typ, który można wykonać w celu przeprowadzenia jakiegoś procesu IO. Tak więc zamiast odwoływać się do wartości time odwołujemy się do wartości IO Time. Wszystko to byłoby czysto funkcjonalne. Nie jesteśmy odwołując się do time ale coś w stylu 'odczytaj wartość rejestru czasu' .

Kiedy faktycznie wykonamy program Haskell, akcja IO rzeczywiście ma miejsce.

 1
Author: Chris Stryczynski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-14 17:26:21

Poruszasz bardzo ważny temat w programowaniu funkcyjnym, czyli wykonywaniu operacji wejścia / Wyjścia. sposób, w jaki wiele czystych języków robi to za pomocą wbudowanych języków specyficznych dla domeny, np. podrzędnego języka, którego zadaniem jest kodowanie akcji, które mogą mieć wyniki.

Środowisko uruchomieniowe Haskell na przykład oczekuje ode mnie zdefiniowania akcji o nazwie main, która składa się ze wszystkich akcji składających się na mój program. Następnie runtime wykonuje tę akcję. Przez większość czasu, w w ten sposób wykonuje czysty kod. Od czasu do czasu runtime użyje obliczonych danych do wykonywania operacji wejścia/wyjścia i przekazuje dane z powrotem do czystego kodu.

Możesz narzekać, że to brzmi jak oszustwo i w pewnym sensie jest: definiując akcje i oczekując, że runtime je wykona, programista może zrobić wszystko, co normalny program może zrobić. Ale system silnego typu Haskella tworzy silną barierę między czystymi i "nieczystymi" częściami programu: nie można po prostu dodać, powiedzmy, dwóch sekund do bieżący czas procesora i wydrukuj go, musisz zdefiniować akcję, która spowoduje bieżący czas procesora i przekazać wynik do innej akcji, która dodaje dwie sekundy i wyświetla wynik. Pisanie zbyt wielu programów jest uważane za zły styl, ponieważ sprawia, że trudno wywnioskować, które efekty są spowodowane, w porównaniu z typami Haskell, które mówią nam Wszystko możemy wiedzieć o tym, czym jest wartość.

Przykład: clock_t c = time(NULL); printf("%d\n", c + 2); w C, VS. main = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000) w Haskell. Operator >>= służy do komponowania działania, przekazując wynik pierwszego do funkcji skutkującej drugą akcją. Wygląda to dość tajemniczo, Kompilatory Haskella obsługują cukier składniowy, który pozwala nam napisać ten ostatni kod w następujący sposób:

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 ()
[5]} ten ostatni wygląda na imperatywny, prawda?
 1
Author: MauganRa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-23 04:56:42