Projektowanie naszej struktury aplikacji wysokiego poziomu

Wystarczy teorii, przejdźmy do działania budującego naszą własną aplikację. Aplikacja, którą zbudujemy, będzie korzystać z poprzedniego rozdziału, więc jeśli go nie czytałeś, zrób to. Pulpit nawigacyjny, który zbudujemy, będzie miał większy sens, jeśli to zrobisz. Ten pulpit nawigacyjny pokaże wykresy z punktami danych cenowych z symulacji danych z poprzedniego rozdziału, a także z obliczeń SMA, które opracowaliśmy. Co więcej, pozwoli nam to zbadać dane cenowe za pomocą dynamicznej tabeli. Przez dynamiczną rozumiemy, że reaguje na dane wejściowe użytkownika.

Konfigurowanie dystrybucji dwukolumnowej

Układ, który wybierzesz dla swojej aplikacji, zależy od jej celów. W takim przypadku wystarczy układ dwukolumnowy. Aby to osiągnąć, używamy funkcji fluidPage() i przypisujemy ją do obiektu ui. Ta funkcja dostosowuje zawartość do wymiarów przeglądarki internetowej:

Wewnątrz fluidPage() używamy funkcji titlePanel() do podania tytułu naszej aplikacji i funkcji sidebarLayout() do stworzenia układu dwukolumnowego. Ta ostatnia funkcja wymaga wywołania w niej dwóch innych funkcji w celu utworzenia odpowiedniej zawartości dla każdej kolumny. Te dwie funkcje są nazywane  sidebarPanel() i miniPanel() i otrzymują treść, którą chcemy w nich stworzyć jako parametry. Kolumna po lewej stronie będzie używana do wyświetlania dostępnych opcji dla użytkowników, a kolumna po prawej do wyświetlania rzeczywistej zawartości w wyniku danych wejściowych użytkownika, więc używamy niektórych ciągów znaków jako symboli zastępczych, które dokładnie opisują to:

ui <- fluidPage (

titlePanel(„Cryptocurrency Markets”),

sidebarLayout (

sidebarPanel(„Options”),

mainPanel(„Content”)

}

}

fuidPage() po prostu generuje HTML, który jest wysyłany do przeglądarki internetowej. Możesz wydrukować obiekt w konsoli R w miarę postępów w rozdziale, aby zobaczyć HTML, który utworzył. Ten kod utworzy bardzo podstawową strukturę, taką jak pokazano na następnym obrazku. W miarę postępów będziemy coraz bardziej skomplikować aplikację, ale musimy od czegoś zacząć. Jak widać, wywołania funkcji zagnieżdżania będą typowym wzorcem w obiekcie ui do tworzenia struktury aplikacji. Może to być trudne, a jeśli z jakiegoś powodu pominiesz gdzieś przecinek („ , ”), możesz znaleźć tajemniczą wiadomość, taką jak ta pokazana. W takim przypadku upewnienie się, że przecinki są poprawnie umieszczone, jest dobrym początkiem do naprawienia tego błędu:

Error in tag(„div”, list(…)) : argument is missing , with no default

Calls : fluidPages … tablesetPanel -> tabPanel -> div ->  -> tag

Funkcje wejścia, wyjścia i renderowania

Każda funkcja *Input() wymaga kilku argumentów. Pierwsza to ciąg znaków z nazwą widżetu, który będzie używany tylko przez Ciebie. Druga to etykieta, która zostanie wyświetlona użytkownikowi w Twojej aplikacji. Pozostałe argumenty dla każdej funkcji *Input() różnią się w zależności od jej funkcjonalności. Obejmują takie rzeczy, jak wartości początkowe, zakresy i przyrosty. Dokładne argumenty potrzebne widgetowi można znaleźć na stronie pomocy funkcji widgetu (na przykład ? selectIput). Poniższa tabela przedstawia wszystkie dostępne funkcje *Input() ze wskazaniem, do czego są używane:

Funkcja *Input() : Użycie

actionButton() : Przycisk akcji

checkboxGroupInput() : Grupa pól wyboru

checkboxInput() : Pojedyncze pole wyboru

