Czy czyste funkcje mogą przyjmować argumenty tylko przez wartość?

Odpowiedz Nowy wątek
2019-08-16 17:04
2

Przejrzałem internet i obecnie mam następującą definicję czystej funkcji (niezależnie od języka programowania):

  1. Zwracana wartość (jeśli ją ma?) zależy wyłącznie od jej argumentów. — Mówiąc kolokwialnie, nie czyta z obiektu dostępnego z zewnątrz. [wykreślone, bo błędne]
  2. Nie posiada skutków ubocznych (side effects). — Mówiąc kolokwialnie, nie pisze do obiektu dostępnego z zewnątrz. [wykreślone, bo być może błędne]

Jednak zastanawia mnie pewna rzecz: z uwagi na punkt nr 1 wszystkie funkcje, które przyjmują argumenty przez referencję, nie będą czyste.

Dedukując, za czystą można uważać jedynie funkcję przyjmującą argumenty przez wartość.

Dobrze myślę?


UPDATE: Przykład w języku JavaScript:

// Funkcja, która moim zdaniem jest czysta według powyższej definicji
//    - liczby przekazywane są przez wartość *
function(someNumber) {
    return someNumber + 1;
}

// Funkcja, która moim zdaniem nie jest czysta według powyższej definicji
//    - obiekty przekazywane są przez referencję *
function(someObject) {
    return someObject.someProperty;
}

UPDATE2: * W języku JavaScript funkcje generalnie uznawane są za takie, które przyjmują argumenty przez wartość (np. według tej dokumentacji MDN), ale najpewniej nie o takie rozróżnienie mi chodzi. MDN podaje, że "object references are values". Dlatego też pominąłem to rozróżnienie wyżej.


edytowany 13x, ostatnio: Silv, 2019-08-17 00:50

Pozostało 580 znaków

2019-08-16 17:42
0

Nie, funkcji nie obchodzi skąd bierze się argument na którym operuje i wynika to wprost z definicji.

Btw:

Mówiąc kolokwialnie, nie czyta z obiektu dostępnego z zewnątrz.

jest błędne. Może czytać z obiektów zewnetrznych, pod warunkiem, że są niemutowalne (przez konwencję lub konstrukt języka). Inaczej domknięcia (integralny element FP) nie miałyby sensu.

Mówiąc kolokwialnie, nie pisze do obiektu dostępnego z zewnątrz.

To też tylko jeden z wielu potencjalnych efektów ubocznych.

edytowany 4x, ostatnio: Maciej Cąderek, 2019-08-16 17:52
Co by potwierdzało intuicję z mojego ostatniego postu. - Silv 2019-08-16 17:45
Co do tego, że to pierwsze stwierdzenie jest błędne, masz rację. Uogólniłem na pewien podzbiór cech języków programowania, nieopatrznie nie uwzględniając w nim niezmienności obiektów. - Silv 2019-08-17 00:20

Pozostało 580 znaków

2019-08-16 17:46
1

Ja nadal stoję przy swoim, jesli przekazujemy Listę Stringów to wartością sa te Stringi a nie referencja. Idąc takim tokiem rozumowania to nie ma w ogóle czystych funkcji, bo w takim Clojure też przekazujesz listy czyli z tego co wiem referencję...


Nie pomagam przez PM. Pytania zadaje się na forum.
Nie, idąc takim tokiem rozumowania (takim jak poprzednio), wszystkie funkcje przyjmujące argumenty przez referencje mogą być czyste. (Czyli jest lepiej, niż myślałem, zakładając ten wątek). - Silv 2019-08-16 17:50

Pozostało 580 znaków

2019-08-16 17:46
2

Po pierwsze co to jest czysta funkcja, która nie zwraca wartości, to musiałaby dawać jakieś side effects, inaczej byłaby tylko dekoracją w kodzie:) Po drugie primo, chodzi CI chyba o to, że jeżeli Przekazujesz referencję do obiektu, to możesz go zmienić w środku funkcji i wtedy nie byłaby czysta, ale... To zależy w jakim języku Programujesz, jeśli w imperatywnym to praktycznie wszystko Możesz zmienić i zależy od programisty, jak napisze funkcję; natomiast w funkyjnym wszystko jest immutable i nic nie Zmienisz, chyba, że w monadach etc.


O, ten jeszcze ważny punkt mi umknął – zmiana obiektu. Ale to raczej oczywiste – w przypadku przekazywania przez wartość, nie ma mowy o zmianie, a przez referencję – programista sam może napisać kod tak, by nie zmieniał. - Silv 2019-08-16 17:51

Pozostało 580 znaków

2019-08-16 17:48
0
Silv napisał(a):

Skoro mogą przyjmować argumenty przez referencję, to w rozumieniu "ogólnym" dwa wywołania funkcji nie będą zwracały takich samych wartości.

beda, zakladajac ze te dwa wywolania dostana obiekt o takim samym stanie. przekazywanie np wielkiego pliku xml przez wartosc nie ma zbytnio sensu, wiec musisz sie zadowolic taka a nie inna "czystoscia" ;), ew rzeczywiscie robic tylko niemutowalne rzeczy co generalnie jest utopia.

Pozostało 580 znaków

2019-08-16 17:55
0

@katelx: No tak, ale stan obiektu jest zależny tylko od tego, czy dany język udostępnia mechanizm do jego zmiany (jeśli udostępnia, nie mamy wpływu na zmianę, np. w jakimś innym module).


edytowany 3x, ostatnio: Silv, 2019-08-16 17:56

Pozostało 580 znaków

2019-08-16 18:01
0

masz kod w stylu

list.add("test");
int a = calc(list);
list.add("test2");
int b = calc(list);

i teraz jesli a != b to nie wyglada mi to na problem z czystoscia calc... w sumie to nie wiem o co ci chodzi, moglbys dac przyklad pokazujacy ten brak czystosci?

Pozostało 580 znaków

2019-08-16 18:04
2

Skoro mogą przyjmować argumenty przez referencję, to w rozumieniu "ogólnym" dwa wywołania funkcji nie będą zwracały takich samych wartości.

Ale też nie mają wtedy takich samych argumentów, więc jest wszystko "ok". Może inaczej - funkcja pozostaje czysta, bo przecież funkcja nie operuje na samych referencjach, tylko na obiektach, które się pod nimi kryją (a te są w tym przypadku inne), natomiast kod na zewnątrz funkcji już czysty nie jest wtedy.

Dla funkcji nie ma żadnej różnicy między tymi przykładami:

const fn = (obj) => obj.foo

const a = { foo: 1 }
const result1 = fn(a)
const b = { foo: 2 }
const result2 = fn(b)
const fn = (obj) => obj.foo

const a = { foo: 1 }
const result1 = fn(a)
a.foo = 2
const result2 = fn(a)

Natomiast jako całość pierwszy przykład jest "czysty", drugi nie.

edytowany 4x, ostatnio: Maciej Cąderek, 2019-08-16 18:09
Hm... jako całość... obecnie myślę podobnie (abstrahując od tego, czy można ową "całość" określić atrybutem "czystości"). - Silv 2019-08-17 00:29

Pozostało 580 znaków

2019-08-16 18:08
0

@katelx: zakładając wątek, miałem wątpliwość (której wtedy jeszcze nie umiałem wyrazić), czy "czystość" jest zapewniona przez dany język (jego mechanizmy), czy przez jakieś wyższe poziomy abstrakcji. Jeśli przez jakieś wyższe poziomy abstrakcji, to funkcja calc nie byłaby czysta. Jeśli przez język, to by była.

@Maciej Cąderek: nie zgodzę się. Moje rozumienie jest takie, że funkcja operuje na samych referencjach. Nie znam wszystkich mechanizmów C++ czy C, więc mogę się mylić... Co by jednak potwierdzało, że "czystość" zależy wyłącznie od języka.


Pozostało 580 znaków

2019-08-16 18:10
1

wiekszosc funkcji w wiekszosci jezykow programowania moze w teorii rzucic jakis rodzaj out of memory exception. tak jak wczesniej pisalam - aby zalozyc ze funkcja jest czysta trzeba sobie darowac ekstremalne sytuacje i zalozyc ze:

  • nie ma problemu ze sprzetem czy maszyna wirtualna
  • nie brakuje pamieci
  • inny watek nie manipuluje nam pamiecia
  • nie manipulujemy pamiecia sami (np poprzez jakies callbacki/hooki/hacki)
