(PureScript)如何在Eff以外的一元上下文中运行DOM事件侦听器回调?

(PureScript)如何在Eff以外的一元上下文中运行DOM事件侦听器回调?,dom,functional-programming,dom-events,purescript,Dom,Functional Programming,Dom Events,Purescript,我正在使用PureScript制作一个画布游戏,我想知道处理事件侦听器的最佳方法是什么,特别是在自定义monad堆栈中运行回调。这是我的游戏堆栈 type BaseEffect e = Eff (canvas :: CANVAS, dom :: DOM, console :: CONSOLE | e) type GameState = { canvas :: CanvasElement, context :: Context2D, angle :: Number } type GameEffec

我正在使用PureScript制作一个画布游戏,我想知道处理事件侦听器的最佳方法是什么,特别是在自定义monad堆栈中运行回调。这是我的游戏堆栈

type BaseEffect e = Eff (canvas :: CANVAS, dom :: DOM, console :: CONSOLE | e)
type GameState = { canvas :: CanvasElement, context :: Context2D, angle :: Number }
type GameEffect e a = StateT GameState (BaseEffect e) a
我想做的是在游戏状态下,当按下任何键时改变“角度”属性(只是为了开发目的,这样我可以调整图形)。这是我的回调函数

changeState :: forall e. Event -> GameEffect e Unit
changeState a = do
  modify \s -> s { angle = s.angle + 1.0 }
  liftEff $ log "keypress"
  pure unit
然而 及 看起来它们只能与Eff一起使用,所以下面不会进行类型检查

addEventListener
  (EventType "keypress")
  (eventListener changeState)
  false
  ((elementToEventTarget <<< htmlElementToElement) bodyHtmlElement)
处理monad堆栈中正在运行的回调的最佳方法是什么?

我将使用这种方法。这意味着将
BaseEffect
更改为
Aff
,但任何
Eff
都可以做到
Aff
也可以做到:

import Prelude

import Control.Coroutine as CR
import Control.Coroutine.Aff as CRA
import Control.Monad.Aff (Aff)
import Control.Monad.Aff.AVar (AVAR)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Rec.Class (forever)
import Control.Monad.State (StateT, lift, modify)
import Data.Either (Either(..))

import DOM (DOM)
import DOM.Event.EventTarget (addEventListener, eventListener)
import DOM.Event.Types (Event, EventTarget, EventType(..))
import DOM.HTML.Types (HTMLElement, htmlElementToElement)
import DOM.Node.Types (elementToEventTarget)

import Graphics.Canvas (CANVAS, CanvasElement, Context2D)

type BaseEffect e = Aff (canvas :: CANVAS, dom :: DOM, console :: CONSOLE, avar :: AVAR | e)
type GameState = { canvas :: CanvasElement, context :: Context2D, angle :: Number }
type GameEffect e = StateT GameState (BaseEffect e)

changeState :: forall e. Event -> GameEffect e Unit
changeState a = do
  modify \s -> s { angle = s.angle + 1.0 }
  liftEff $ log "keypress"
  pure unit

eventProducer :: forall e. EventType -> EventTarget -> CR.Producer Event (GameEffect e) Unit
eventProducer eventType target =
  CRA.produce' \emit ->
    addEventListener eventType (eventListener (emit <<< Left)) false target

setupListener :: forall e. HTMLElement -> GameEffect e Unit
setupListener bodyHtmlElement = CR.runProcess $ consumer `CR.pullFrom` producer
  where
  producer :: CR.Producer Event (GameEffect e) Unit
  producer =
    eventProducer
      (EventType "keypress")
      ((elementToEventTarget <<< htmlElementToElement) bodyHtmlElement)
  consumer :: CR.Consumer Event (GameEffect e) Unit
  consumer = forever $ lift <<< changeState =<< CR.await