dateInput() : Wybór daty

dateRangeInput() : Wybór zakresu dat

fileInput() : Udostępnianie pliku

helpText() : Tekst pomocy dla formularzy wejściowych

numericInput() :Numeryczne pole wejściowe

radioButtons : Zestaw opcji w przyciskach radiowych

selectInput() : Zestaw opcji w menu rozwijanym

sliderInput() : Suwak wprowadzania numerycznego

submitButton() : Przycisk Prześlij

textInput() : Pole wprowadzania tekstu

Każda z funkcji *Output() wymaga pojedynczego argumentu, którym jest ciąg znaków, którego Shiny użyje do zidentyfikowania odpowiedniego obserwatora na podstawie parametru output w funkcji server. Użytkownicy nie zobaczą tej nazwy, będzie ona używana tylko przez Ciebie. Poniższa tabela przedstawia listę wszystkich dostępnych funkcji *Output() ze wskazaniem, do czego są one używane. Więcej informacji na ich temat można znaleźć na odpowiednich stronach pomocy (na przykład ? tableOutput):

Funkcja *Output(): Użyj

dataTableOutput() : Tabela danych

htmlOutput : Surowy HTML

imageOutput() : Obrazy

plotOutput() : Wykresy

tableOutput() : Tabele

textOutput : Tekst

uiCutput() : Surowy HTML

verbatimTextOutput() : Dosłowny tekst

Wreszcie każda funkcja render*() przyjmuje pojedynczy argument, wyrażenie R otoczone nawiasami klamrowymi ( {} ). Wyrażenia te mogą zawierać jeden prosty wiersz tekstu lub wiele wierszy kodu i wywołań funkcji. Poniższa tabela przedstawia listę wszystkich funkcji render*() ze wskazaniem, do czego są używane. Zgadłeś, możesz znaleźć więcej informacji na ich temat, korzystając z odpowiednich stron pomocy (na przykład? renderText):

Funkcja render*() : Użyj

renderDataTable : Tabela danych

renderImage() : Wizerunek

renderPlot() : Wykres

renderPrint() : Dowolny wydruk

renderTable(): Ramka danych, macierz lub inna struktura przypominająca tabelę

renderText() : Ciąg znaków

renderUI() : Shiny tag lub HTML

Aplikacje Shiny łączą funkcje *Input*(),*Output() i render*() , tworząc potężne aplikacje internetowe. Najprostsze aplikacje będą składać się tylko z wartości reaktywnych i obserwatorów, bez zbytniej logiki między nimi. Jednak możliwe jest również umieszczenie między nimi dowolnej liczby wyrażeń, co pozwala na bardziej złożone aplikacje. Istnieje wiele innych sposobów pracy z reaktywnością w Shiny.

Elementy konstrukcyjne do reaktywności w Shiny

Bloki konstrukcyjne reaktywności w Shiny są zbudowane wokół trzech typów funkcji: funkcji wejściowych, wyjściowych i renderujących. Funkcje wejściowe przez większość czasu kończą się łańcuchem Input (nie zawsze) i będę nazywać je funkcjami Input(). Funkcje wyjściowe zawsze kończą się łańcuchem Output i będę nazywać je funkcjami Output() . Wreszcie funkcje renderujące zaczynają się od ciągu znaków render i podobnie będę nazywać je funkcjami render*(). Funkcje Input*() są używane w obiekcie ui i generują wartości reaktywne, które są wartościami otrzymanymi w wyniku interakcji za pośrednictwem przeglądarki internetowej i są przekazywane przez parametr input w funkcji server . Funkcje render*() są używane wewnątrz funkcji server i wykorzystują wartości reaktywne do tworzenia obserwacji, które wracają do obiektu ui za pośrednictwem parametru output funkcji server. Wreszcie, funkcje *Output() są używane w obiekcie ui , aby wyświetlić zawartość tych obserwacji w przeglądarce internetowej. Wartości reaktywne są odbierane w funkcji server() za pośrednictwem parametru input , czyli listy, której elementy zawierają elementy, które są połączone z obiektem ui za pomocą ciągów znaków pełniących rolę unikalnych identyfikatorów. Parametr output w funkcji server jest również listą, ale służy do odbierania obserwacji, które zostaną przesłane do przeglądarki internetowej. Funkcje, które wiedzą, jak radzić sobie z wartościami reaktywnymi, nazywane są funkcjami reaktywnymi. Nie każda funkcja R jest funkcją reaktywną i wymagają specjalnych mechanizmów konstrukcyjnych dostarczonych przez Shiny, a jeśli spróbujesz użyć wartości reaktywnej w funkcji niereaktywnej, otrzymasz błąd (jest to łatwy błąd przy uruchomieniu Shiny). Funkcje  render*() służą do tworzenia funkcji reaktywnych. Innym sposobem jest użycie funkcji reactive(), którą wyjaśnimy w dalszej części. Funkcje reaktywne są powszechnie używane do generowania obserwacji, które mogą być używane przez inne funkcje reaktywne lub przez funkcje render*(). Jednak funkcje reaktywne mogą również powodować efekty uboczne (na przykład zapis do bazy danych). Jeśli funkcje reaktywne mają wartości return, są one zapisywane w pamięci podręcznej, dzięki czemu funkcja nie musi być ponownie wykonywana, jeśli odpowiadające jej wartości reaktywne nie uległy zmianie.

