Argument/variable naming: Phoenix.Socket vs Phoenix.LiveView.Socket

A %Phoenix.LiveView.Socket{} is not a %Phoenix.Socket{} yet both are called socket.
Would it be better to call a Phoenix.LiveView.Socket struct a live_socket ?

1 Like

Names are always contextual. In the context of a live view module the %Phoenix.Socket{} is never accessible, so you don’t need to distinguish the live socket you have from an ordinary phoenix socket. In a similar way, we always refer to a %Plug.Conn{} as conn even though there is also a cowboy connection out there making the request work. The cowboy connection isn’t really accessible in the context of a plug, so conn has plenty of context and there’s no ambiguity.

3 Likes

To interpret contextual names it’s necessary to be aware of the particular context which is not always easy when there’s many contexts and you’re not actually working on the module you’re looking at.
When names are self explanatory there’s no need to figure anything out.
Explicitness really is the only thing Python has over Elixir in my opinion.

I’m going to agree with @benwilson512 here. The point of not exposing lv_socket vs phx_socket is that it doesn’t really matter in the LiveView itself. As a public consumer of LiveView, you shouldn’t need to worry about what the Socket is. If Phoenix wanted to swap out the implementation of Socket completely, they could do so and it wouldn’t affect you.

Phoenix itself does distinguish between the types of sockets. You can find an example in the LiveView source code. I think this is interesting because it shows that explicitness is being prioritized where important, and removed where it’s not important. If you are curious about how the sausage is made, then the source code is very accessible. This is a very pragmatic approach, imo.

It would be helpful to have more examples here. I’ve generally felt like Elixir as a language is really explicit. Libraries like Phoenix are good stewards of quality code, as well.

What concrete problem are you running in to?

To me live_socket would expose that I’m looking at a live view.

Another example could be:
https://groups.google.com/forum/#!msg/phoenix-core/oHKMYzWLBe8/maLSjnfsAgAJ

which is about assigns vs conn (.assigns)

The concrete problem is that I sometimes loose track of what is what.
For example today I was doing something like:

Phoenix.LiveView.live_render @conn,
  MyLiveView,
  looks_like_assigns: "but should be one of [:session, :container, :id, :router]"

And then I don’t get any feedback saying looks_like_assigns will never work,
probably you mean something else.
(of course I should read the docs better)

Can you elaborate on how that connects to socket variable name conventions?

To address what feels like a different topic, live_render having different args than render, I think a PR validating the options to live_render would be a great addition. It’s still very much a work in progress.

Well that example doesn’t.
I was trying to give an example of explicitness that would help.
With regard to socket variable name conventions many times I will pass sockets to helper modules that aren’t using Phoenix.LiveView.
Then, when I want to assign things, I would call Phoenix.LiveView.assign directly.
Which doesn’t work when it’s not a live socket.

Right, if I have a module that has helpers and some are for live view and some are not, then in THOSE functions I would have the variable be named live_socket. The context of the helpers is different from the context of a live view module. The caller of a function and the function argument don’t need to be exactly the same name. Type specs can be very helpful there too.

1 Like

Please don’t get me wrong, I absolutely love Elixir and Phoenix is awesome.
Even to the point where I dislike having to work with anything else.

My questions regarding names, explicitness and feedback are about understanding what’s going on when something unexpected happens while working with Phoenix.
I haven’t had a lot of experience with rendering and live views.
Mostly I’ve used Absinthe and routers with mostly default settings.
I didn’t study the inner workings of Phoenix a lot, I just went for it using a few howto’s on the internet.
To me, not fully understanding exactly what is the purpose of many of the moving parts, things can get confusing.
The docs suggest I shouldn’t have to worry about how stuff works, it just does.

Confusion is never about just one thing, it’s always about the constellation of things.
It’s also about being allowed to do stuff that is confusing.

What should be the type of a session?
Plug.Conn implements put_session/3 using Map.put/3, so probably session should be a map.
So probably I shouldn’t be able to do: live_render @conn, MyLiveView, session: false.
And this should probably not work: def mount(false, socket), do: ....
But is does.

And what happens when I don’t supply a :session options to live_render/3 ?
I receive an empty map which is confusing because that’s not what my session looks like.
When I don’t set a value it should be nil right?
And is the :session option to live_render/3 an actual session?
Or should I consider :session to be init_args that are named session in the context of a live view socket?
Which would be more like assigns and would have little to do with my concept of sessions.
Assigning init_args to the live view socket makes a lot of sense to me though.

What exactly are assigns?
At first I thought it’s stuff you use inside templates with @.
That’s what render(conn, template, assigns) suggests to me, assigns being a seperate argument.
You have a conn, a template and some assigns (bound to the supplied template).
So that would mean it’s not stuff you assign to a conn or a socket, but it is (and that’s awesome).
Having a seperate assigns argument to render/3 makes assigns in the context of rendering templates seem unrelated to assigns in the context of a conn or a socket.
It turns out that: conn |> assign(:some, :value) |> render("index.html") is equal to: conn |> render("index.html", some: :value).
To me the former is very explicit and the latter is not helping me understand what’s actually happening, my assigns seem lost in space.
So having a little helper argument to be able to write shorter code, makes me misinterpret the whole thing.

Assigns is not a Phoenix concept but it comes from Plug.conn.
According to the docs: assigns - shared user data as a map.
In my opinion assigns is an unfortunate name, why not call it user_data or shared_data.
Phoenix kind of hides Plug, so it’s not necessary for me to worry about an unfortunate name like assigns.
If it had been up to me, I would have probably implemented something like:
conn |> assign(%Phoenix.UserData{some: :value})
Forcing consumers to fetch any existing user_data and merge explicitly.
I like the function name assign and the concept of assigning A to B.
The implementation can match on A and make sure it becomes part of B in a meaningful way.
It can perform validation and transformation and give me feedback.
I could also conn |> assign(%Phoenix.SessionData{user_id: 1}) and pattern match on those types when passing values around.

Some things in Phoenix only become clear after a lot of investigation and gaining experience.
Some of this has to do with names that could mean anything or suggest things that weren’t intended.
And some of it has to do with lots of macro magic that is sometimes hard to follow.
For the most part it’s just that Phoenix is a litte complex for novice users.

I’m writing this to give some feedback about my experiences as a novice Phoenix user.
I would like Elixir and Phoenix to not only be awesome but also extremely popular.