导入前奏曲 导入控制。作为CR的协同程序 导入控制.Coroutine.Aff作为CRA 进口控制单体Aff(Aff) 进口管制.Monad.Aff.AVar(AVar) 导入控制.Monad.Eff.Class(liftEff) 导入Control.Monad.Eff.Console(控制台,日志) 导入控制.Monad.Rec.Class(永远) 导入控件.Monad.State(StateT、lift、modify) 导入数据。或者(或者(…) 导入DOM(DOM) 导入DOM.Event.EventTarget(addEventListener、eventListener) 导入DOM.Event.Types(事件、事件目标、事件类型(..) 导入DOM.HTML.Types(HTMLElement、htmlElementToElement) 导入DOM.Node.Types(elementToEventTarget) 导入Graphics.Canvas(Canvas、CanvasElement、Context2D) 键入BaseEffect e=Aff(canvas::canvas,dom::dom,console::console,avar::avar | e) 类型GameState={canvas::CanvasElement,context::Context2D,angle::Number} 类型GameEffect e=StateT GameState(BaseEffect e) changeState::forall e。事件->游戏效果单位 变更状态a=do 修改\s->s{angle=s.angle+1.0} liftEff$log“按键” 纯单位 eventProducer::forall e。事件类型->事件目标->CR.制作人事件(游戏效果e)单元 eventProducer事件类型目标= CRA.PRODUCT'\emit->
addEventListener事件类型(eventListener(emit)我能够让它工作,但只有一次按键,按键似乎会阻止图形渲染。在第一次按键后,消费者关闭并进行图形渲染。我想我需要为Gameffect(用新类型包装)创建一个
并行
实例因此,我可以使用
CR.runProcess$connect producer consumer
。这听起来像是我在正确的轨道上吗?哎呀,
consumer
应该这样定义:
ever$lift阻塞解决起来有点棘手,因为简单的答案是,但是没有分叉
StateT
的实例。你可以bably为
GameEffect
想出了一个
newtype
的东西。我有点惊讶它会阻止渲染,因为这意味着在渲染循环中调用
setupListener
?看起来你应该可以在设置结束时只做一次。基本上我对这一点没有一个好的答案,抱歉!谢谢由于得到了很多帮助,我能够让它工作。我最终将侦听器放在设置的末尾(因此它不会阻止任何东西),并将按键消费者和图形渲染器放在永久循环中。
import Prelude

import Control.Coroutine as CR
import Control.Coroutine.Aff as CRA
import Control.Monad.Aff (Aff)
import Control.Monad.Aff.AVar (AVAR)
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Console (CONSOLE, log)
import Control.Monad.Rec.Class (forever)
import Control.Monad.State (StateT, lift, modify)
import Data.Either (Either(..))

import DOM (DOM)
import DOM.Event.EventTarget (addEventListener, eventListener)
import DOM.Event.Types (Event, EventTarget, EventType(..))
import DOM.HTML.Types (HTMLElement, htmlElementToElement)
import DOM.Node.Types (elementToEventTarget)

import Graphics.Canvas (CANVAS, CanvasElement, Context2D)

type BaseEffect e = Aff (canvas :: CANVAS, dom :: DOM, console :: CONSOLE, avar :: AVAR | e)
type GameState = { canvas :: CanvasElement, context :: Context2D, angle :: Number }
type GameEffect e = StateT GameState (BaseEffect e)

changeState :: forall e. Event -> GameEffect e Unit
changeState a = do
  modify \s -> s { angle = s.angle + 1.0 }
  liftEff $ log "keypress"
  pure unit

eventProducer :: forall e. EventType -> EventTarget -> CR.Producer Event (GameEffect e) Unit
eventProducer eventType target =
  CRA.produce' \emit ->
    addEventListener eventType (eventListener (emit <<< Left)) false target

setupListener :: forall e. HTMLElement -> GameEffect e Unit
setupListener bodyHtmlElement = CR.runProcess $ consumer `CR.pullFrom` producer
  where
  producer :: CR.Producer Event (GameEffect e) Unit
  producer =
    eventProducer
      (EventType "keypress")
      ((elementToEventTarget <<< htmlElementToElement) bodyHtmlElement)
  consumer :: CR.Consumer Event (GameEffect e) Unit
  consumer = forever $ lift <<< changeState =<< CR.await