2016-08-27 48 views
5

Próbuję poznać funkcjonalne programowanie w JavaScript. Właśnie read że wskaźnik funktor jest:Jak poprawnie używać spiczastego funktora?

Obiekt z of funkcji, które stawia każdą pojedynczą wartość do niego.

ES2015 dodaje Array.of czyniąc tablice ostrym funktorem.

Moje pytanie brzmi: co dokładnie oznacza "pojedyncza wartość"?

Chcę utworzyć Functor/Container (podobnie jak w https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch8.html), który utrzymuje siatkę o danym wymiarze (szerokość, wysokość) jako tablicę jednowymiarową i pozwala mi na jej transformacje. Jako zwykły obiekt zapisałbym go jako { width: 2, height: 2, list: [1, 2, 3, 4] }, ale chcę umieścić go w funktorze i nie jestem pewien, jak zrobić to poprawnie.

wiem, że to perfekcyjnie używać spiczastą funktor tak przechowywać pojedynczą wartość:

Container.of(47) 

Ale to jest ok aby użyć obiektu jako wartości zakładającej obiektu jest „pojedyncza wartość”:

Grid.of({ width: 2, height: 2, list: [1, 2, 3, 4] }) 

Albo jeszcze tak:

Grid.of(2, 2, [1, 2, 3, 4]) 
+0

co to jest "Foo.of"? miałeś na myśli Array.of? Pomyślałem, że od wersji Es2015 tylko Array i * TypedArray * miały 'of' –

+0

. Jaka jest twoja oczekiwana wydajność? Nie całkiem rozumiem. – Leo

+0

Podaj przykład swoich danych wejściowych i oczekiwanej wynikowej zawartości tablicy - w tej postaci zmieszałeś mnie z 'Foo' –

Odpowiedz

5

Wyjaśnienie w https://github.com/hemanth/functional-programming-jargon jest niestety mało dokładne.

spiczasty funktor jest naprawdę funktor F razem z funkcją of zdefiniowanej dla każdego typu a i wysyłanie wartości x typu a na wartość of(x) typu F a. W Hindley-Milner signature wygląda to tak:

of :: a -> F a 

Na przykład, funktor Array jest wskazał of = x => [x], określona dla każdej wartości x dowolnego typu a.

Ponadto, funkcja of (lub dokładniej, zbiór funkcji of jak masz jeden dla każdego typu a) musi być naturalna transformacja z funktora tożsamości w F. Co oznacza, że ​​of wartości funkcji jest równa of argumentu odwzorowanym przez tę samą funkcję:

of(f(x)) === of(x).map(f) 

Na przykład, w przykładzie Array masz

[f(x)] === [x].map(f), 

tak x => [x] jest rzeczywiście naturalny przemiana.

Ale można też wziąć

of = x => [x, x] 
[f(x), f(x)] === [x, x].map(f) 

który jest inny szpiczasty funktor, nawet jeżeli funktor na map pozostaje taka sama. (Zauważ, że w każdym przypadku masz bardzo specjalne tablice jako wartości of(x).)

Nie można jednak wziąć np.

of = x => [x, 0] 
[f(x), 0] !== [x, 0].map(f) 

Teraz

var grid = Grid.of({ width: 2, height: 2, list: [1, 2, 3, 4] }) 

jest zupelnie zwraca Twój obiekt przeszedł owinięty w Grid. Następnie możesz zmapować swoją grid z jakąkolwiek zwykłą funkcją f z czystych obiektów na zwykłe obiekty, a wynik będzie taki sam jak zastosowanie f i zawijanie do Grid, z powodu prawa naturalnego przekształcania. Zauważ, że w ten sposób możesz także zadzwonić pod numer Grid.of z dowolną inną wartością, taką jak Grid.of({width: 2}) z nawet Grid.of(2). Alternatywnie można ograniczyć typy, dla których zdefiniowano Grid.of, a następnie wartość musi być dozwolona tylko dla określonego typu.


Ten jest nieco kłopotliwe:

Grid.of(2, 2, [1, 2, 3, 4]) 

Dotyczy Grid.of kilka argumentów.Ponieważ Grid.of jest z definicji funkcją tylko jednego argumentu, wynikiem będzie Grid.of(2), co może nie być tym, czego potrzebujesz. Jeśli naprawdę chcesz karmić wszystkie wartości, prawdopodobnie chcesz napisać

Grid.of([2, 2, [1, 2, 3, 4]]) 

Alternatywnie, można przedłużyć Grid.of do wielu argumentów przez pre-owijając je do tablicy wewnętrznie, a następnie zastosowanie Grid.of. To naprawdę zależy od tego, czego szukasz.

