Pseudo functional style - piętnuję

4

Piętnuję styl 'pseudo funkcyjny', taki w którym koder leci z filter, map, reduce, pipeline bo modne, a side effects wyłażą bokami jak dziury w skarpetkach

*Uproszczona symulacja
*
JSON lista obiektów z produktem i ceną
cena większa od 40 to cena o połowę w dół, do zwrotu lista promocyjna
oczywiście bazowa lista produktów nie ma się zmieniać

Leci sobie kod (przykłady - bdnopis), biały człowiek patrzy, widzi map, zakłada na szybko, że to pure function
a to zmyłka, badPromotion - mutuje przy okazji oryginalna lista cen

const badPromotion = products =>
    products
        .map(product => {
            if (product.price > 40) {
                product.price /= 2;
            }

            return product;
        });

const promotion = products =>
    products
        .map(product => {
            const p = {name: product.name, price: product.price}

            if (product.price > 40) {
                p.price = p.price /= 2;
            }

            return p;
        });

module.exports = {promotion, badPromotion}

Dla porządku testy, tylko promotion() nie mutuje otrzymanych danych o cenach

const {promotion, badPromotion} = require('./products');

describe('Promotion related tests', () => {

    function getProducts() {
        return [
            {name: 'banana', price: 20},
            {name: 'orange', price: 40},
            {name: 'apple', price: 30},
            {name: 'plum', price: 10},
            {name: 'peach', price: 50}
        ];
    }

    let products;

    beforeEach(() => {
        products = getProducts()
    })

    it('badPromotion should modify original prices', () => {
        const promoProducts = badPromotion(products);
        expect(products[4].price).toEqual(25);
        expect(promoProducts[4].price).toEqual(25);
    })

    it('promotion should not modify original prices', () => {
        const promoProducts = promotion(products);
        expect(products[4].price).toEqual(50)
        expect(promoProducts[4].price).toEqual(25)
    })

})

Myślę, okropny JavaScript, jakbym to była rozgadana Java to od razu by się rzuciło w oczy.
No to sprawdzam
Pojo

package pl.bv.p48;

import lombok.Data;

@Data
public class Product {

    private String name;
    private int price;
}

Promocje

package pl.bv.p48;

import java.util.List;
import java.util.stream.Collectors;

public abstract class Promotion {

    public static List<Product> badPromotion(List<Product> products) {

        return products
                .stream()
                .map(product -> {

                    if (product.getPrice() > 40) {
                        product.setPrice(product.getPrice() / 2);
                    }

                    return product;
                })
                .collect(Collectors.toList());
    }

    public static List<Product> promotion(List<Product> products) {

        return products
                .stream()
                .map(product -> {
                    final Product p = new Product();
                    p.setName(product.getName());
                    p.setPrice(product.getPrice());

                    if (product.getPrice() > 40) {
                        p.setPrice(p.getPrice() / 2);
                    }

                    return p;
                })
                .collect(Collectors.toList());
    }
}

I testy

package pl.bv.p48;

import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

public class PromotionTest {

    private List<Product> products;

    @Before
    public void setUp() throws Exception {

        Product apple = new Product();
        apple.setName("apple");
        apple.setPrice(20);

        Product orange = new Product();
        orange.setName("orange");
        orange.setPrice(50);

        products = Arrays.asList(apple, orange);
    }

    @Test
    public void badPromotion() {

        final List<Product> updatedProducts = Promotion.badPromotion(products);

        assertThat(products, not(sameInstance(updatedProducts)));
        assertThat(updatedProducts.get(1).getPrice(), equalTo(25));
        assertThat(products.get(1).getPrice(), equalTo(25));
    }

    @Test
    public void goodPromotion() {

        final List<Product> updatedProducts = Promotion.promotion(products);

        assertThat(products, not(sameInstance(updatedProducts)));
        assertThat(updatedProducts.get(1).getPrice(), equalTo(25));
        assertThat(products.get(1).getPrice(), equalTo(50));
    }
}

I co? I zachowuję się jak szeryf w Teksasie. Widzę pipeline filter, map, reduce to myślę "no side effect". Szeryf widzi białego albo czarnego i też od razu szufladkuje. To wygląda, że będzie dobre a to nie.
Nic z tego, szukasz buga, szukasz złodziei, w obu przypadkach nie upraszczaj sobie pracy, nie idź na skróty, analizuj.

3

Dokładnie. U mnie np również szeroko używamy streamów, itp itd, aczkolwiek zdarza się też że jakiś programista tworzy jakieś fajne interfejsy funkcyjne, a później mutuje stan argumentów :/