Jak jest obsługiwana reaktywność funkcjonalna w Shiny?

Reaktywność sprawia, że ​​aplikacje Shiny reagują. Wygląda na to, że aplikacja aktualizuje się natychmiast, gdy użytkownik dokona zmiany. Jednak w rzeczywistości Shiny ponownie uruchamia twoje wyrażenia R w dokładnie zaplanowany sposób co kilka mikrosekund, co stwarza iluzję responsywności. Nie musisz wiedzieć, jak zachodzi reaktywność, aby go używać, ale zrozumienie reaktywności sprawi, że będziesz lepszym programistą Shiny.

Pamiętasz, że kiedy uruchomiliśmy naszą bardzo prostą aplikację Shiny w poprzedniej sekcji, konsola R przestała być interaktywna? Cóż, działo się tak, ponieważ wykonywanie funkcji shinyApp() sprawia, że ​​R jest zajęty przez ciągłe monitorowanie i aktualizowanie wyrażeń w razie potrzeby, co tworzy wrażenia responsywne dla użytkowników. Teraz wyobraź sobie, że masz złożoną aplikację z dużą ilością interaktywności, a uruchamianie każdego wyrażenia co kilka mikrosekund całkowicie nasyciłoby twój procesor, a użytkownicy mieliby okropne wrażenia z użytkowania. Dlatego Shiny musi być wystarczająco inteligentny, aby aktualizować tylko te wyrażenia, które tego wymagają. Za każdym razem, gdy użytkownik przesyła akcję (zdarzenie), wyrażenia obsługujące takie zdarzenia zostają unieważnione, co oznacza, że ​​wymagają aktualizacji, a to zachowanie jest propagowane wśród wszystkich wyrażeń, które od nich zależą. Po upływie kilku mikrosekund R sprawdzi, które wyrażenia są oznaczone do aktualizacji i tylko je zaktualizuje. Opisany mechanizm może zmniejszyć liczbę przeliczanych wyrażeń z tysięcy do żadnego, na wypadek gdyby nie było żadnej akcji użytkownika, a najwyżej do kilku, ponieważ użytkownikowi bardzo trudno jest wiele osiągnąć w ciągu kilku mikrosekund co z kolei skutkowałoby kilkoma wymaganymi aktualizacjami, zamiast pełnej aktualizacji aplikacji za każdym razem. Ten mechanizm pozwala R na obsługę złożonych aplikacji Shiny i jest kluczem do reaktywności. Pozwala na jak najszybszą aktualizację aplikacji, dzięki czemu koordynacja wejścia / wyjścia jest niemal natychmiastowa.

Co to jest funkcjonalne programowanie reaktywne i dlaczego jest przydatne?

