Czy funkcja jest natychmiast zanieczyszczona, jeśli przyjmuje funkcję jako parametr?

17

Ponieważ czystość parametru wejściowego jest nieznana do czasu działania, czy funkcja jest natychmiast uważana za nieczystą, jeśli przyjmuje funkcję jako parametr wejściowy?

Powiązane: jeśli funkcja stosuje funkcję czystą, która jest zdefiniowana poza funkcją, ale nie jest przekazywana jako parametr, czy nadal jest czysta, jeśli spełnia kryteria braku efektów ubocznych, a wynik zależy wyłącznie od danych wejściowych?

Dla kontekstu piszę kod funkcjonalny w JavaScript.

Dancrumb
źródło
Jako trywialny przykład, rozważ:foo = function(function bar){ print(bar.toString()) }
David mówi Przywróć Monikę
1
@DavidGrinberg Myślę, że to nie jest przeciwny przykład, i podkreśla większy problem; jeśli masz funkcje, które można zastąpić i nie możesz zagwarantować, że implementacje są wolne od efektów ubocznych, to nie możesz zagwarantować, że większość funkcji, które pobierają obiekty i wywołują ich metody, jest czysta. Może toString () bar usuwa niektóre pliki z dysku?
Joshua Taylor
3
@DavidGrinberg Ale myślę, że myślisz w dobrym kierunku. foo = function(function bar) { return 3; } jest czysty i przyjmuje funkcję jako argument.
Joshua Taylor
@JoshuaTaylor Fair point, nie myślałem o tym. Ale już zasadniczo rozwiązałeś ten problem. Jako alternatywną poprawkę, po prostu wywołaj „root” toString()(tj. Ten, który można znaleźć w obiekcie Java).
David mówi Przywróć Monikę

Odpowiedzi:

22

Dopóki wszystkie wartości użyte w funkcji są zdefiniowane wyłącznie przez jej parametry, jest to funkcja czysta.

Aspekt wyjściowy jest za każdym razem taki sam dla tych samych danych wejściowych jest kontrolowany przez to, czy parametry są czyste. Jeśli założymy, że parametry (takie jak argument funkcji) są również czyste, to jest czyste.

W języku takim jak Javascript, gdzie czystość nie jest wymuszona, oznacza to, że możliwe jest, aby w przeciwnym razie czysta funkcja miała nieczyste zachowanie, wywołując nieczystą funkcję przekazaną jako parametr.

Oznacza to skutecznie, że w przypadku języków, które nie wymuszają czystości (tj. Prawie wszystkich), niemożliwe jest zdefiniowanie funkcji czystej, która wywołuje funkcje przekazywane jako argumenty. Nadal przydatne jest pisanie ich tak czysto, jak to możliwe, i rozumowanie ich jako czystych funkcji, ale musisz zachować ostrożność, ponieważ założenie, że jest czyste, zostanie złamane, jeśli podasz niewłaściwe argumenty.

Z mojego doświadczenia w praktyce nie jest to zwykle wielka sprawa - zdarza się, że nieczyste funkcje mogą być używane jako argumenty funkcji dla funkcji czystych.

Daenyth
źródło
Jeśli chodzi o stwierdzenie, że „dopóki wszystkie wartości użyte w funkcji są zdefiniowane wyłącznie przez jej parametry, jest to funkcja czysta”. Co dzieje się w przypadku stałych? Jeśli mam jakąś funkcję areaOfCircle r => Math.Pi * r * r, czy areaOfCirclenie będzie ona czysta, ponieważ nie używa ona tylko parametrów?
David Arno
2
@DavidArno To słuszna kwestia. Zgodnie z przejrzystością referencyjną odwołanie się do statycznej wartości zewnętrznej nie różni się niczym od jej zakodowania na stałe, więc nadal byłoby czyste.
Daenyth,
1
„oznacza to, że czysta funkcja może mieć nieczyste zachowanie” - z definicji czysta funkcja nie może mieć nieczystego zachowania. Popełniasz błąd, myśląc, f(f2)że funkcja, która się wywołuje f2, nie zależy w żaden sposób od niczego f2. Funkcja, która może wywoływać dowolne przekazane funkcje, nie jest czysta.
user2357112 obsługuje Monikę
2
@ Daenyth: Lepiej, ale nadal zakłada, że ​​funkcja musi wywoływać przekazaną funkcję. Może to być coś podobnego function compose(f, g) {return function h(x) {return f(g(x));};}, co jest czyste pomimo przyjmowania funkcji jako argumentu.
user2357112 obsługuje Monikę
1
„Rzadko zdarza mi się, aby nieczyste funkcje były używane jako argumenty funkcji dla funkcji czystych”. - nie jest językiem funkcjonalnym, ale niektóre funkcje biblioteczne C ++ mają określone ostrzeżenia, że ​​argumenty predykatów muszą być (z pewnym przybliżeniem) czyste. W pewnym sensie oznacza to, że jest to nie tylko rzadkie, ale nigdy się nie zdarza. Ale w innym sensie fakt, że muszą tego zabronić, to dlatego, że ludzie czasem chcą to zrobić. Na przykład chcą przekazać findprocedurę nieczystemu predykatowi, który zwraca „prawda” dla trzeciego pasującego elementu, który napotyka, lub jakiegoś takiego nonsensu.
Steve Jessop
19

