Closure

Odpowiedz Nowy wątek
2019-09-04 14:00
0

Proste rekurencyjne wypisanie kolejnych liczb.

Jestem uczony w stylu starej daty, napisałbym bez wahania w stylu Old.

function logNumbersRecursivelyOld(start, end) {
    console.log(`Recursively looping from ${start} until ${end}`);

    function recurse(left, right) {
        console.log(`Hitting index ${left}`);

        if (left < right) {
            recurse(left + 1, right);
        }
    }

    recurse(start, end);
}

Niedawno usłyszałem, że tak nadmiarowo to się już nie pisze, tylko tak:

function logNumbersRecursivelyNew(start, end) {
    console.log(`Recursively looping from ${start} until ${end}`);

    function recurse(left) {
        console.log(`Hitting index ${left}`);

        if (left < end) {
            recurse(left + 1);
        }
    }

    recurse(start);
}

Wywołanie jawne recurse(left, right) pisze się teraz recurse(left) a aktualna wartość argumentu right idzie w closure?
Jak dla mnie stary (przestarzały?) styl z jawnym przekazaniem obu argumentów function recurse(left, right) jest bardziej czytelny.

@BraVolt: Zmieniłbym tytuł na bardziej opisowy, np. "Czy rekurencja bez domknięć jest przestarzała?". - Silv 2019-09-04 16:08
@Silv A co z Java? ;)))) - BraVolt 2019-09-04 16:12
@BraVolt: nie rozumiem, co masz na myśli? - Silv 2019-09-04 16:14
Nie każdy popularny język ma closures choć wspiera paradygmat funkcyjny - BraVolt 2019-09-04 16:16
Może tak jest, ale wydawało mi się, że pytasz o używanie domknięć, a nie ma sensu o nich mówić, jeśli dany język ich nie wspiera. - Silv 2019-09-04 17:58

Pozostało 580 znaków

2019-09-04 14:20
0

Jaki jest problem? Jeżeli w języku jest domknięcie "closure", to obydwa listingi są równoważne (czasowo i pamięciowo).


Pozostało 580 znaków

2019-09-04 14:25
0

Niby w starym stylu w takich językach piszą tylko dinozaury.

Pozostało 580 znaków

2019-09-04 14:30
0

A to już inna sprawa, jak Jesteś "cool kid", to wiadomo jak Napiszesz;)


Pozostało 580 znaków

2019-09-04 15:20
5

Od jakiegoś czasu czytam i próbuję zrozumieć "Struktura i interpretacja programów komputerowych" z 1996 (Second Edition) i mam wrażenie że ten New Style to tak naprawdę Old Old Style. po prostu wiele języków nie posiadało dobrze działających closure więc trzeba było kantować. W Scheme zawsze były closure więc można było je używać.


Podobno How To design Programs jest lepsze: https://htdp.org/.(Nie wiem, nie czytałem całości) - lion137 2019-09-04 15:59

Pozostało 580 znaków

2019-09-04 16:11
0

Na szybko bo zostałem przez górę pogoniony do pilnej roboty jeszcze na dziś
Mamy function as first class citizen to musimy mieć closures żeby wszystko działało modelowo

let tank = 'Rudy';

function battle() {
    console.log(tank + 'destroyed Tiger');
}

setTimeout(battle, 1000);

Specjalnie setTimeout bo to już przekazanie funkcji do innej "biblioteki" w której to scope nie ma widocznej zmiennej tank.

Bez closure, z parametrem byłoby kombinowanie

tank = {
    name: 'T-34'
};

function kursk(name) {
    console.log(this.name + ' fought & won');
}

setTimeout(kursk.bind(tank), 1000);

Tylko IMHO wpakowywanie closure wszędzie - jak np do rekurencji - prostych funkcji pomocniczych - bo closure jest cool cool czyni kod dużo mniej czytelnym

Pozostało 580 znaków

2019-09-04 16:23
1
BraVolt napisał(a):

Tylko IMHO wpakowywanie closure wszędzie - jak np do rekurencji - prostych funkcji pomocniczych - bo closure jest cool cool czyni kod dużo mniej czytelnym

No i niestety tu wszystko kończy się na "IHMO". Np kiedyś czytałem że prywatne metody zawsze powinny być statyczne, żeby było widać które metody korzystają z których pól w obiekcie. (Zalecenie dla języka Java)

W obu wypadkach można się kłócić czy lepiej przekazywać zmienną jawnie przez parametr, czy lepiej żeby była to zmienna wolna. IHMO jeśli klasa/funkcja lub ogólnie zakres leksykalny jest mały i mieści się na jednym ekranie to parametr tylko zaciemnia.


edytowany 1x, ostatnio: Kamil Żabiński, 2019-09-04 16:24

Pozostało 580 znaków

2019-09-04 16:32
0

"Tylko IMHO wpakowywanie closure wszędzie - jak np do rekurencji - prostych funkcji pomocniczych - bo closure jest cool cool czyni kod dużo mniej czytelnym" - tam gdzie pasuje zastosowanie closure kod na pewno będzie czytelniejszy, Python, podaję za "Fluent Python":

def make_average():
    s = []
    def ave(n):
        s.append(n)
        return sum(s) / len(s)
    return ave

>>> a = make_average()
>>> a(1)
1.0
>>> a(2)
1.5
>>> a(3)
2.0
>>> a(4)
2.5

