与CMD结合
我有一个函数
convertMsg : Msg1 -> List Msg2
,其中msg1
和msg2
是某些消息类型。我想将其转变为一个函数:
convertCmd : Cmd Msg1 -> Cmd Msg2
对于批处理中的每条消息,它将用一些消息替换为1个或超过1个。
作为Hastkell程序员的心脏,我立即达到了一个单调绑定((
中and 中的),具有类型的函数:)(&gt(&gt) ;> =)
在haskell和
bind : (a -> Cmd b) -> Cmd a -> Cmd b
我可以轻松地将我的convertmsg
更改为以下内容:
convertMsg : msg1 -> Cmd Msg2
在这一点上,它将非常适合绑定
。
但是在 platform.cmd ,在那里,我无法找到这样的功能。有一个相似的地图
,但是convertmsg
不能真正是convertmsg:msg1-> MSG2
,因为它并不总是会完全回馈一条消息。
有没有办法实现这一目标? cmd
类型是否会阻止这种事情?
I have a function
convertMsg : Msg1 -> List Msg2
where Msg1
and Msg2
are certain message types. And I would like to turn this into a function:
convertCmd : Cmd Msg1 -> Cmd Msg2
Which would for every message in the batch replace it with some messages possibly none or more than 1.
As a Haskell programmer at heart I immediately reach for a monadic bind ((>>=)
in Haskell and andThen
in the Elm parlance), a function with the type:
bind : (a -> Cmd b) -> Cmd a -> Cmd b
I can easily change my convertMsg
to be the following:
convertMsg : msg1 -> Cmd Msg2
At which point it would be just perfect for the bind
.
But looking in Platform.Cmd, there isn't such a function I can find. There's a map
which is similar, but convertMsg
can't really be convertMsg : Msg1 -> Msg2
since it doesn't always give back exactly one message.
Is there a way to achieve this? Is there some limitation to the Cmd
type that would prevent this sort of thing?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
你想对消息做什么,你会如何做,以及这是否是一个好的计划
我保证我会尽力回答我认为你想做的事情,但首先我认为有更重要的事情...
您可能假设
Cmd
类似于 Haskell 中的IO
,但Cmd
是异步的,而不是旨在连锁行动。您的更新
是将结果粘合到输出上的:在
更新
结束时,您可以发出Cmd Msg
来要求elm在外部执行某些操作,通常会传递一个构造函数来包装其输出。此输出返回到您的update
函数。您不做的是将
Cmd
链接在一起,就像在单子中一样。Cmd
没有bind
,或者换句话说,Cmd
的唯一bind
是您的更新
功能!现在我想,如果您想捕获
MyComplexMsg : Msg
并将其转换为[SimpleMsg1,SimpleMsg2]
,您可以在update< 中对其进行模式匹配/code> 函数,保持模型不变并发出新的
Cmd Msg
,但是第二次运行什么命令?你当然可以接受一个纯粹的
Msg ->;列出 Msg
函数并使用Cmd.map
来应用它,或者在update
开头的模式匹配中手动应用它,就像甚至进入完整状态monad 风格
尝试模拟 Monadic 绑定,但我不知道为什么你可能想要这个,并且 elm 文献和社区中的很多建议是,如果你从 update 调用 update 你可能会这样做错误的。为这些东西创建一个单独的单一用途辅助函数,而不是重新运行整个程序逻辑两次!
放弃对单子的需求
我怀疑问题在于你没有放弃单子控制流心态。
update
就是它所在的位置。update
是您让事情发生的地方。用户输入和异步消息是您的驱动程序,而不是排序。Cmd
仅用于外部通信。您无需将结果返回,elm 架构会为您完成此操作。只需将Cmd
的结果(将以消息形式到达)作为update
中的分支进行处理,一切都会顺利进行,并且如果用户按下某个按钮他们自己的选择,如果你不让他们发生,那就这样吧。你也可以处理这个问题。我担心你试图在 elm 中编写一个 monad 转换器堆栈,这有点像试图在 haskell 中编写一个面向对象的编程库。 Haskell 不进行面向对象编程,人们越早放弃 OO 思维并放弃将数据和函数捆绑在一起的需求,他们就越早编写出好的 Haskell 代码。 Elm 不做类型类,它做模型/视图/更新,而且做得非常好。不再需要查找和使用 monad 来控制程序流程,而是响应所提供的消息。为您想要发生的事情创建一个
Msg
,提供一种在您的view
中适当触发它的方法,然后在您的update
中处理它。什么时候一条消息应该变成三条消息?
如果您的一条消息确实是三条消息,为什么它不是三条消息呢?如果只是为了响应该特定消息,您只需对模型执行三件事并发出五个命令,为什么不只使用一条消息并获取
更新
使用纯代码对模型执行这三件事并批量发出五个命令?如果您需要记录成功登录,然后获取用户的照片,然后在数据库中查询他们最近的活动,然后显示所有内容,然后我不同意即时性,它们都是异步的。您可以发出命令来批量执行这些操作,当响应返回时,您将需要单独处理每个操作 - 在图像到达时使用图像更新您的模型,在图像到达时使用最近活动的列表更新您的模型。一旦您的模型处于图片和最近活动都在那里的状态,您就可以更改视图,但为什么不一出现就显示它们呢?
使用 monad 有时会训练我们在不需要的情况下进行效果编程时按顺序思考,但现在,最后,我将解决当迫切需要对命令进行排序时该怎么做。
真正必要的顺序命令
也许确实存在您需要的顺序命令。也许您必须在向其他地方发送某些请求之前查询某些数据存储。您仍然不使用绑定,只需使用
更新
:一个方便的点是,如果您的模型没有所需的数据,它会自动不会在视图中显示丢失的数据(获胜的总和类型),并且您可以在模型中存储多阶段过程所处的任何状态。
您可能更愿意将所有这些消息放入一个新类型中,以便它在处理程序中进一步缩进,并且永远不会与顺序中的其他消息混合,就像
但更可能使用像 MSPmsg 这样的辅助函数消息->更新MultiStageProcess。
结论性建议
也许有一些很好的用例可以深入研究消息和命令并对其进行编辑,但您尚未明确说明,但是
Cmd
是不透明的,您所能做的就是发出它们并处理生成的消息,所以我对此表示怀疑,但绝对感兴趣。另外,在为您提供要编写的
update
时,几乎就像他们为您提供了要编写的特定于应用程序的bind
(但它不是一个函子)而且你绝对确实查看数据),所以他们给了你特斯拉的钥匙。这需要一些时间来适应,但您真的会喜欢红绿灯处发生的事情。在您学会驾驶门铰链之前,请勿尝试拆卸门铰链。编辑:您的具体用例:页面间通信
事实证明,在聊天中,您试图从一个页面获取消息以便在其他页面或整体更新中使用 - 有时一个页面需要告诉应用程序更改页面并告诉新页面开始动画。如果我一开始就知道的话,我可能会跳过上面的所有建议,但我认为这对于来自 Haskell 的任何人来说都是很好的建议,我将其保留下来!
多条消息
我仍然认为重要的是要接受有时一条消息需要涵盖多个操作,并且您可以在
update
函数中对其进行排序,而不是尝试创建多条消息来响应单个用户操作。许多 elm 人给出的建议是,您的消息应该描述过去发生的事情,而不是像
AddProduct
那样描述要做的事情,部分原因是这就是消息在您的更新中发送给您的方式
因此,您对 elm 运行时正在执行的操作的心理模型是准确的,部分原因是您不太可能想要发送两条消息,并在应该发送一条消息时进行奇怪的消息翻译。我想指出的是,您在消息类型中转换消息并以某种方式过滤它们的想法是在错误的位置进行的。无论如何,您必须在
update
中进行过滤,以便您的主页不会获取登录页面的所有消息 - 不要在创建或传递消息时尝试过滤它们。update
是决定如何响应用户输入的地方。消息用于描述用户输入。好的,但是如何让消息跨越页面之间的障碍呢?!
有几种方法可以实现这一点,并且可能值得研究在 Elm 中制作单页应用程序 (SPA) 的不同方法。我找到了 这篇文章 作者:Rogério Chaves 在 Medium 上发表的关于从子页面到父应用程序组织消息的各种方式的主题非常具有启发性。他在 此存储库中以不同的方式完成了 TodoMVC 应用程序 堆栈溢出帖子更好如果它内嵌想法,那么我们就开始:
这可以通过为所有模块导入的消息类型创建一个单独的模块来实现。消息看起来像
ProductsMsg (UserCreatedNewProduct ProductRecord)
,无论如何它们都可能这样做,但因为所有消息类型都是全局的,所以您可以调用另一个页面的方法。使用比这些更好的名称(例如
Login.Msg
而不是LoginMsg
),但是...(您需要
NoOp :: OutMsgFromLogin
或使用Maybe OutMsgFromLogin
。我不喜欢NoOp
。将它用于未实现的功能非常诱人,并且它是所有脱离用户意图的消息之王,它无法解释为什么您应该什么也不做,或者我认为您如何编写一些生成无目的消息的内容。这是一种代码味道,表明有更好的编写方式。)Msg
消息转换为全局消息消息。(再次,使用更好的特定于域的名称,我试图在我的名称中传达用法。)
并且在您的 main 中,您可以指定这些:
您可以在登录代码中使用这些参数化函数它们都使用 LoginMessagesRecord 并生成
msg
,或者您可以使用真正的本地消息类型并在登录模块中编写翻译助手:然后您可以使用
Html.map loginMsgTranslator在全局视图中使用loginView
,或者如果您使用非常出色的无html和css方式编写elm应用程序,则使用Element.map loginMsgTranslator loginView
,elm-ui。总结/要点
您可能还想阅读此 Reddit 帖子,了解如何扩展 elm 应用罗杰里奥·查维斯 (Rogério Chaves) 提到的。
What you're trying to do to messages, how you might, and whether it's a good plan
I promise I'll try to answer what I think you're trying to do, but first I think there's a more important thing...
You're perhaps assuming that
Cmd
is analogous toIO
from Haskell, butCmd
is asynchronous and isn't designed to chain actions. Yourupdate
is what glues consequences to outputs:At the end of your
update
, you can issue aCmd Msg
to ask elm to do something externally, usually passing it a constructor with which it can wrap its output. This output comes back to yourupdate
function.What you don't do is chain
Cmd
s together as you would in a monad. There's nobind
forCmd
, or to put it another way, the onlybind
forCmd
is yourupdate
function!Now I suppose that if you wanted to catch a
MyComplexMsg : Msg
and turn it into[SimpleMsg1,SimpleMsg2]
, you could pattern match for it in yourupdate
function, leave the model unchanged and issue a newCmd Msg
, but what command would you be running the second time?You could certainly take a pure
Msg -> List Msg
function and useCmd.map
to apply it, or apply it manually in the pattern match at the beginning ofupdate
, likeor even go full state monad style with
to try to emulate monadic bind, but I don't know why you might ever want this, and a lot of advice in the elm literature and community is that if you're calling update from update you're probably doing it wrong. Make a separate single-purpose helper function for that stuff instead of re-running your entire program logic twice!
Let go of your need to have a monad
I suspect that what's going wrong is that you're not letting go of a monadic control flow mentality.
update
is where it's at.update
is where you make things happen. User input and asynchronous messages are your drivers, not sequencing.Cmd
is just for communicating externally. You don't plumb the results back in, the elm architecture does that for you. Just handle the result of yourCmd
(which will arrive as a message) as a branch in yourupdate
and it'll all progress nicely, and if the user presses some button of their own choice without you making it happen, so be it. You can handle that too.I worry that you're trying to write a monad transformer stack in elm, which is a bit like trying to write an object oriented programming library in haskell. Haskell doesn't do object oriented programming, and the sooner folks drop the OO thinking and let go of their need to bundle data and functions together, the sooner they're writing good haskell code. Elm doesn't do typeclasses, it does model/view/update, and does it extraordinarily well. Let go of your need to find and use a monad to control the flow of your program, and instead respond to what messages you're given. Make a
Msg
for something you want to happen, provide a way to trigger it appropriately in yourview
and then handle it in yourupdate
.When should one message become three messages?
If one of your messages is really three messages, why isn't it three messages already? If it's just that in response to that particular message, you just need to do three things to your model and issue five commands, why not just have one message and get
update
to do those three things to your model using pure code and issue the five commands in a batch?If you need to log the successful login, then get the user's photo, then query the database for their recent activity, then display it all, then I disagree about the immediateness, and they're all asynchronous. You can issue commands to do each of those things in a batch, and when the responses come back you will need to separately deal with each - update your model with the image when it arrives, with the list of recent activity when it arrives. Once your model is in the state that the picture and the recent activity are both there you can change the view, but why not show each as soon as they're there?
Using monads sometimes trains us to think sequentially when we're doing effects programming when we needn't, but now, finally, I'll address what to do when there is a compelling need to sequence commands.
Genuinely necessary sequential commands
Perhaps there really is something sequential that you need. Maybe you have to query some data store for something before you send some request elsewhere. You still don't use a bind, you just use your
update
:A handy point is that if your model doesn't have the required data, it automatically won't show the missing data in the view (sum types for the win), and you can store whatever state your multistage process is in in the model.
You might prefer to put all of those messages into a new type so it becomes indented once further in the handler and won't ever be mixed with other ones in the order, like
but much more likely use a helper function like
MSPmsg msg -> updateMultiStageProcess
.Concluding advice
Maybe there's some great use case for delving into messages and commands and editing them that you haven't made explicit, but
Cmd
is opaque and all you can do is issue them and handle the resulting messages, so I'm sceptical but definitely interested.Also in giving you
update
to write, it's almost like they've given you the app-specificbind
to write (but it's not a functor and you absolutely do look at the data), so they've given you the keys to the Tesla. It takes a bit of getting used to but you're really going to like what happens at the traffic lights. Don't attempt to dismantle the door hinges until you've learned to drive it.Edit: Your specific use case: inter-page communication
It turns out in chat that you're trying to get messages from one page to be usable in other pages or the overall update - sometimes one page needs to tell the app to change page and tell the new page to start an animation. I might have skipped all the advice above if I'd known that initially, but I think it's good advice for anyone coming from Haskell and I'm leaving it in!
Multiple messages
I still think it's important to accept that sometimes a single message needs to cover multiple actions, and you sort that out in your
update
function rather than try to create multiple messages in response to a single user action.Lots of elm folks give the advice that your messages, rather than describing something to do like
AddProduct
they should describe something that happened in the past, partly because that's how messages come to you in yourupdate
so your mental model of what the elm runtime is doing is accurate, and partly because you're less likely to want to make two messages and do weird message translations when you ought to make one message.I'd like to point out that your idea to convert your messages and filter them somehow within the messages type is doing it in the wrong place. Filtering so that your Home page doesn't get all the messages for your Login page is something you have to do in
update
anyway - don't try to filter them while you're making or passing the messages.update
is where it's at for deciding what to do in response to user input. Messages are for describing user input.OK, but how do you get messages to cross the barriers between pages?!
There are a few ways to achieve this and it might be worth looking into different ways of making a Single Page Application (SPA) in Elm. I found this article by Rogério Chaves on Medium quite enlightening on the topic of various ways of organising messages from child page to parent app. He's done the TodoMVC app all the different ways in this repo A stack overflow post is better if it inlines ideas, so here we go:
This can work by having a separate module for your message types which all your modules import. Messages look like
ProductsMsg (UserCreatedNewProduct productRecord)
, as they might well do anyway, but because all the message types are global you can call another page's methods.Use better names than these (eg
Login.Msg
rather thanLoginMsg
), but...(You'd need
NoOp :: OutMsgFromLogin
or useMaybe OutMsgFromLogin
there. I'm not a fan ofNoOp
. It's terribly tempting to use it for unimplemented features, and it's the king of all divorced-from-user-intentions messages that doesn't explain why you ought to do nothing or how you came to write something where you generated a purposeless message. I think it's a code smell that there's a better way of writing something.)Msg
s messages into global messages.(Again, use better domain-specific names, I'm trying to convey usage in my names.)
and in your main, you would specify these:
You can either parameterise functions in your Login code with those so they all consume a LoginMessagesRecord and produce a
msg
, or you can use a genuinely local message type and write a translation helper in your Login module:and then you can use
Html.map loginMsgTranslator loginView
in your global view, orElement.map loginMsgTranslator loginView
if you're using the utterly brilliant html&css-free way to write elm apps, elm-ui.Summary / takeaway
You might want to also read this reddit thread about scaling elm apps that Rogério Chaves references.