Ponieważ czystość parametru wejściowego jest nieznana do czasu działania, czy funkcja jest natychmiast uważana za nieczystą, jeśli przyjmuje funkcję jako parametr wejściowy?

Nie. Przeciwprzykład:

function pure(other_function) {
    return 1;
}

Nie ma znaczenia, czy other_functionjest to funkcja czysta, czy nieczysta, czy nie. pureDziałanie jest czyste.

Inne kontrprzykład:

function identity(x) {
    return x;
}

Ta funkcja jest czysta, nawet jeśli xjest nieczystą funkcją. identity(impure_function)zawsze wróci impure_function, bez względu na to, ile razy powtórzysz połączenie. Nie ma znaczenia, czy identity(impure_function)()zawsze zwraca to samo; wartość zwracana przez funkcję nie wpływa na jej czystość.


Ogólnie rzecz biorąc, jeśli funkcja może wywołać funkcję, została przekazana jako argument, nie jest czysta. Na przykład funkcja function call(f) {f();}nie jest czysta, ponieważ chociaż nie wspomina o żadnym stanie globalnym lub zmiennym, fmoże być coś takiego, alertco powoduje widoczne skutki uboczne.

Jeśli funkcja przyjmuje funkcje jako argumenty, ale ich nie wywołuje ani nie wywołuje, wówczas może być czysta. Może nadal być nieczyste, jeśli zrobi coś innego. Na przykład function f(ignored_function) {alert('This isn't pure.');}jest nieczyste, mimo że nigdy nie dzwoni ignored_function.

user2357112 obsługuje Monikę
źródło
4
Ta reakcja wydaje się zbyt pedantyczna. Możemy wywnioskować z pytania, że ​​problem dotyczy wywoływanego parametru funkcji. Istnienie funkcji, które mogą / mogą przyjmować inne funkcje jako parametr bez ich wywoływania, nie wpływa na to pytanie.
walpen
13
@walpen: Pytanie nie wspomina o przywołaniu argumentu. Nie ma powodu zakładać, że pytający zdał sobie sprawę, że funkcja może przyjąć inną funkcję jako dane wejściowe bez jej wywoływania. Ważne jest, aby wskazać takie ukryte założenia, a nie tylko zakładać, że masz je przyjąć.
user2357112 obsługuje Monikę
12

Ponieważ czystość parametru wejściowego jest nieznana do czasu działania, czy funkcja jest natychmiast uważana za nieczystą, jeśli przyjmuje funkcję jako parametr wejściowy?

Technicznie tak, chyba że w twoim języku jest jakiś sposób na zagwarantowanie, że funkcja wprowadzania danych jest również czysta.

jeśli funkcja stosuje funkcję czystą, która jest zdefiniowana poza funkcją, ale nie jest przekazywana jako parametr, czy nadal jest czysta, jeśli spełnia kryteria braku efektów ubocznych, a wynik zależy wyłącznie od danych wejściowych?

Tak. Skupmy się więc na tym, co ważne. Wywołanie funkcji czystej lub nie jest samo w sobie przydatne. Czyste funkcje są przydatne, ponieważ wytwarzanie tego samego wyniku dla dowolnego wejścia i niezależne od stanu lub powodujące skutki uboczne jest bardzo użytecznym zestawem właściwości. Oznacza to, że po uruchomieniu funkcji można „zapamiętać” odpowiedź na to wejście i zawsze będzie ona prawdziwa. Nie musisz też ponownie uruchamiać tej funkcji, aby generować efekty uboczne. I można uruchomić tę funkcję równolegle (lub poza kolejnością) z innymi funkcjami i wiem, że nie będzie miał żadnych ukrytych interakcji, które zachowują się źle.

Te użyteczne właściwości nadal obowiązują, jeśli funkcja wykonuje inne funkcje tylko do odczytu, niezależnie od tego, w jaki sposób się do nich odwołuje.

Telastyn
źródło
5

Jak powiedział Telastyn: Technicznie tak, chyba że w twoim języku jest jakiś sposób na zagwarantowanie, że funkcja wprowadzania danych jest również czysta.

To nie jest hipotetyczne, istnieją naprawdę dobre sposoby, aby to zagwarantować. Przynajmniej w silnie napisanym języku.

Taką czystą ~ funkcję, którą napisalibyście w JavaScript jako

function foo(f) {
   return f(1) + 2;
}

można przetłumaczyć bezpośrednio na Haskell:

foo :: (Int -> Int) -> Int
foo f = f 1 + 2

Teraz w JavaScript możesz robić złe rzeczy, takie jak

js> foo (function(x) {console.log("muharhar"); return 0})
muharhar
2

W Haskell nie jest to możliwe . Powodem jest to, że coś takiego jak efekt uboczny console.log()musi zawsze mieć typ wyniku IO something, a nie tylko somethingsam.

GHCi> foo (\x -> print "muarhar" >> return 0)

<interactive>:7:12:
    Couldn't match expected type ‘Int’ with actual type ‘IO b0’
    In the expression: print "muarhar" >> return 0
    In the first argument of ‘foo’, namely
      ‘(\ x -> print "muarhar" >> return 0)’
    In the expression: foo (\ x -> print "muarhar" >> return 0)

Aby to wyrażenie mogło sprawdzić typ, musielibyśmy podać foopodpis typu

foo :: (Int -> IO Int) -> Int

Ale okazuje się, że nie mogę go już zaimplementować: ponieważ IOw wyniku tego funkcja argumentu nie może go użyć w sobie foo.

<interactive>:8:44:
    Couldn't match expected type ‘Int’ with actual type ‘IO Int’
    In the first argument of ‘(+)’, namely ‘f 1’
    In the expression: f 1 + 2

Jedynym sposobem, w jaki mógłbym użyć IOakcji foojest to, że wynik fooma IO Intsam typ :

foo :: (Int -> IO Int) -> IO Int
foo f = do
   f1 <- f 1
   return (f1 + 2)

Ale w tym momencie z podpisu jasno wynika, fooże nie jest to również czysta funkcja.

po lewej stronie
źródło
1
Zanim powiesz „niemożliwe”, spójrz na unsafeIO:-)
Bergi
2
@Bergi: tak naprawdę to nie jest część Haskell, ale jego interfejs funkcji obcych: aby umożliwić stwierdzenie, że funkcja zdefiniowana w innym języku jest czysta, co kompilator Haskell oczywiście nie może wywnioskować z podpisu typu, ponieważ inne języki na ogół nie mają coś takiego jak IO. Nawiasem mówiąc, można go również użyć do wywołania chaosu, ukrywając skutki uboczne w „czystej” funkcji, ale w Haskell jest to naprawdę niebezpieczne, ponieważ nie ma naprawdę niezawodnego sposobu określenia kolejności oceny czystych funkcji.
leftaroundabout
Tak to prawda. Wydaje mi się jednak, że widziałem, że jest on używany do „ukrywania” korzystnych skutków ubocznych również w „czystej” funkcji, w której bezpieczeństwo tego podejścia musiało zostać wcześniej ustalone.
Bergi
@Bergi Najczęściej nie powinieneś używać unsafeIO; jest to klapa ratunkowa ostatniej szansy, która omija system gwarantowany przez system typów, a zatem twój nie jest dobrym pomysłem.
Andres F.
0

Nie, nie jest.

Jeśli przekazana funkcja jest nieczysta ORAZ twoja funkcja wywołuje przekazaną funkcję, wówczas twoja funkcja zostanie uznana za nieczystą.

Relacja czysta / nieczysta jest trochę podobna do synchronizacji / asynchronizacji w JS. Możesz swobodnie używać czystego kodu od nieczystego, ale nie na odwrót.

Bobby Marinoff
źródło
Ta odpowiedź nie dodaje niczego, co nie zostało jeszcze wyjaśnione w tym ... Przed dokonaniem przekształcenia zapoznaj się z poprzednimi odpowiedziami :)
Andres F.
Co z analogią synchronizacji / asynchronizacji?
Bobby Marinoff,