Aduril

Aduril

Hello there,
Whenever I setup a new project, there is a small function I always add: reply/1. What does it do?
In a LiveView mount, handle_event or handle_info
instead of writing
{:ok, stream(socket, :posts, Blog.list_posts())}
or
{:noreply, stream_insert(socket, :posts, post)}

one should be able to write

socket 
|> stream(:posts, Blog.list_posts())
|> reply(:ok)

or

socket
|> stream_insert(:posts, post)
|> reply(:noreply)

Therefore, a function called reply/1 could be added into Phoenix. It could be implemented somewhat like that:
def reply(socket, reply) when is_atom(reply), do: {reply, socket}

This could increase the readability (it’s faster to “scan” the code of your LiveView).
You can put that little function in your project, but I would suggest to make it a part of LiveView itself.

I am eager to hear your opinion on that :slight_smile:

Showing Posts 1 to 10

sodapopcan

sodapopcan

This has been talked about before a few times. I used to have have separate ok, noreply, reply, cont, halt functions but ultimately I found I preferred just using the plain ol’ bare tuples. I harp about “scannability” a lot and actually found this pattern to hinder it. With my eyes zooming through some code without actually reading any of it, I found it basically impossible to catch nested returns without a { poking out at me as the pipe version just looks like any other ol’ pipeline.

I don’t think this pattern is particularly bad for those who like it, but I don’t think it belongs in LiveView as it’s really easy for anyone to implement themselves. Some people also do this which doesn’t require any extra functions:

socket
|> assign(:foo, foo)
|> then(&{:ok, &1})

Also, you will want to to change it to reply/3 mount is also allow to return a 3-tuple!

al2o3cr

al2o3cr

It’s only more-readable if you know what it stands for, and it doesn’t stand for very much… :man_shrugging:

If you really really really want you actions to be pipelines, there’s then:

socket
|> stream(:posts, Blog.list_posts())
|> then(&{:ok, &1})
sorax

sorax

First of all, I like the idea and appreciate @Aduril sharing a suggestion.
Yes, the same thing can be achieved in many other ways - but I don’t think that’s the point here.

People new to elixir & phoenix learn about the great syntax and readability.
There is real value in being able to scan and understand code quickly. Pipes do help a lot there. So the idea is good!
Think of it this way: A language and framework should be consistent and predictable. The LiveView functions are something of a special case here. A plug or controller don’t return {:halt, conn} or {:ok, conn}, do they? No, you pipe your conn all the way through.

The best solution for me would be if the @impl functions like mount and handle_* would do the “reply magic” in the Phoenix.LiveView macro on their own.
I see no point in typing {:noreply, socket} in every LiveView because it adds no functionality and no value. (therefore it shouldn’t be in every LiveView you write)
It’s just something you have to explain every time you show LiveView to someone coming new to phoenix.

LostKobrakai

LostKobrakai

It’s not true that this is without value.

Those tagged tuples exist, because they do distinguish multiple distinct multi value return types.

:noreply is not the only tag you’re able to return from those functions. E.g. for handle_event may return {:noreply, Phoenix.LiveView.Socket.t()} | {:reply, map(), Phoenix.LiveView.Socket.t()}.

And even for the callbacks, which cannot currently allow multiple distinct tagged tuples that’s what allows them to evolve without breaking changes to eventually in the future have additional return tuples. E.g. handle_event didn’t always have the :reply tuple as a valid return type. It was eventually added once the integration with js hooks became a thing. That would’ve been a more complicated change if you would’ve been allowed to just return state without tagged tuple before.

Also this approach doesn’t really exist in isolation as well. The way those return values are structured are quite consistent between many different behaviours of various levels of abstraction backed by a process. You can look at GenServer, :gen_statem, GenStage, Broadway, Phoenix.Channel, Phoenix.LiveView and you’ll find that approach taken between all of them.

So this actually is achiving what you’re calling for:

derek-zhou

derek-zhou

Then you need to explain to a new team member that the reply/2 call must be the last of the “train”. You are replacing one idiom with another, more obscure one.

sodapopcan

sodapopcan

Is the “explaining it to newcomers” really such a big deal? This often gets used as an argument for several things and I feel it might be overstated. In this particular case, return tuples are consistent and there are no gotchas. You can go a very long time without understanding exactly why it is that way and still be productive in LiveView. Meanwhile in the world’s most used web framework, you have to worry about pretty insane things like memoizing functions if they are defined in a certain place or remembering not to wrap your state setup in conditionals that are much harder to explain.

Aduril

Aduril OP

Well,
I would not have thought that my suggestion would cause so much discussion. Nice to see that the community is so lively :slight_smile:

In general I agree, that this would not be any big change, nor would it make LiveViews essentially better. What I like is, that you can keep the pipeline flow a little bit better. Yes, I know that |> then(fn socket -> {:no_reply, socket}) does the codewise the same, but when I recently introduced some of our junior devs to our phoenix project, I saw that these helpers at least caused some cluttering in their minds and it did not help them to focus on the relevant parts.

I think my core issue is, that for a beginner it seems like an arbitrary rule at the beginning, that you have to learn without seeing the benefit.

Anyway, thanks for the replies! :slight_smile:

thiagomajesk

thiagomajesk

I’ve been working on a project for a while that was first built by a lot of Elixir “newbies” sort of speak (at least that’s what I heard) and this is one of the first things that immediately caught my attention when I started browsing some of the project’s LiveViews.

I think this is mainly because a lot of people who first learn Elixir get mesmerized by how beautiful pipelines are and try to shoehorn everything in there (yeah, it’s cool I know, been there done that). But sometimes I find that this obsession tends to create an extra cost of overly abstracting things at the expense of readability.

Also, I want to echo what @derek-zhou said:

I’m not against abstraction, but poor abstractions create a level of indirection that makes things harder to follow, so keep that in mind.

sodapopcan

sodapopcan

I’ve been biting my tongue in this thread on that so thanks for saying it :slight_smile:

I love the pipe operator and use it often but as you said, a lot of folks go out of their way to make everything a pipeline claiming it’s more readable. I think they are mistaking “readability” with “pretty-looking”. I agree, pipelines are super pretty but they become horrendously unreadable if they change types too often—sometimes even when they change types just once—and especially when they perform side-effects in the middle :scream: And “Just stick a |> dbg() at the end” is not a solution—if you have to debug code to understand it, that is the very definition of unreadable.

Sorry, this is a sticking point for me :upside_down_face:

Aduril

Aduril OP

I’m not against abstraction, but poor abstractions create a level of indirection that makes things harder to follow, so keep that in mind.

So far I think we are on the same side :wink:

I think this is mainly because a lot of people who first learn Elixir get mesmerized by how beautiful pipelines are and try to shoehorn everything in there (yeah, it’s cool I know, been there done that). But sometimes I find that this obsession tends to create an extra cost of overly abstracting things at the expense of readability.

I think, I see what you mean, but here the shoehorning is entirely not the case, isn’t it?
The paradigm of LiveView encourages exactly that pattern with a socket being transformed into another socket. This pattern breaks only at the end.
I would necessarily say that the reply function would be a big win regarding this, but for me it feels like a small imperfection within the framework to have a ceremony at the end that serves no real benefit for most of the basic use cases*. Do you see my point there?

Edit:
* given that there are of course use cases, where it serves a purpose. Though relevant, I personally so far encountered just a handful of those cases.

Where Next? Top

Trending in Proposals: Ideas Top

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
aseigo
ICal is a library for interacting with iCalendar data. It parses iCalendars into typed Elixir structs via ICal.from_ics, and can prepare ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews