Zdaję sobie sprawę, że spóźniam się na przyjęcie, ale masz tutaj dwie teoretyczne odpowiedzi i chciałem zapewnić praktyczną alternatywę do przeżuwania. Dochodzę do tego jako krewny Haskell noob, który mimo to został ostatnio wymuszony przez temat Arrows do projektu, nad którym aktualnie pracuję.
Po pierwsze, możesz produktywnie rozwiązać większość problemów w Haskell bez sięgania po strzały. Niektórzy znani Haskellerzy naprawdę ich nie lubią i nie używają ich (zobacz tutaj , tutaj i tutaj, aby uzyskać więcej na ten temat). Więc jeśli mówisz do siebie: „Hej, nie potrzebuję ich”, zrozum, że naprawdę możesz mieć rację.
To, co najbardziej frustrowało mnie w Arrows, kiedy po raz pierwszy się nauczyłem, to sposób, w jaki samouczki na ten temat nieuchronnie sięgały po analogię obwodów. Jeśli spojrzysz na kod strzałki - przynajmniej odmiany cukrowej - nie przypomina on nic więcej niż język definicji sprzętu. Twoje wejścia ustawiają się po prawej stronie, twoje wyjścia po lewej, a jeśli nie uda ci się poprawnie je wszystkie połączyć, po prostu nie będą strzelać. Pomyślałem sobie: naprawdę? Czy to tam skończyliśmy? Czy stworzyliśmy język na tak wysokim poziomie, że znów składa się z drutów miedzianych i lutu?
Prawidłowa odpowiedź na to pytanie, o ile udało mi się ustalić, brzmi: Właściwie tak. Przypadkiem użycia zabójców w Arrows jest teraz FRP (pomyśl Yampa, gry, muzyka i systemy reaktywne w ogóle). Problem, przed którym stoi FRP, jest w dużej mierze tym samym problemem, z którym borykają się wszystkie inne synchroniczne systemy przesyłania wiadomości: jak połączyć ciągły strumień danych wejściowych w ciągły strumień danych wyjściowych bez upuszczania odpowiednich informacji lub powodowania przecieków. Możesz modelować strumienie jako listy - kilka najnowszych systemów FRP stosuje to podejście - ale gdy masz dużo danych wejściowych, zarządzanie listami staje się prawie niemożliwe. Musisz odizolować się od prądu.
Strzały dopuszczają w systemach FRP składanie funkcji w sieć, przy jednoczesnym całkowitym oderwaniu wszelkich odniesień do podstawowych wartości przekazywanych przez te funkcje. Jeśli dopiero zaczynasz przygodę z FP, może to być na początku mylące, a następnie oszałamiające, gdy wchłonąłeś jego implikacje. Dopiero niedawno zrozumiałeś, że funkcje można wyodrębnić i jak rozumieć listę taką [(*), (+), (-)]
jak typ [(a -> a -> a)]
. Za pomocą strzałek możesz przesuwać abstrakcję o jedną warstwę dalej.
Ta dodatkowa umiejętność abstrakcji niesie ze sobą własne niebezpieczeństwa. Po pierwsze, może popchnąć GHC do narożnych przypadków, w których nie wie, co zrobić z założeniami typu. Musisz być przygotowany do myślenia na poziomie typu - jest to doskonała okazja do zapoznania się z rodzajami i typami RankNTyp oraz innymi podobnymi tematami.
Istnieje również wiele przykładów tego, co nazwałbym „głupimi sztuczkami ze strzałami”, w których programista sięga po kombinator strzał, tylko dlatego, że chce popisać się zgrabną sztuczką z krotkami. (Oto mój własny trywialny wkład w szaleństwo .) Zapraszam do ignorowania takich upartych pechów, gdy natkniesz się na nie na wolności.
UWAGA: Jak wspomniałem powyżej, jestem względnym noobem. Jeśli ujawniłem powyżej jakiekolwiek nieporozumienia, proszę o poprawienie mnie.
removeAt' n = arr(\ xs -> (xs,xs)) >>> arr (take (n-1)) *** arr (drop n) >>> arr (uncurry (++)) >>> returnA
można je bardziej zwięźle i wyraźniej zapisać jakoremoveAt' n = (arr (take $ n-1) &&& arr (drop n)) >>> (arr $ uncurry (++))
.Jest to rodzaj „miękkiej” odpowiedzi i nie jestem pewien, czy jakieś odniesienie faktycznie podaje to w ten sposób, ale tak oto pomyślałem o strzałkach:
Typ strzałki
A b c
jest w zasadzie funkcją,b -> c
ale ma większą strukturę w taki sam sposób, jak wartość monadycznaM a
ma większą strukturę niż zwykła staraa
.To, jaka będzie ta dodatkowa struktura, zależy od konkretnej instancji strzały, o której mówisz. Podobnie jak w przypadku monad
IO a
iMaybe a
każda z nich ma inną strukturę dodatkową.To, co dostajesz z monadami, to niemożność przejścia od an
M a
doa
. Teraz może to wydawać się ograniczeniem, ale w rzeczywistości jest to cecha: system typów chroni cię przed zamianą wartości monadycznej w zwykłą starą wartość. Z wartości można korzystać tylko, uczestnicząc w monadzie za pośrednictwem>>=
lub prymitywnych operacjach konkretnej instancji monady.Z tego, co dostajesz,
A b c
jest niezdolność do zbudowania nowej „funkcji” produkującej c-b. Strzała chroni cię przed zużywaniemb
i tworzeniemc
wyjątku, uczestnicząc w różnych kombinatorach strzałek lub używając prymitywnych operacji konkretnego wystąpienia strzały.Na przykład funkcje sygnału w Yampie są z grubsza
(Time -> a) -> (Time -> b)
, ale dodatkowo muszą przestrzegać pewnego ograniczenia przyczynowości : wyjście w chwilit
zależy od przeszłych wartości sygnału wejściowego: nie możesz patrzeć w przyszłość. Więc zamiast programować(Time -> a) -> (Time -> b)
, programujesz za pomocąSF a b
i budujesz funkcje sygnałowe z prymitywów. Zdarza się, że ponieważSF a b
zachowuje się bardzo podobnie do funkcji, więc ta wspólna struktura nazywana jest „strzałką”.źródło
b
i tworzeniemc
wyjątku poprzez uczestnictwo w różnych kombinatorach strzałek lub użycie prymitywnych operacji konkretnego wystąpienia strzały”. Z przeprosinami za odpowiedź na tę starożytną odpowiedź: to zdanie przypomniało mi typy liniowe, tzn. Że zasobów nie można sklonować ani zniknąć. Czy uważasz, że może istnieć jakieś połączenie?Lubię myśleć o Strzałkach, takich jak Monady i Functory, które pozwalają programiście wykonywać egzotyczne kompozycje funkcji.
Bez monad i strzałek (i funktorów) składanie funkcji w języku funkcjonalnym ogranicza się do zastosowania jednej funkcji do wyniku innej funkcji. Za pomocą monad i funktorów możesz zdefiniować dwie funkcje, a następnie napisać osobny kod wielokrotnego użytku, który określa, w jaki sposób te funkcje, w kontekście konkretnej monady, oddziałują na siebie nawzajem oraz z danymi, które są do nich przekazywane. Ten kod jest umieszczony w kodzie powiązania Monady. Tak więc monada to jeden widok, tylko pojemnik na kod powiązania wielokrotnego użytku. Funkcje komponują się inaczej w kontekście jednej monady niż innej monady.
Prostym przykładem jest może monada, gdzie kod funkcji powiązania zawiera kod, tak że jeśli funkcja A jest złożona z funkcją B w obrębie monady Może, a B generuje Nic, to kod wiązania zapewni, że skład dwie funkcje generują Nic, nie zawracając sobie głowy zastosowaniem wartości A do Nic wychodzącego z B. Gdyby nie było monady, programista musiałby napisać kod do A, aby sprawdzić wejście Nic.
Monady oznaczają również, że programista nie musi jawnie wpisywać parametrów wymaganych przez każdą funkcję w kodzie źródłowym - funkcja wiązania obsługuje przekazywanie parametrów. Używając monad, kod źródłowy może zacząć wyglądać bardziej jak statyczny łańcuch nazw funkcji, niż wyglądać tak, jakby funkcja A „wywoływała” funkcję B z parametrami C i D - kod zaczyna przypominać bardziej obwód elektroniczny niż ruchoma maszyna - bardziej funkcjonalna niż konieczna.
Strzałki łączą również funkcje z funkcją wiązania, zapewniając funkcjonalność wielokrotnego użytku i parametry ukrywania. Ale same strzałki mogą być ze sobą łączone i komponowane, i mogą opcjonalnie kierować dane do innych strzałek w czasie wykonywania. Teraz możesz zastosować dane do dwóch ścieżek strzałek, które „wykonują różne czynności” względem danych, i ponownie złożyć wynik. Lub możesz wybrać gałąź strzałek, do której chcesz przekazać dane, w zależności od pewnej wartości w danych. Wynikowy kod jest jeszcze bardziej podobny do obwodu elektronicznego, z przełącznikami, opóźnieniami, integracją itp. Program wygląda bardzo statycznie i nie powinieneś widzieć dużej ilości manipulacji danymi. Jest coraz mniej parametrów do przemyślenia, a mniej trzeba myśleć o tym, jakie wartości wartości mogą przyjmować lub nie parametry.
Pisanie programu Arrowized polega głównie na wybieraniu z półki strzałek, takich jak rozdzielacze, przełączniki, opóźnienia i integratory, podnoszeniu funkcji do tych strzałek i łączeniu strzałek razem, aby utworzyć większe strzały. W Arrowized Functional Reactive Programming, strzałki tworzą pętlę, a dane wejściowe ze świata są łączone z danymi wyjściowymi z ostatniej iteracji programu, dzięki czemu dane wyjściowe reagują na dane wejściowe ze świata rzeczywistego.
Jedną z rzeczywistych wartości jest czas. W Yampa strzałka funkcji sygnału niewidocznie przewija parametr czasu przez program komputerowy - nigdy nie uzyskujesz dostępu do wartości czasu, ale jeśli podłączysz strzałkę integratora do programu, wyświetli wartości zintegrowane w czasie, które możesz następnie wykorzystać, aby przejść do inne strzały.
źródło
To tylko dodatek do innych odpowiedzi: osobiście bardzo pomaga mi zrozumieć, czym jest taka koncepcja (matematycznie) i jak odnosi się do innych pojęć, które znam.
W przypadku strzałek uważam, że następujący artykuł jest pomocny - porównuje monady, funktory aplikacyjne (idiomy) i strzały: idiomy są nieświadome, strzały są drobiazgowe, monady są rozwiązłe przez Sama Lindleya, Philipa Wadlera i Jeremy'ego Yallopa.
Uważam też, że nikt nie wspomniał o tym linku, który może dostarczyć ci pomysłów i literatury na ten temat.
źródło