0

Jak leci z mutowaniem bo tak to ma wg. niego działać, to OK. Gorzej kiedy (jak wrzuciłem na przykład) mutowanie jest pod pewnym rzadko występującym warunkiem

9

Cargo cult. Ludzie naśladują pewien styl, ale nie wiedzą dlaczego.
To tak samo jak ludzie piszą "OOP", które różni się od proceduralnego tylko tym, że wrzucą kod w klasy. Plus do wszystkiego gettery/settery, bo przecież "tak inni robią".

1

Wniosek? Standardowym kolekcjom z Javy nie można ufać. Tylko jak przekonać zespół do przejścia na vavr w projekcie który ma lata?

1

Przykład w Java zrobiłem dla sportu i żeby zobaczyć jak to by wyglądało. Siedziałem nad JavaScript, a w JS to od zawsze wolna amerykanka w kodzie.

0

Wniosek - referencje to zło. Szczególnie gdy budujesz przez nie teamy.

1

Podpisuje się obiema rekami, w jezykach jak java/c# taki styl jest nie skalowanly, nie utrzymywalny, nie czytyletny. Fuj. Lamda jest dobra do prostych map, reduce, where itp. Przez brak duck typingu, silne typowanie wszystkiego nie da się w praktyce pisać prosto duzych lambd- bez klniecia pod nosem lub tworzenia właśnie takich kwiatków. Dlaczego? Lamda tworzy oczekiwanie, zrobie to w dwie linie i bedzie git, ale tak sie nie da. Trzeba dopisać jaką nową zmienną potem pewnie przekopiować wartośći, przy wiecej niż jednej to bardzo upiedliwe, potem powalczyć z klamerkami, klamerkami w klamerkach, nawiasem na końcu, ostani średnikiem. To całkiem dużo irytującej roboty, i nie są to dwie linie. A spróbuj zmodyfikować dodając jakiś parametr... Formatowanie też nie jest przyjemne. Tego nie da się przeszkoczyć i taki styl bedzie zawsze prowadził do bałąganu.

Bardziej skalowalne jest napisać mała fukcje, lub funckje lokaną, potem uzyć jej zmiast lambdy lub w jedno linijkowej lamdzie. Nakład pracy, na napisanie fukcji, jest taki sam lub mniejszy niż nie trywialnej lamby, wiec czasu zabiera tyle samo. A dzieki temu:
Linq/streamy można pisać jedno linijkowo.
Fukcja ma nazwe, która powinna wprost mowić co się dzieje, wiec masz spełnione oczekiwanie, to co czytam to się stanie, nawet jeśli są slideefekty. Czyli powod załozenia wątku znika. Słowem, jest czytelniej.
Dodatkowo, przez to że masz normalną fukcje z nazwą, a nie jakieś nie wiadomo co doklejone do jezyka, wolno Ci stosować wszystkie sztuczki z metodami do których java/c# zostały zaprojektowane. Wiec bardziej sie skaluje pisać małe funkcje niż potworki.

8

Protest programistów funkcyjnych

mutuje.jpg

2
_flamingAccount napisał(a):

Fukcja ma nazwe, która powinna wprost mowić co się dzieje, wiec masz spełnione oczekiwanie, to co czytam to się stanie, nawet jeśli są slideefekty. Czyli powod załozenia wątku znika. Słowem, jest czytelniej.

Pod warunkiem, że ktoś dobrze nazwie taką funkcję. A nie spodziewałbym się dobrego nazewnictwa po kimś, kto mutuje w takich miejscach.

1

Kiedyś młode frontendy się mnie doczepiły, że używam for zamiast forEach w JS. Bo nowe musi być lepsze. Aczkolwiek w pewnym momencie doszedłem do wniosku, że może faktycznie lepiej pisać kod, który jest bardziej zrozumiały dla ludzi, którzy nie mieli nigdy styczności ze "starym" JS, jeśli tacy są w zespole.

0
tsz napisał(a):

jeśli tacy są w zespole.

Wszystko fajnie, jeśli tacy są drzwi obok albo piętro niżej/wyżej
Inne miasto albo kraj to nie połapiesz tego sensownie w całość.

Dlatego IMHO (i nie tylko IMO) wszyscy przyjęci powinni mieć podstawowy szeroki!!! zasób wiedzy. Nie pamiętam/pamiętasz składni? OK, jest StackOverflow. Ale nie ma bata, każdy musi przynajmniej kojarzyć co to jest, o co chodzi.

