{"id":323293,"date":"2021-05-17T21:00:50","date_gmt":"2021-05-17T21:00:50","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=323293"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=323293","title":{"rendered":"\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 Haskell \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Reflex. \u0427\u0430\u0441\u0442\u044c 3"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\">\n<p><a href=\"https:\/\/habr.com\/ru\/company\/typeable\/blog\/543998\/\">\u0427\u0430\u0441\u0442\u044c 1<\/a>.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/habr.com\/ru\/company\/typeable\/blog\/549542\/\">\u0427\u0430\u0441\u0442\u044c 2<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u0430 <code>EventWriter<\/code> \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 <a href=\"https:\/\/github.com\/ghcjs\/ghcjs-dom\">ghcjs-dom<\/a>.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/88\/ip\/nt\/88ipnt2szajdmkyui-5awvbhcrq.gif\"><\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h2 id=\"ispolzovanie-eventwriter\">\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 EventWriter<\/h2>\n<p>  <\/p>\n<p>\u0421\u0435\u0439\u0447\u0430\u0441, \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u043a\u0438\u043d\u0443\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0441 \u0431\u043e\u043b\u0435\u0435 \u0433\u043b\u0443\u0431\u043e\u043a\u0438\u0445 \u0443\u0440\u043e\u0432\u043d\u0435\u0439, \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0438\u0445 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0435\u043c\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439. \u042d\u0442\u043e \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u0443\u0434\u043e\u0431\u043d\u043e, \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e, \u043a\u043e\u0433\u0434\u0430 \u043d\u0430\u0434\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e, \u043f\u043e\u043c\u0438\u043c\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0444\u043e\u0440\u043c\u0430 \u0432\u0432\u043e\u0434\u0430 \u043c\u043e\u0436\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u043d\u0430\u0436\u0430\u0442\u0438\u044f \u043a\u043d\u043e\u043f\u043a\u0438, \u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 \u0444\u043e\u0440\u043c\u044b). \u0413\u043e\u0440\u0430\u0437\u0434\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0431\u044b\u043b\u043e \u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 &quot;\u043f\u0440\u043e\u043a\u0438\u043d\u0443\u0442\u044c&quot; \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u043d\u0430\u0432\u0435\u0440\u0445 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u043d\u0435 \u0437\u0430\u0434\u0443\u043c\u044b\u0432\u0430\u044f\u0441\u044c \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043d\u0430\u0434\u043e \u0438\u0445 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c. \u0418 \u0442\u0430\u043a\u043e\u0439 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0435\u0441\u0442\u044c \u2014 <code>EventWriter<\/code>. \u042d\u0442\u043e\u0442 \u043a\u043b\u0430\u0441\u0441 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u043d\u0430\u043f\u043e\u0434\u043e\u0431\u0438\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0439 \u043c\u043e\u043d\u0430\u0434\u044b <code>Writer<\/code>. \u041f\u0435\u0440\u0435\u043f\u0438\u0448\u0435\u043c \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c <code>EventWriter<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0441\u0430\u043c \u043a\u043b\u0430\u0441\u0441 <code>EventWriter<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">class (Monad m, Semigroup w) =&gt; EventWriter t w m | m -&gt; t w where   tellEvent :: Event t w -&gt; m ()<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0438\u043f <code>w<\/code> \u043a\u0430\u043a \u0440\u0430\u0437 \u0438 \u0435\u0441\u0442\u044c \u0442\u0438\u043f \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u044f, \u0438 \u044d\u0442\u043e\u0442 \u0442\u0438\u043f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0430 <code>Semigroup<\/code>, \u0442.\u0435. \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u044d\u0442\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043c\u043e\u0436\u043d\u043e \u043a\u043e\u043c\u0431\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u0440\u0443\u0433 \u0441 \u0434\u0440\u0443\u0433\u043e\u043c. \u0412 \u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0435\u0441\u043b\u0438 \u0434\u0432\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0437\u0430\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>tellEvent<\/code>, \u0438 \u043e\u043d\u0438 \u0432 \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043c\u043e\u043c\u0435\u043d\u0442 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442, \u0442\u043e \u043e\u043d\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u043a\u0430\u043a-\u0442\u043e \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u0435\u043d\u044b \u0432 \u043e\u0434\u043d\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0435 \u0442\u043e\u0433\u043e \u0436\u0435 \u0442\u0438\u043f\u0430, \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u043d\u0430\u0434\u044b \u0431\u044b\u043b\u043e \u043e\u0434\u043d\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0435.<\/p>\n<p>  <\/p>\n<p>\u0421\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0435\u0440, \u044f\u0432\u043b\u044f\u044e\u0449\u0438\u0439\u0441\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u043c \u044d\u0442\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u2014 <code>EventWriterT<\/code>, \u0434\u043b\u044f \u0435\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>runEventWriterT<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0439. \u041d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043e\u0436\u0438\u0434\u0430\u044e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>rootWidget<\/code>.<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">rootWidget :: MonadWidget t m =&gt; m () rootWidget =   divClass &quot;container&quot; $ mdo     elClass &quot;h2&quot; &quot;text-center mt-3&quot; $ text &quot;Todos&quot;     (_, ev) &lt;- runEventWriterT $ do       todosDyn &lt;- foldDyn appEndo mempty ev       newTodoForm       delimiter       todoListWidget todosDyn     blank<\/code><\/pre>\n<p>  <\/p>\n<p>\u041c\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u043b\u0438 \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0440\u0430\u043d\u0441\u0444\u043e\u0440\u043c\u0435\u0440\u0430 \u0438 \u0438\u0437\u0431\u0430\u0432\u0438\u043b\u0438\u0441\u044c \u043e\u0442 \u0432\u0441\u0435\u0445 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439.<\/p>\n<p>  <\/p>\n<p>\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 <code>newTodoForm<\/code> \u043d\u0435 \u0442\u0430\u043a\u0438\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0435, \u043d\u043e \u0432\u0441\u0435 \u0436\u0435, \u0441\u0442\u043e\u0438\u0442 \u0438\u0445 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c:<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">newTodoForm :: (EventWriter t (Endo Todos) m, MonadWidget t m) =&gt; m () newTodoForm = rowWrapper $ el &quot;form&quot; $ divClass &quot;input-group&quot; $ mdo   iEl &lt;- inputElement $ def     &amp; initialAttributes .~       (  &quot;type&quot; =: &quot;text&quot;       &lt;&gt; &quot;class&quot; =: &quot;form-control&quot;       &lt;&gt; &quot;placeholder&quot; =: &quot;Todo&quot; )     &amp; inputElementConfig_setValue .~ (&quot;&quot; &lt;$ btnEv)   let     addNewTodo = \\todo -&gt; Endo $ \\todos -&gt;       insert (nextKey todos) (newTodo todo) todos     newTodoDyn = addNewTodo &lt;$&gt; value iEl     btnAttr = &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;       &lt;&gt; &quot;type&quot; =: &quot;button&quot;   (btnEl, _) &lt;- divClass &quot;input-group-append&quot; $     elAttr' &quot;button&quot; btnAttr $ text &quot;Add new entry&quot;   let btnEv = domEvent Click btnEl   tellEvent $ tagPromptlyDyn newTodoDyn $ domEvent Click btnEl<\/code><\/pre>\n<p>  <\/p>\n<p>\u041c\u044b \u0432\u0438\u0434\u0438\u043c, \u0447\u0442\u043e \u043e\u0431\u043d\u043e\u0432\u0438\u043b\u0441\u044f \u0442\u0438\u043f \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043e\u043d\u0430 \u0442\u0435\u043f\u0435\u0440\u044c \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442, \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 <code>EventWriter<\/code>. \u0412 \u0442\u0435\u043b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043c\u044b \u0438\u0437\u0431\u0430\u0432\u0438\u043b\u0438\u0441\u044c \u043e\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438 \u0442\u0435\u043f\u0435\u0440\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>tellEvent<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f <code>todoListWidget<\/code> \u0441\u0438\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u043b\u0430\u0441\u044c.<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">todoListWidget   :: (EventWriter t (Endo Todos) m, MonadWidget t m)   =&gt; Dynamic t Todos -&gt; m () todoListWidget todosDyn = rowWrapper $   void $ listWithKey (M.fromAscList . IM.toAscList &lt;$&gt; todosDyn) todoWidget<\/code><\/pre>\n<p>  <\/p>\n<p>\u041d\u0430\u0441 \u0442\u0435\u043f\u0435\u0440\u044c \u0432\u043e\u043e\u0431\u0449\u0435 \u043d\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0435 \u0441\u043e\u0431\u044b\u0442\u0438\u0435, \u0438, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043e\u0442\u043f\u0430\u043b\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0432 \u0438\u0437\u0432\u043b\u0435\u0447\u0435\u043d\u0438\u0438 <code>Event<\/code> \u0438\u0437 <code>Dynamic<\/code>.<\/p>\n<p>  <\/p>\n<p>\u0412 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>todoWidget<\/code> \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0438 \u0437\u0430\u043c\u0435\u0442\u043d\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f. \u0411\u043e\u043b\u044c\u0448\u0435 \u043d\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u044b\u043c \u0442\u0438\u043f\u043e\u043c \u2014 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c <code>Event t (Event t TodoEvent)<\/code>. \u041e\u0442\u043b\u0438\u0447\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>dyn_<\/code> \u043e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>dyn<\/code>, \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043e\u043d\u0430 \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435.<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">todoWidget   :: (EventWriter t (Endo Todos) m, MonadWidget t m)   =&gt; Int -&gt; Dynamic t Todo -&gt; m () todoWidget ix todoDyn' = do   todoDyn &lt;- holdUniqDyn todoDyn'   dyn_ $ ffor todoDyn $ \\td@Todo{..} -&gt; case todoState of     TodoDone         -&gt; todoDone ix todoText     TodoActive False -&gt; todoActive ix todoText     TodoActive True  -&gt; todoEditable ix todoText<\/code><\/pre>\n<p>  <\/p>\n<p>\u0415\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u0445 <code>todoDone<\/code>, <code>todoActive<\/code> \u0438 <code>todoEditable<\/code> \u044d\u0442\u043e \u043d\u043e\u0432\u044b\u0439 \u0442\u0438\u043f \u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u0441\u043e\u0431\u044b\u0442\u0438\u044f \u0432\u043c\u0435\u0441\u0442\u043e \u0435\u0433\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430.<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">todoActive   :: (EventWriter t (Endo Todos) m, MonadWidget t m)   =&gt; Int -&gt; Text -&gt; m () todoActive ix todoText = divClass &quot;d-flex border-bottom&quot; $ do   divClass &quot;p-2 flex-grow-1 my-auto&quot; $     text todoText   divClass &quot;p-2 btn-group&quot; $ do     (doneEl, _) &lt;- elAttr' &quot;button&quot;       (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;       &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Done&quot;     (editEl, _) &lt;- elAttr' &quot;button&quot;       (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;       &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Edit&quot;     (delEl, _) &lt;- elAttr' &quot;button&quot;       (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;       &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Drop&quot;     tellEvent $ Endo &lt;$&gt; leftmost       [ update (Just . toggleTodo) ix &lt;$ domEvent Click doneEl       , update (Just . startEdit) ix  &lt;$ domEvent Click editEl       , delete ix &lt;$ domEvent Click delEl       ]  todoDone   :: (EventWriter t (Endo Todos) m, MonadWidget t m)   =&gt; Int -&gt; Text -&gt; m () todoDone ix todoText = divClass &quot;d-flex border-bottom&quot; $ do   divClass &quot;p-2 flex-grow-1 my-auto&quot; $     el &quot;del&quot; $ text todoText   divClass &quot;p-2 btn-group&quot; $ do     (doneEl, _) &lt;- elAttr' &quot;button&quot;       (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;       &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Undo&quot;     (delEl, _) &lt;- elAttr' &quot;button&quot;       (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;       &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Drop&quot;     tellEvent $ Endo &lt;$&gt; leftmost       [ update (Just . toggleTodo) ix &lt;$ domEvent Click doneEl       , delete ix &lt;$ domEvent Click delEl       ]  todoEditable   :: (EventWriter t (Endo Todos) m, MonadWidget t m)   =&gt; Int -&gt; Text -&gt; m () todoEditable ix todoText = divClass &quot;d-flex border-bottom&quot; $ do   updTodoDyn &lt;- divClass &quot;p-2 flex-grow-1 my-auto&quot; $     editTodoForm todoText   divClass &quot;p-2 btn-group&quot; $ do     (doneEl, _) &lt;- elAttr' &quot;button&quot;       (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;       &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Finish edit&quot;     let updTodos = \\todo -&gt; Endo $ update (Just . finishEdit todo) ix     tellEvent $       tagPromptlyDyn (updTodos &lt;$&gt; updTodoDyn) (domEvent Click doneEl)<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u0430 <code>EventWriter<\/code> \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u043b\u043e \u043a\u043e\u0434 \u0438 \u0441\u0434\u0435\u043b\u0430\u043b\u043e \u0435\u0433\u043e \u0431\u043e\u043b\u0435\u0435 \u0447\u0438\u0442\u0430\u0435\u043c\u044b\u043c.<\/p>\n<p>  <\/p>\n<h1 id=\"ghcjs-dom\">ghcjs-dom<\/h1>\n<p>  <\/p>\n<p><code>reflex<\/code> \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u043c \u0442\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u043e\u0432\u0430\u0442\u044c <code>DOM<\/code>, \u043d\u043e \u0437\u0430\u0447\u0430\u0441\u0442\u0443\u044e \u043e\u0442 JS-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0441\u043b\u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442 \u043f\u043e \u043d\u0430\u0436\u0430\u0442\u0438\u044e \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443, \u0442\u043e <code>reflex<\/code> \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0443\u0436\u043d\u044b\u0445 \u043d\u0430\u043c \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0441\u0440\u0435\u0434\u0441\u0442\u0432. \u041d\u0430 \u043f\u043e\u043c\u043e\u0449\u044c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 <code>ghcjs-dom<\/code>. \u041f\u043e \u0441\u0443\u0442\u0438, \u044d\u0442\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f <code>JS API<\/code> \u043d\u0430 Haskell. \u0412 \u043d\u0435\u0439 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432\u0441\u0435 \u0442\u0435 \u0436\u0435 \u0441\u0430\u043c\u044b\u0435 \u0442\u0438\u043f\u044b \u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0435\u0441\u0442\u044c \u0432 JS.<\/p>\n<p>  <\/p>\n<p>\u041d\u0430 \u0447\u0438\u0441\u0442\u043e\u043c JS, \u0431\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a, \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<p>  <\/p>\n<pre><code class=\"javascript\">function toClipboard(txt){   var inpEl = document.createElement(&quot;textarea&quot;);   document.body.appendChild(inpEl);   inpEl.value = txt   inpEl.focus();   inpEl.select();   document.execCommand('copy');   document.body.removeChild(inpEl); }<\/code><\/pre>\n<p>  <\/p>\n<p>\u0412 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u043c\u044b \u0432\u0435\u0448\u0430\u0435\u043c \u044d\u0442\u043e\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043d\u0430 \u043a\u043d\u043e\u043f\u043a\u0443.<br \/>  \u041a\u0430\u043a \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043d\u0430 Haskell? \u0412 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043d\u043e\u0432\u044b\u0439 \u043c\u043e\u0434\u0443\u043b\u044c <code>GHCJS<\/code> \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 <code>ghcjs<\/code> \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e.<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE MonoLocalBinds #-} module GHCJS where  import Control.Monad import Data.Functor (($&gt;)) 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 =&gt; Text -&gt; m () toClipboard txt = do   doc &lt;- currentDocumentUnchecked   body &lt;- getBodyUnchecked doc   inpEl &lt;- uncheckedCastTo HTMLInputElement &lt;$&gt; createElement doc     (&quot;textarea&quot; :: Text)   void $ appendChild body inpEl   HE.focus inpEl   HIE.setValue inpEl txt   HIE.select inpEl   void $ execCommand doc (&quot;copy&quot; :: Text) False (Nothing :: Maybe Text)   void $ removeChild body inpEl<\/code><\/pre>\n<p>  <\/p>\n<p>\u041f\u043e\u0447\u0442\u0438 \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u0438\u0437 haskell \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>toClipboard<\/code> \u0435\u0441\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0438\u0437 JS \u0444\u0443\u043d\u043a\u0446\u0438\u0438. \u0421\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u0437\u0434\u0435\u0441\u044c \u043d\u0435\u0442 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 <code>MonadWidget<\/code>, \u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f <code>MonadJSM<\/code> \u2014 \u044d\u0442\u043e \u0442\u0430 \u043c\u043e\u043d\u0430\u0434\u0430, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u044f\u0442\u0441\u044f \u0432\u0441\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>ghcjs-dom<\/code>. \u041a\u043b\u0430\u0441\u0441 <code>MonadWidget<\/code> \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043a\u043b\u0430\u0441\u0441 <code>MonadJSM<\/code>. \u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u043a \u0441\u043e\u0431\u044b\u0442\u0438\u044e:<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">copyByEvent :: MonadWidget t m =&gt; Text -&gt; Event t () -&gt; m () copyByEvent txt ev =   void $ performEvent $ ev $&gt; toClipboard txt<\/code><\/pre>\n<p>  <\/p>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0432\u0438\u0434\u0438\u043c \u043d\u043e\u0432\u0443\u044e \u0434\u043b\u044f \u043d\u0430\u0441 \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>performEvent<\/code>, \u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043d\u0435\u0435 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u043a \u0441\u043e\u0431\u044b\u0442\u0438\u044e. \u041e\u043d\u0430 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0430 <code>PerformEvent<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">class (Reflex t, Monad (Performable m), Monad m) =&gt; PerformEvent t m | m -&gt; t where   type Performable m :: * -&gt; *   performEvent :: Event t (Performable m a) -&gt; m (Event t a)   performEvent_ :: Event t (Performable m ()) -&gt; m ()<\/code><\/pre>\n<p>  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0438\u0437\u043c\u0435\u043d\u0438\u043c \u0432\u0438\u0434\u0436\u0435\u0442 \u043d\u0435\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044f, \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435 \u0437\u0430\u0431\u044b\u0432 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442 <code>import GHCJS<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"haskell\">todoActive   :: (EventWriter t TodoEvent m, MonadWidget t m) =&gt; Int -&gt; Todo -&gt; m () todoActive ix Todo{..} =   divClass &quot;d-flex border-bottom&quot; $ do     divClass &quot;p-2 flex-grow-1 my-auto&quot; $       text todoText     divClass &quot;p-2 btn-group&quot; $ do       (copyEl, _) &lt;- elAttr' &quot;button&quot;         (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;         &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Copy&quot;       (doneEl, _) &lt;- elAttr' &quot;button&quot;         (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;         &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Done&quot;       (editEl, _) &lt;- elAttr' &quot;button&quot;         (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;         &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Edit&quot;       (delEl, _) &lt;- elAttr' &quot;button&quot;         (  &quot;class&quot; =: &quot;btn btn-outline-secondary&quot;         &lt;&gt; &quot;type&quot; =: &quot;button&quot; ) $ text &quot;Drop&quot;       copyByEvent todoText $ domEvent Click copyEl       tellEvent $ leftmost         [ ToggleTodo ix &lt;$ domEvent Click doneEl         , StartEditTodo ix &lt;$ domEvent Click editEl         , DeleteTodo ix &lt;$ domEvent Click delEl         ]<\/code><\/pre>\n<p>  <\/p>\n<p>\u0411\u044b\u043b\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u043d\u043e\u0432\u0430\u044f \u043a\u043d\u043e\u043f\u043a\u0430 <code>Copy<\/code> \u0438 \u0432\u044b\u0437\u043e\u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>copyByEvent<\/code>. \u042d\u0442\u0438 \u0436\u0435 \u0441\u0430\u043c\u044b\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0434\u0435\u043b\u0430\u0442\u044c \u0441 \u0432\u0438\u0434\u0436\u0435\u0442\u0430\u043c\u0438 \u0434\u043b\u044f \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0439 \u0437\u0430\u0434\u0430\u043d\u0438\u044f.<\/p>\n<p>  <\/p>\n<p>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442, \u043a\u0430\u043a \u0432\u0441\u0435\u0433\u0434\u0430, \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c <a href=\"https:\/\/github.com\/typeable\/blog-posts-ru\/tree\/1156cc99d55c1a6e6b45a82675a5fd3a926f8473\/reflex-todo\">\u0432 \u043d\u0430\u0448\u0435\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 JSFFI (JS Foreign Function Interface).<\/p>\n<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/company\/typeable\/blog\/557852\/\"> https:\/\/habr.com\/ru\/company\/typeable\/blog\/557852\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\">\n<p><a href=\"https:\/\/habr.com\/ru\/company\/typeable\/blog\/543998\/\">\u0427\u0430\u0441\u0442\u044c 1<\/a>.<\/p>\n<p>  <\/p>\n<p><a href=\"https:\/\/habr.com\/ru\/company\/typeable\/blog\/549542\/\">\u0427\u0430\u0441\u0442\u044c 2<\/a>.<\/p>\n<p>  <\/p>\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u0430 <code>EventWriter<\/code> \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 <a href=\"https:\/\/github.com\/ghcjs\/ghcjs-dom\">ghcjs-dom<\/a>.<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/88\/ip\/nt\/88ipnt2szajdmkyui-5awvbhcrq.gif\"><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-323293","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/323293","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=323293"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/323293\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=323293"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=323293"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=323293"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}