Reflection을 사용하여 Haskell 응용 프로그램을 만듭니다.제3부분
35894 단어 tutorialprogramminghaskellfunctional
안녕하세요!본고에서 우리는
EventWriter
류와 ghcjs-dom라이브러리를 어떻게 사용하는지 토론할 것이다.EventWriter 사용
현재 실행 중, 플러그인 단계에서 이벤트를 보내기 위해, 우리는 이벤트를 반환값으로 전달합니다.이것은 항상 편리하지 않습니다. 특히 이벤트 이외의 내용을 되돌려야 할 때 (예를 들어 폼을 입력하면 클릭한 이벤트와 폼의 데이터를 동시에 되돌릴 수 있습니다.)이벤트를 자동으로 맨 위에 보낼 수 있는 메커니즘을 사용하면 이벤트로 되돌아오는 번거로움을 줄일 수 있습니다.이런 메커니즘은 확실히 존재한다.이 클래스는 표준
EventWriter
monad와 비슷한 이벤트를 쓸 수 있습니다.애플리케이션을 다시 작성하려면 Writer
을 사용하십시오.우선
EventWriter
류를 고려해 봅시다.class (Monad m, Semigroup w) => EventWriter t w m | m -> t w where
tellEvent :: Event t w -> m ()
EventWriter
형이 바로 우리가 활동하는 유형이다.이 유형은 w
류의 실례로 이 유형의 값을 조합할 수 있다.만약 Semigroup
을 사용하여 두 개의 다른 이벤트를 기록하고 어느 시점에서 동시에 실행한다면, 모든monad가 실행하면 하나의 이벤트를 초래할 수 있도록 어떤 방식으로 이 이벤트를 같은 종류의 이벤트로 조합해야 한다.transformer가 이 종류를 대표하는 실례가 하나 있는데 그것이 바로
tellEvent
이다.함수 EventWriterT
을 사용하여 실행할 수 있습니다.그 후에 우리는 함수를 바꾸기 시작했다.기능
runEventWriterT
은 가장 큰 변화를 겪을 것입니다.rootWidget :: MonadWidget t m => m ()
rootWidget =
divClass "container" $ mdo
elClass "h2" "text-center mt-3" $ text "Todos"
(_, ev) <- runEventWriterT $ do
todosDyn <- foldDyn appEndo mempty ev
newTodoForm
delimiter
todoListWidget todosDyn
blank
transformer runner 호출을 추가하고 모든 반환 이벤트를 삭제했습니다.비록
rootWidget
의 변화는 그다지 현저하지 않지만, 그것들은 여전히 언급할 가치가 있다.newTodoForm :: (EventWriter t (Endo Todos) m, MonadWidget t m) => m ()
newTodoForm = rowWrapper $ el "form" $ divClass "input-group" $ mdo
iEl <- inputElement $ def
& initialAttributes .~
( "type" =: "text"
<> "class" =: "form-control"
<> "placeholder" =: "Todo" )
& inputElementConfig_setValue .~ ("" <$ btnEv)
let
addNewTodo = \todo -> Endo $ \todos ->
insert (nextKey todos) (newTodo todo) todos
newTodoDyn = addNewTodo <$> value iEl
btnAttr = "class" =: "btn btn-outline-secondary"
<> "type" =: "button"
(btnEl, _) <- divClass "input-group-append" $
elAttr' "button" btnAttr $ text "Add new entry"
let btnEv = domEvent Click btnEl
tellEvent $ tagPromptlyDyn newTodoDyn $ domEvent Click btnEl
보시다시피 함수 형식이 업데이트되어 아무것도 돌아오지 않습니다.필요한 구속 newTodoForm
도 추가했습니다.이에 따라 우리는 함수체에서 반환값을 삭제하고 현재 EventWriter
함수를 사용하고 있다.함수
tellEvent
이 훨씬 간단해졌다.todoListWidget
:: (EventWriter t (Endo Todos) m, MonadWidget t m)
=> Dynamic t Todos -> m ()
todoListWidget todosDyn = rowWrapper $
void $ listWithKey (M.fromAscList . IM.toAscList <$> todosDyn) todoWidget
현재 우리는 되돌아오는 사건에 전혀 관심이 없기 때문에 todoListWidget
에서 Event
을 추출할 필요가 없다.Dynamic
의 기능도 눈에 띄게 달라졌다.현재 우리는 되돌아오는 형식을 사용하고 todoWidget
을 변환할 필요가 없다.함수 Event t (Event t TodoEvent)
과 dyn_
의 다른 점은 전자가 되돌아오는 값을 무시하는 데 있다.todoWidget
:: (EventWriter t (Endo Todos) m, MonadWidget t m)
=> Int -> Dynamic t Todo -> m ()
todoWidget ix todoDyn' = do
todoDyn <- holdUniqDyn todoDyn'
dyn_ $ ffor todoDyn $ \td@Todo{..} -> case todoState of
TodoDone -> todoDone ix todoText
TodoActive False -> todoActive ix todoText
TodoActive True -> todoEditable ix todoText
함수 dyn
, todoDone
, todoActive
의 유일한 변화는 되돌아오는 것이 아니라 새로운 형식과 이벤트 쓰기입니다.todoActive
:: (EventWriter t (Endo Todos) m, MonadWidget t m)
=> Int -> Text -> m ()
todoActive ix todoText = divClass "d-flex border-bottom" $ do
divClass "p-2 flex-grow-1 my-auto" $
text todoText
divClass "p-2 btn-group" $ do
(doneEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Done"
(editEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Edit"
(delEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Drop"
tellEvent $ Endo <$> leftmost
[ update (Just . toggleTodo) ix <$ domEvent Click doneEl
, update (Just . startEdit) ix <$ domEvent Click editEl
, delete ix <$ domEvent Click delEl
]
todoDone
:: (EventWriter t (Endo Todos) m, MonadWidget t m)
=> Int -> Text -> m ()
todoDone ix todoText = divClass "d-flex border-bottom" $ do
divClass "p-2 flex-grow-1 my-auto" $
el "del" $ text todoText
divClass "p-2 btn-group" $ do
(doneEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Undo"
(delEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Drop"
tellEvent $ Endo <$> leftmost
[ update (Just . toggleTodo) ix <$ domEvent Click doneEl
, delete ix <$ domEvent Click delEl
]
todoEditable
:: (EventWriter t (Endo Todos) m, MonadWidget t m)
=> Int -> Text -> m ()
todoEditable ix todoText = divClass "d-flex border-bottom" $ do
updTodoDyn <- divClass "p-2 flex-grow-1 my-auto" $
editTodoForm todoText
divClass "p-2 btn-group" $ do
(doneEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Finish edit"
let updTodos = \todo -> Endo $ update (Just . finishEdit todo) ix
tellEvent $
tagPromptlyDyn (updTodos <$> updTodoDyn) (domEvent Click doneEl)
todoEditable
류의 사용은 코드를 더욱 간단하고 읽기 쉽게 한다.ghcjs domEventWriter
은 reflex
만 수정할 수 있지만 JS 응용 프로그램은 보통 더 많은 작업을 해야 한다.예를 들어 단추를 누르면 텍스트를 복사할 수 있습니다. DOM
은 우리에게 필요한 도구를 제공할 수 없습니다.reflex
도서관이 구조하러 온다.본질적으로 이것은 Haskell의 ghcjs-dom
이 실현한 것이다.여기서 JS와 같은 유형과 함수를 찾을 수 있습니다.
일반 JS에서 타사 라이브러리를 사용하지 않는 경우 텍스트 복사 기능은 다음과 같습니다.
function toClipboard(txt){
var inpEl = document.createElement("textarea");
document.body.appendChild(inpEl);
inpEl.value = txt
inpEl.focus();
inpEl.select();
document.execCommand('copy');
document.body.removeChild(inpEl);
}
일반적인 방법은 이 이벤트 처리 프로그램을 단추에 추가하는 것입니다.
하스켈은 어떻게 될까?우선, 우리는 새로운 JS API
모듈을 창설하여 GHCJS
과 함께 작업하고 관련 기능을 정의한다.
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MonoLocalBinds #-}
module GHCJS where
import Control.Monad
import Data.Functor (($>))
import Data.Text (Text)
import GHCJS.DOM
import GHCJS.DOM.Document
(createElement, execCommand, getBodyUnchecked)
import GHCJS.DOM.Element as Element hiding (scroll)
import GHCJS.DOM.HTMLElement as HE (focus)
import GHCJS.DOM.HTMLInputElement as HIE (select, setValue)
import GHCJS.DOM.Node (appendChild, removeChild)
import GHCJS.DOM.Types hiding (Event, Text)
import Reflex.Dom as R
toClipboard :: MonadJSM m => Text -> m ()
toClipboard txt = do
doc <- currentDocumentUnchecked
body <- getBodyUnchecked doc
inpEl <- uncheckedCastTo HTMLInputElement <$> createElement doc
("textarea" :: Text)
void $ appendChild body inpEl
HE.focus inpEl
HIE.setValue inpEl txt
HIE.select inpEl
void $ execCommand doc ("copy" :: Text) False (Nothing :: Maybe Text)
void $ removeChild body inpEl
haskell 함수 ghcjs
의 거의 모든 줄은 JS 함수에 일치하는 줄이 있습니다.우리가 여기에 익숙하지 않은 toClipboard
류는 언급해야 한다.반대로 우리는 MonadWidget
을 사용하는데 이것은 MonadJSM
을 사용하여 모든 업무를 수행하는 리스트이다.ghcjs-dom
유형 계승 MonadWidget
.프로세서가 이벤트에 어떻게 연결되는지 보여 줍니다.
copyByEvent :: MonadWidget t m => Text -> Event t () -> m ()
copyByEvent txt ev =
void $ performEvent $ ev $> toClipboard txt
처리 프로그램을 이벤트에 연결하는 데 사용되는 새로운 함수 MonadJSM
을 보았습니다.이 함수는 performEvent
클래스의 메서드입니다.
class (Reflex t, Monad (Performable m), Monad m) => PerformEvent t m | m -> t where
type Performable m :: * -> *
performEvent :: Event t (Performable m a) -> m (Event t a)
performEvent_ :: Event t (Performable m ()) -> m ()
이제 PerformEvent
이 추가되었는지 확인한 후 중단된 작업 위젯을 변경합니다.
todoActive
:: (EventWriter t TodoEvent m, MonadWidget t m) => Int -> Todo -> m ()
todoActive ix Todo{..} =
divClass "d-flex border-bottom" $ do
divClass "p-2 flex-grow-1 my-auto" $
text todoText
divClass "p-2 btn-group" $ do
(copyEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Copy"
(doneEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Done"
(editEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Edit"
(delEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Drop"
copyByEvent todoText $ domEvent Click copyEl
tellEvent $ leftmost
[ ToggleTodo ix <$ domEvent Click doneEl
, StartEditTodo ix <$ domEvent Click editEl
, DeleteTodo ix <$ domEvent Click delEl
]
우리는 새로운 단추 import GHCJS
과 특정한 함수 호출 Copy
을 추가했다.다른 작업 상태에 사용되는 작은 위젯도 이를 할 수 있다.
여느 때와 마찬가지로 우리가 얻은 결과는 in our repository을 찾을 수 있다.
다음 글에서는 JSFFI(JS 외부 함수 인터페이스)를 어떻게 사용하는지 토론할 것이다.
Reference
이 문제에 관하여(Reflection을 사용하여 Haskell 응용 프로그램을 만듭니다.제3부분), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/typeable/creating-a-haskell-application-using-reflex-part-3-47d6
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
function toClipboard(txt){
var inpEl = document.createElement("textarea");
document.body.appendChild(inpEl);
inpEl.value = txt
inpEl.focus();
inpEl.select();
document.execCommand('copy');
document.body.removeChild(inpEl);
}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MonoLocalBinds #-}
module GHCJS where
import Control.Monad
import Data.Functor (($>))
import Data.Text (Text)
import GHCJS.DOM
import GHCJS.DOM.Document
(createElement, execCommand, getBodyUnchecked)
import GHCJS.DOM.Element as Element hiding (scroll)
import GHCJS.DOM.HTMLElement as HE (focus)
import GHCJS.DOM.HTMLInputElement as HIE (select, setValue)
import GHCJS.DOM.Node (appendChild, removeChild)
import GHCJS.DOM.Types hiding (Event, Text)
import Reflex.Dom as R
toClipboard :: MonadJSM m => Text -> m ()
toClipboard txt = do
doc <- currentDocumentUnchecked
body <- getBodyUnchecked doc
inpEl <- uncheckedCastTo HTMLInputElement <$> createElement doc
("textarea" :: Text)
void $ appendChild body inpEl
HE.focus inpEl
HIE.setValue inpEl txt
HIE.select inpEl
void $ execCommand doc ("copy" :: Text) False (Nothing :: Maybe Text)
void $ removeChild body inpEl
copyByEvent :: MonadWidget t m => Text -> Event t () -> m ()
copyByEvent txt ev =
void $ performEvent $ ev $> toClipboard txt
class (Reflex t, Monad (Performable m), Monad m) => PerformEvent t m | m -> t where
type Performable m :: * -> *
performEvent :: Event t (Performable m a) -> m (Event t a)
performEvent_ :: Event t (Performable m ()) -> m ()
todoActive
:: (EventWriter t TodoEvent m, MonadWidget t m) => Int -> Todo -> m ()
todoActive ix Todo{..} =
divClass "d-flex border-bottom" $ do
divClass "p-2 flex-grow-1 my-auto" $
text todoText
divClass "p-2 btn-group" $ do
(copyEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Copy"
(doneEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Done"
(editEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Edit"
(delEl, _) <- elAttr' "button"
( "class" =: "btn btn-outline-secondary"
<> "type" =: "button" ) $ text "Drop"
copyByEvent todoText $ domEvent Click copyEl
tellEvent $ leftmost
[ ToggleTodo ix <$ domEvent Click doneEl
, StartEditTodo ix <$ domEvent Click editEl
, DeleteTodo ix <$ domEvent Click delEl
]
Reference
이 문제에 관하여(Reflection을 사용하여 Haskell 응용 프로그램을 만듭니다.제3부분), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/typeable/creating-a-haskell-application-using-reflex-part-3-47d6텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)