Zacznijmy od reaktywnej części programowania. Programowanie reaktywne to programowanie z asynchronicznymi strumieniami danych. Zaczynamy od zdefiniowania tych terminów na poziomie ogólnym. Strumień to sekwencja trwających wydarzeń uporządkowanych w czasie. W rzeczywistości prawie wszystko można traktować jako strumień, ale prostymi przykładami są odbijające się piłki, gdzie zdarzenie jest brane pod uwagę za każdym razem, gdy piłka uderza w podłogę. Może się to zdarzyć wielokrotnie, wiele razy, bez określonych wzorców, zatrzymać się na chwilę, następnie kontynuować, a następnie ponownie. Użytkownicy klikający w witrynie to także strumień, w którym każde kliknięcie jest zdarzeniem. Jak możesz sobie wyobrazić, strumienie są wszędzie wokół nas. Drugi termin, który należy zdefiniować, jest asynchroniczny, co dosłownie oznacza bez synchronizacji. Zwykle funkcje synchroniczne czekają w wierszu wywołania funkcji do zakończenia wykonywania wywoływanej funkcji, prawdopodobnie zwracając wartość. W ten sposób programowaliśmy do tej pory. Jednak funkcje asynchroniczne niekoniecznie czekają na zakończenie funkcji, które wywołują. Oznacza to, że nasze funkcje muszą reagować na to, gdy tylko nadejdzie. Jeśli połączymy te dwa terminy, zrozumiemy, że programowanie z asynchronicznymi strumieniami danych działa poprzez pisanie kodu, który jest w stanie reagować na zdarzenia w sposób ciągły i losowy. W przypadku tego rozdziału zdarzeniami tymi będą interakcje użytkownika (kliknięcia lub naciśnięcia klawiszy) z naszą aplikacją, co oznacza, że ​​nasz kod R będzie odpowiadał bezpośrednio na te kliknięcia i naciśnięcia klawiszy, gdy się one pojawią. Jeśli nadal trudno jest pojąć ten pomysł, pomyśl o tym jak o arkuszu kalkulacyjnym z formułami. Gdy zmienisz wartość, od której inne komórki zależą lub której nasłuchują (analogicznie do otrzymywania pewnych danych wejściowych od użytkownika w naszej aplikacji), wtedy inne komórki odpowiednio reagują i prezentują nowo obliczoną wartość (która będzie zmianą wyjściową, którą pokazujemy użytkownikowi ). To naprawdę takie proste. Odsłuchiwanie strumienia nazywa się subskrybowaniem. Funkcje, które definiujemy, są obserwatorami, a strumień jest obserwowalną istotą. To jest dokładnie wzorzec projektowy obserwatora. Zapoznaj się z książką Gamma, Helm, Johnson i Vlissides Design Patterns: Elements of Reusable Object-Oriented Software, autorstwa Addison-Wesley, 1994. Ponadto otrzymujesz wspaniały zestaw narzędzi, które pozwalają tworzyć, filtrować i łączyć dowolne z tych strumieni. W tym miejscu wkracza magia programowania funkcjonalnego. Programowanie funkcjonalne pozwala na kompozycję i właśnie do tego będziemy go używać, do komponowania strumieni. Strumień może służyć jako dane wejściowe do innego. Nawet wiele strumieni może być używanych jako dane wejściowe dla wielu innych. Ponadto możesz użyć dowolnego z tych surowych lub przekształconych strumieni w dowolnym miejscu kodu. To naprawdę sprawia, że ​​Shiny jest tak wspaniałym narzędziem.

Funkcjonalne programowanie reaktywne podnosi poziom abstrakcji kodu, dzięki czemu możesz skupić się na współzależności zdarzeń, które definiują logikę aplikacji, zamiast ciągłego majstrowania przy dużej liczbie szczegółów implementacji. Kod funkcjonalny reaktywny będzie również prawdopodobnie bardziej zwięzły. Korzyści są bardziej widoczne w nowoczesnych aplikacjach, które są wysoce interaktywne. W dzisiejszych czasach aplikacje mają mnóstwo zdarzeń w czasie rzeczywistym, które umożliwiają wysoce interaktywne doświadczenia, a funkcjonalne programowanie reaktywne jest do tego doskonałym narzędziem