edytowany 1x, ostatnio: katelx, 2019-08-16 18:11
"inny watek nie manipuluje nam pamiecia"/"nie manipulujemy pamiecia sami" - hipotetycznie rzecz biorąc to nie ma znaczenia czy manipulujesz pamięcią czy nie, bo "czystość" jest matematyczno-teoretycznym pojęciem. W większości imperatywnych języków programowania jeśli mają one oznaczenie "czystości funkcji" (constexpr w C++, pure w D, const w Ruście) to służy ono głównie do oznaczenia, że daną funkcję można wykonać już w czasie kompilacji. - hauleth 2019-08-16 18:40
@katelx: ja bym tak daleko nie posuwał się w definicji funkcji czystej i pozostałbym jedynie na pierwszym niższym poziomie abstrakcji – tj. w ramach wywołań innych funkcji w tej funkcji. Nie szedłbym niżej po drabince wywołań. - Silv 2019-08-17 00:31

Pozostało 580 znaków

2019-08-16 18:30
3

Przy odpowiedniej dyscyplinie (traktowanie obiektów jako niemutowalnych) ew. przy siłowym wymuszeniu niemutowalności (np. Object.freeze) takie coś będzie czyste:

function getFoo(obj) {
   return obj.foo;
}

No bo zakładamy, że obj nie może się zmienić, jest immutable i już. Co z tego, że referencja.

z drugiej strony przy takim kodzie:

const obj = {foo: 123};

function getFoo(obj) {
   return obj.foo;
}
getFoo();
obj.foo++;
getFoo();

to cóż. Czy getFoo będzie czysta?
Może rozbijmy to na dwie części:

  • nie będzie mieć skutków ubocznych. Czyli można ją sobie nawet wywoływać z ileś razy bez ryzyka, że funkcja odpali jakiś skutek uboczny i coś zmieni zewnątrz.
  • ale nie będzie jednak idempotentna. Tzn. jeśli ją wywołamy ileś razy w różnych miejscach, to możemy mieć rózny wynik w zależności od tego co zawiera obj.

Tylko, że punkt drugi może "brudzi" funkcję, ale jednak takie funkcje mogą być bardzo przydatne (np. jako selektory, gettery czy coś, co robi jakieś obliczenia i zwraca wynik).
Nawet bym argumentował, że taka funkcja na jakimś poziomie abstrakcji będzie idempotentna. Bo zawsze będzie zwracać analogiczną wartość (nawet jeśli inną), jej zachowanie/rola będą takie same. To, że obiekt się zmienił pod spodem, to już problem reszty kodu. Funkcja nie jest winna, że coś się zmieniło (zresztą: jeśli obiekt się zmienił, to nie jest to już ten sam obiekt, bo ma inny stan już. Więc obiekt się zmienił w środku, nawet jeśli referencja pozostała taka sama. Więc funkcja dostaje już w argumencie inny (w środku) obiekt, nawet jeśli jest ten sam pod kątem tożsamości. Czyli ma prawo zwrócić inny wynik.

Z drugiej strony taka funkcja:

function foo() {
   return {};
}

zapewne będzie nazwana przez większość czystą funkcją. Ale jednak za każdym razem będzie zwracać inną wartość, bo każdy nowy obiekt jest inny:

console.log(foo() === foo())  // false

i tu pytanie jeszcze o to, czym jest ta sama wartość.

Więc myślę, że generalnie to zależy z której strony się patrzy i do czego nam ta czystość potrzebna (chyba, że z perspektywy formalnej, to przypuszczam, że istnieją jakieś formalne definicje, jednak jestem praktykiem i patrzę od strony praktycznej. Jednak i tak - jak wymyślano programowanie funkcyjne kilkadziesiąt lat temu to nikt i tak nie myślał, że powstanie taki dziwny język jak JavaScript, w którym wszystko jest niepewne).


((0b10*0b11*(0b10**0b101-0b10)**0b10+0b110)**0b10+(100-1)**0b10+0x10-1).toString(0b10**0b101+0b100);
edytowany 5x, ostatnio: LukeJL, 2019-08-16 18:41
Pokaż pozostałe 23 komentarze
@LukeJL: OK, dzięki. Jeśli tak działa wzorzec Proxy, to brzmi trochę jak mieszanie odpowiedzialności i/lub jest nieintuicyjne? - Silv 2019-08-17 17:30
Hm. Jeszcze muszę o tym poczytać. - Silv 2019-08-17 17:34
czemu mieszanie odpowiedzialności? - LukeJL 2019-08-17 19:47
Nie wiem, muszę poczytać. - Silv 2019-08-17 19:51

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0