Wrapper for receive block.

80 views
Skip to first unread message

Andrzej Podobiński

unread,
Jan 20, 2022, 10:22:43 AM1/20/22
to elixir-lang-core
Lastly, I was trying to implement a macro with similar functionality as assert_receive from ExUnit. The purpose of this macro was to allow the user to wait synchronously for a message specified by a pattern. The given pattern may contain variables that the user is interested to extract from the arrived message (exactly as in ExUnit.assert_receive). I've noticed that there is a significant amount of code that expands the given pattern and then collects the variables from it etc. in order to properly extract variables from the pattern. I have a feeling that it is not a valid solution to use an ExUnit.assert_receive in the production code, so maybe it would be possible to add the function of similar functionality to the Process module. Something like Process.await_message(). Another possibly better solution could be adding some functionality to the Macro module that would cover expanding the macro and collecting variables etc. (https://github.com/elixir-lang/elixir/blob/a64d42f5d3cb6c32752af9d3312897e8cd5bb7ec/lib/ex_unit/lib/ex_unit/assertions.ex#L467)

Theron Boerner

unread,
Jan 23, 2022, 12:58:14 PM1/23/22
to elixir-lang-core
Hi,

I'm wondering what sort of code you're trying to write where a receive ... after block wouldn't work well in this situation. Furthermore what do we do in the error case if the receive times out? For ExUnit this is quite simple: the test fails, but in your application code this doesn't mean anything. If you get to the point that you're specifying control from with this new macro then you've just recreated receive with more steps. Could you provide some code samples for what you're trying to achieve?

Best,
Theron

Andrzej Podobiński

unread,
Jan 24, 2022, 8:50:11 AM1/24/22
to elixir-lang-core
Hi,
thanks for your interest! I'm working on a specific pipeline in the Membrane framework. The API of this pipeline allows the user to subscribe for events that this pipeline emits, and then synchronously wait for these events. The idea is to provide a macro that wraps the receive block to make waiting for the event nicer to the user - just like assert_receive. 
ps. I'm aware that the current implementation of "await" I've linked won't work because of e.g. lack of macro expansion.

José Valim

unread,
Jan 24, 2022, 8:56:55 AM1/24/22
to elixir-lang-core
Honestly, I am not sure if it is worth encapsulating the event as you propose compared to a receive. For example, what happens if you want to receive at least one message from two distinct pipelines? What happens if you are inside a GenServer, where any receive pattern can be harmful and handle_info should be preferred?

The main reason "assert_receive" exists is not because of the convenience, but rather the improved error message. And those scenarios are less common too (at least you are not running a test inside a GenServer).

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/86836f06-f926-42de-aed0-9576d836b61cn%40googlegroups.com.

Mateusz Front

unread,
Jan 24, 2022, 12:09:56 PM1/24/22
to elixir-lang-core
Hi there,

we're preparing a message-based API as well, so one can use `receive` or `handle_info` when needed. However, we expect that functionality to be used mostly in elixir scripts/livebooks, where `await` is going to cover the majority of cases, saving us from having `receive` blocks everywhere. It can be also compared to `Task.await`, which has similar problems to what you described, yet it proves useful in particular situations. Another point is that extracting variables from a match can be used in many other scenarios and currently it requires hundreds of lines of code to implement properly AFAIK :P

Regards,
Mateusz

Ben Wilson

unread,
Jan 24, 2022, 12:30:45 PM1/24/22
to elixir-lang-core
For that purpose, I feel like:

{:ok, %{pattern: [match, here]}} = Foo.await(item)

would work right? Can you help me understand why the patterns themselves need to be passed to your `await` function?

José Valim

unread,
Jan 24, 2022, 12:47:47 PM1/24/22
to elixir-lang-core
Another idea is to generate a "make_ref()" when you subscribe, and then you can uniquely match on this reference on await, without requiring explicit patterns.

However, even if the above is not possible and the await(pattern) is the only solution, I don't think this is an approach we want to generally encourage. As mentioned, there are assumptions that are true under testing, and some corner cases, but not everywhere. :)

Mateusz Front

unread,
Jan 25, 2022, 6:00:31 AM1/25/22
to elixir-lang-core
> Can you help me understand why the patterns themselves need to be passed to your `await` function?

Because that way you can't `await` for a particular pattern ;) For example, we have a concept of playback states. One could need to wait until the playback state changes, so they could do

`await(pipeline, playback_state)`

and they would have the new playback state under the `playback_state` variable. But if they wanted to wait for a particular playback state, like `:playing`, they could do

`await(pipeline, :playing)`

I feel that would be a flexible way of handling scenarios when one needs to wait for a particular event. Anyway, I see two separate issues here
- The concept of waiting for a particular message by wrapping a receive block - like `Task.await`, `GenServer.call`
- Unpacking variables from a pattern passed to a macro - which is the problematic part here and as far as I understand it's not the Elixir way - in that case, we'll figure out something else, as it's definitely not worth going against the standards ;)
Reply all
Reply to author
Forward
0 new messages