Aby zapoznać się z rzeczywistym przykładem użycia, zobacz np. here gdzie "Nudne" zadanie jest zdefiniowane przez Task.of z wartości zwykłej. Z drugiej strony, bardziej interesujące jest zadanie here z funkcją, której nie można uzyskać z Task.of. Ważne jest jednak, że oba zadania mogą być używane z tym samym jednolitym interfejsem, jak pokazano na obu przykładach.

Należy również zauważyć, że w tych przykładach nie stosuje się funktorów aplikacyjnych, więc nadal istnieją zastosowania wskazanych funktorów bez zastosowania.

4

Ale czy można użyć obiektu jako wartości przyjmując, że obiekt jest "pojedynczą wartością":

Tak. of ma pobrać dowolną wartość i umieścić ją w kontenerze. Obiekt z pewnością jest taką pojedynczą wartością.

Grid.of(2, 2, [1, 2, 3, 4])

nr of ma wziąć jeden parametr. Jeśli chcesz umieścić wiele wartości wewnątrz funktora, umieść je wewnątrz innej struktury przed i umieść tę strukturę wewnątrz funktora lub skonstruuj funktor przez coś innego niż jego funkcję punktu (of).

Grid.of({ width: 2, height: 2, list: [1, 2, 3, 4] })

Nie, jeśli można oczekiwać, że powrót wejście to nie będzie działać. of powinien przyjmować dane wejściowe bez zmian i owijać strukturę wokół niego. W przypadku swojej sieci, to z pewnością wyglądać następująco:

// Grid<A> 
class Grid { 
    // Int -> Int -> [A] -> Grid<A> 
    constructor(w, h, vals) { 
     assert(Number.isInteger(w) && Number.isInteger(h)); 
     this.width = w; 
     this.height = h; 
     const list = Array.from(vals); 
     assert(list.length == w * h); 
     this.list = list; 
    } 
    // Grid<A> -> (A -> B) -> Grid<B> 
    map(f) { 
     return new Grid(this.width, this.height, this.list.map(f)); 
    } 
    // A -> Grid<A> 
    static of(x) { 
     return new Grid(1, 1, [x]); 
    } 
} 

Więc powyższe wezwanie stworzyłoby Grid obiektów, a nie siatki z czterech cyfr. Zauważ, że of nie jest jedynym sposobem skonstruowania instancji funktora, to tylko sposób na skonstruowanie instancji z jednego elementu.

Zauważ, że of jest najważniejszym elementem Aplikacji, a nie jest tak interesujący dla zwykłych Funktorów.Przy okazji, jeśli interesują Cię koncepcje programowania funkcjonalnego, powinieneś być w stanie uczynić swój Grid Monoidem, Traversable i Monad - patrz https://github.com/fantasyland/fantasy-land.

+0

Wygląda więc na to, że wcale nie potrzebuję "of", a wszystko, czego potrzebuję, to przyzwoity sposób tworzenia obiektu. Unikałem lekcji, ponieważ wydają mi się one niefunkcjonalne i nigdy nie widziałem ich w żadnych tutorialach ... Teraz widzę, że te dwa świat można połączyć w jedno. – jesper

+0

Jak sprawić, by konstruktor był czysty? Wyobrażam sobie, że 'assert' powinien rzucić jakiś wyjątek w świecie OOP, ale co, jeśli chciałbym uczynić go czysto funkcjonalnym? Jestem dobrze obeznany z prawą, lewą i prawą stroną, ale nie jestem pewien, jak ją zastosować tutaj. Jednym z rozwiązań, jakie mam, jest delegowanie tworzenia obiektu Grid do funkcji sprawdzającej, czy podane wartości określają poprawną siatkę, a następnie zwracają _jeden_ obiekt siatki lub błąd. Czy to właściwe podejście? – jesper

+1

@jesper: Tak, 'of' nie jest wymagane i nie powinno być jedynym sposobem tworzenia instancji. Potrzebujesz go tylko wtedy, gdy chcesz, aby twój typ danych był zgodny ze specyfikacją fantasy-land, co sprawia, że ​​działa on poprawnie z wieloma funkcjonalnymi bibliotekami, takimi jak Ramda. I tak, "klasy" nie są w żaden sposób sprzeczne z podejściem funkcjonalnym - zawsze potrzebujesz sposobu, aby zrobić "rekordy" lub "obiekty" do przechowywania danych. Możesz zrobić to samo z funkcją fabryczną i funkcjami statycznymi, właśnie wybrałem składnię 'class' tutaj, ponieważ jest krótsza. – Bergi