Co ta funkcja ma robić, bo dziwnie wygląda jej użycie? - Silv 2019-09-04 18:00
@Silv: liczy średnią ze wszystkich argumentów przekazanych jako argument kiedykolwiek. - hauleth 2019-09-04 18:31
Tak, liczy średnią kroczącą, a argumenty, dzięki closure, przechowuje w tablicy s. - lion137 2019-09-04 18:59
Dzięki, @hauleth, @lion137. Nie znam się na średniej kroczącej, może jej wykorzystanie narzuca takie działanie; jednak jeśli nawet, to przechowywanie tablicy w funkcji wydaje mi się nieintuicyjne w tym przypadku. - Silv 2019-09-04 19:34

Pozostało 580 znaków

2019-09-04 19:43
1

"Nie znam się na średniej kroczącej, może jej wykorzystanie narzuca takie działanie; jednak jeśli nawet, to przechowywanie tablicy w funkcji wydaje mi się nieintuicyjne w tym przypadku"
To jest właśnie wykorzystanie domknięcia, i HOF's, można to samo osiągnąć tworząc obiekt, ale, jak dla mnie opcja funkcyjna jest czytelniejsza i ładniejsza.

class Averager():
    def __init__(self):
        self.series = []
    def __call__(self, new_value):
        self.series.append(new_value)
        total = sum(self.series)
        return total/len(self.series)

Pokaż pozostałe 12 komentarzy
@lion137: niech kryterium będzie testowanie. Przecież łatwiej testować algorytm, jeśli jest "maksymalnie" oddzielony od danych? - Silv 2019-09-04 21:50
Ciężko coś powiedzieć na temat tego przykładu, bo jest bardzo prosty, nawet dzielenia przez zero tam nie ma. Co do oddzielenia algorytmu od danych, w Lispie, na przykład dane i algorytm to to samo, listy; także ciężko mi stwierdzić. - lion137 2019-09-04 22:00
@lion137: Niedokładnie się wyraziłem, miałem na myśli struktury danych, nie dane jako takie. Ale przecież nadal choćby takie testy jednostkowe są potrzebne, które sprawdzają, czy dane przekazywane do funkcji/metody/konstruktora nie są undefined/null/"puste"/itd. - Silv 2019-09-04 22:07
W tescie jednostkowym to możesz co najwyżej przetestować zachowanie funkcji jak trafi do niej undefined/null, a nie czy taka wartość do niej trafi. - Maciej Cąderek 2019-09-04 22:17
@Maciej Cąderek: tak, o to mi chodziło, nieściśle się wyraziłem. - Silv 2019-09-04 22:22

Pozostało 580 znaków

2019-09-04 22:42
cs
2

Jak dla mnie stary (przestarzały?) styl z jawnym przekazaniem obu argumentów function recurse(left, right) jest bardziej czytelny.

Może czytelny, ale bez domknięcia męczysz stos przekazywaniem w kółko parametru right, który jest stały w każdym wywołaniu. Więc tu raczej nie moda gra rolę, ale względy praktyczne. Rekurencyjne metody do czytelnych nie należą, więc domknięcie tu raczej dużo nie popsuje.

W Javie też da się zrobić takie "bieda" domknięcie, tylko kod nie wygląda zbyt elegancko:

class Recursive<I>{
    public I func;
}
...
void logNumbersRecursivelyNew(int start, int end) {
        Recursive<Function<Integer, Void>> recurse = new Recursive<>();
        recurse.func  = left -> {
            System.out.printf("Hitting index %d\n", left);
            if (left < end){
                recurse.func.apply(left+1);
            }
            return null;
        };
        recurse.func.apply(start);
    }
edytowany 1x, ostatnio: cerrato, 2019-09-04 22:52
Java niestety taka już jest. Niby wszystko można zrobić, ale wszystko wygląda biednie :/ - Kamil Żabiński 2019-09-05 08:34

Pozostało 580 znaków

2019-09-04 23:05
1

Może czytelny, ale bez domknięcia męczysz stos przekazywaniem w kółko parametru right, który jest stały w każdym wywołaniu.

Tylko jeśli nie ma TCO, co w większości języków już jest. A nawet jak nie ma TCO, to jeśli kompilator/interpreter optymalizuje, to to zostanie wyoptymalizowane, zwłaszcza jeśli IR używa SSA.

Java nie ma optymalizacji rekurencji ogonowej, Groovy podobnie, a Clojure ma jakąś bieda wersje, bo podobno się nie da na JVM. Co ciekawe Scala i Kotlin mają. Ciekawe czy języki skryptowe (Perl, Python, Ruby) mają. W wersji normalnej/natywnej i na JVM. IHMO więcej jednak nie ma niż ma TCO - Kamil Żabiński 2019-09-05 08:38
@Kamil Żabiński: w ograniczonym stopniu JVM wspiera tail-recursion, ale nie TCO. Więc częściowo również wspiera. - hauleth 2019-09-05 12:35
@Kamil Żabiński: Clojure wymaga wykonania wywołania rekurencyjnego explicite operatorem recur, ale nie nazwałbym tego bieda wersją. W połączeniu z loop pozwala to pisać anonimowe funkcje rekurencyjne, a z nazywaniem funkcji wewnętrznych zawsze mam problem. :) Python nie ma TCO, Perl 6 ma, Ruby ma, ale nie by default. - Kalrais 2019-09-05 13:03

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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

Robot: CCBot