What To Discuss About The _With_ Keyword?

Hi all,

I’m going to do a presentation on the with keyword at an upcoming user group meeting. Would anyone care to share any fun or interesting uses of with that they’ve come across in writing Elixir code? Thanks for anything anyone cares to share. If I use anyone’s ideas I will credit them (of course) and I’ll try to post a slidedeck if I come up with anything worthwhile.

2 Likes

I really look forward to this! with still is somewhat new, and it is one of the features I find myself to not use so often as I don’t fully grasp when it is useful yet.

More material on when it is helpful is greatly appreciated! :slight_smile:

1 Like

I think the with keyword really shines in situations where you could use the pipe operator but where error handling is somewhat cumbersome.
That is for me the most practical usecase. But, ofcourse i’m very interested in other practical usecases.

1 Like

I concur Sven; I was hoping to hear about any other use cases others may
have found as well.

1 Like

For anyone that likes with I encourage you to look at the Happy package, it is with but done better, a more simple syntax, better error handling, and more. I use it extensively in ways where with would be substantially more verbose, and yet it is trivially simple in implementation. It is what I think with should have been. With is more like a single-loop comprehension (that is what <- tells me), where happy_path is more like a block that you expect to succeed, yet you handle its errors with utter ease.

Here is a simple example of it from one of my projects:

  def index(conn, _params) do
    happy_path!(else: handle_error(conn)) do
      @perm true = conn |> can?(index(%Perms.Help{}))
      render(conn, :index)
    end
  end

This checks if the user has permissions, if not it will redirect them via my default handle_error callback to the login page (you can also handle errors in-line too).

A more complex example:

  def show(conn, %{"sid" => sid_param, "id" => sem_id_param}) do
    happy_path!(else: handle_error(conn)) do
      @perm true = conn |> can?(show(%Perms.Nursing.Student.Semester{}))
      {:ok, {_cnum, uid, _obj, student}} = verify_nursing_student(sid_param)
      @perm true = conn |> can?(show(%Perms.Nursing.Student.Semester{uid: uid}))
      {:ok, {sem_id, semester}} = verify_nursing_semester(sem_id_param)
      semesters = Repo.all(from sem in Student.Semester, where: sem.uid == ^uid and sem.semester_id == ^sem_id)
      render(conn, :show, semesters: semesters, sid: sid_param, semester: semester, student: student)
    end
  end

This one checks if they have permission to the area, then tries to convert the sid string to an integer and fetch the student (returning an :error tuple if it fails), then checks they have permission for this specific student, then gets all semesters for the student, and finally renders it. If any of it fails my handle_error callback handles it (I have some functions that have special in-line error handling too, but those are rare, I try to follow my handle_error for proper webserver results). It will do unauthorized on permissions issues (and redirect to login saving the current path), it will do bad request if the conversion from string->data could not be done, etc… etc… It has significantly simplified my phoenix code.

Read the docs on it to see how powerful and succinct it is (and it has examples in both the docs and tests on the code it converts it to, it is extremely straightforward).

The same author also made ok_jose as a simplified pipe operator that works over, by default, {:ok/:error, data} tuples, though you can define your own shapes too.

EDIT: Also, happy_path is older than with, I’ve not clue why with was not modeled on it… It was never mentioned at all in talks, so they might not have known. But really, with has nothing over happy_path.

1 Like

Setting aside a general comparison between happy path and with, what does @perm do?

1 Like

I think a lot of people are interested in being shown a comparison between case do and with. For example, when with was first announced I thought it was just another way to use case do. It somehow felt like the expression had just been flipped around. So I think it would be a good approach to start by separating it from the other conditional expressions :slight_smile:

2 Likes

That is one of my things. :slight_smile:
happy_path supports tags, just putting a @anything in front of a match statement and it just ‘tags’ it. My permissions are not uniquely tagged (due to how the canada library works) so adding @perm converts something like @perm true = blah() into {:perm, true} = {:perm, blah()} so that if perm is false the match fails and thus the ‘else’ branch gets called (which I rarely have) and if it is not handled there then the else: gets called (which in my case I have a {:perm, false} matcher), but if it does not get handled there then it will, if happy_path return the {:perm, false} or if happy_path! then it will throw it as an exception. I like using happy_path! everywhere in phoenix so I get notified if there is an error case that I am not handling but I should handle, but a few cases happy_path is useful to just always return something. I use the @tags for a surprising amount of things to easily tag and make greppable library code that does not follow useful error returns.

1 Like

I would say be sure to include the relatively new feature that allows you to pattern match in the else clause. This was a huge win for the recent update for me and allows me to use it much more easily.

2 Likes

Thanks for sharing. I missed that one, very usefull feature indeed!

2 Likes