Może bez CSS, bo widziałem 'opracowania' jak na 15 sposobów wypośrodkować w pionie div, a i tak kod był zrobiony w tabeli z zafixowanymi wymiarami.

Akademii Magii w Ban Ard nie kończyłem, przy magii CSS odpadam

0
BraVolt napisał(a):

Akademii Magii w Ban Ard nie kończyłem, przy magii CSS odpadam

Ja nie. Czasami sam się siebie boję jak coś wyczaruję w CSS.

1
tsz napisał(a):

Ja nie. Czasami sam się siebie boję jak coś wyczaruję w CSS.

Jak już bardzo ale to bardzo musze, to na bazarze kupuję flakoniki z miksturą bootstrapową i modlę się do Proroka Lebiody żeby to wystarczyło.

Prorok Lebioda
Zazwyczaj jest przedstawiany jako brodaty pasterz pasący swe owce.
Zginął zjedzony przez smoka, którego chciał powstrzymać od nękania kaedweńskich wieśniaków.
Szczątki proroka, odzyskane z odchodów stworzenia, zostały zebrane przez jego uczniów
i złożone do sarkofagu w świątyni w Novigradzie.
W niektóre święta, wierni mogą całować relikwie.
3

Tak to jest jak jeden cymbał z drugim przeleci kurs na szybko i jedyne co zostaje mu w głowie, to to, że map, filter, reduce itd nie mutują tablicy, tylko zwracają nową, ale już nie ma wystarczająco oleju w głowie żeby nie mutować obiektów wewnątrz tych funkcji.

0

W jednym z nowych greenfield projektów nad jakimi pracuję, na froncie korzystają z RxJs i utarło się już pisanie w stylu:

getWhatever()
  .pipe(
    tap(value => this.whatever = value),
    map(value => getFooByWhatever(value)),
    tap(foo => this.foo = value),
    tap(foo => doSth()) // doSth korzysta wewnątrz z this.foo i this.whatever...
  ).subscribe();

przy czym w pipe jest czasem 20+ takich instrukcji a subscribe jest prawie zawsze puste. Czasem zawiera jakąś logikę ale nie znalazłem reguły. Nie mam argumentów, bo to nowe devy z wieloletnim doświadczeniem we froncie i rx, ja dopiero teraz mam z tym styczność i podobno tak się pisze, jest czytelniej i od razu wiadomo co się dzieje w przeciwieństwie do

getWhatever()
  .subscribe(value => {
    this.whatever = value;
    this.foo = getFooByWhatever(value);
    doSth();
  });

bo jak się dowiedziałem - taki kod jest nieczytelny, ciężko się połapać co się dzieje i nie korzysta z flołów więc ciężej go zmienić. Jeszcze długa nauka przede mną żeby zostać front-endowcem

2

Można to zrobić prosto mapem bez mutowania:

const promotion = (products) =>
  products.map(({ price, ...product }) => ({
    price: price > 40 ? price / 2 : price,
    ...product,
  }));

To nie jest wada map - tylko jego nieumiejętne użycie.

W "greenfield" projekcie, zazwyczaj jest podpięty linter z jakimś standardem np. airbnb który nie pozwala na modyfikacje parametrów funkcji w żaden sposób (nie tylko w map).

0

https://www.learnrxjs.io/

Introduction
RxJS is one of the hottest libraries in web development today.
But...
Learning RxJS and reactive programming is hard. There's the multitude of concepts, large API surface, and fundamental shift in mindset from an imperative to declarative style.

Strach się bać.

Pierwszy link z googla: https://rxjs-dev.firebaseapp.com/
BLACK LIVES MATTER
We stand in solidarity with the Black Lives Matter movement. We believe that technologists must not be silent in the fight to end racial inequality.
[...]
In solidarity, we ask you to consider financially supporting efforts such as Black Lives Matter, The Equal Justice Initiative or local charity organizations.

3

Tylko prawilny Haskell

0

To że ktoś używa map i filter nie znaczy jeszcze że próbuje/pisze funkcyjnie.

Prawdopodobnie zobaczył gdzieś ich użycie i zastosował. To że te funkcje zostały stworzone z myślą; albo że ktoś/większość uważa że powinny być używane funkcyjnie (tzn np. pure function), niestety znaczy że każdy to wie.

To nie jest kod pseudo-funkcyjny. To jest kod nie-funkcyjny.

0
TomRiddle napisał(a):

Prawdopodobnie zobaczył gdzieś ich użycie i zastosował.

Na 80% miało to miejsce przy czytaniu wymagań ofertowych a później zrobieniu zadanka rekrutacyjnego.

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