Crowdhailer

Crowdhailer

Creator of Raxx

This example overrides Kernel.def/2, so I am aware it is only suitable as an experiment. However here are the results.

Given the functions fetch_user and fetch_cart that both return {:ok, value} or {:error, reason}.

defmodule MyApp do
  use OK.Kernel

  def checkout(user_id, cart_id) do
    user <- fetch_user(user_id)        # `<-` will bind user when fetch_user returns {:ok, user}
    cart <- fetch_cart(cart_id)        # `<-` will shortcut to else clause if returned {:error, reason}
    order = checkout(cart, user)       # Lines without `<-` behave normally

    order.invoice_id
  else
    :user_not_found ->
      IO.puts("No user for user_id: #{user_id}")
      nil
    :cart_not_found ->
      IO.puts("User has no cart")
      nil
  end

end

By using OK.Kernel errors can be grouped in what I consider to be a very natural way.
I guess it’s natural because it looks like how I would arrange raise and catch in previous languages. However with the magic of Elixir macros I can do it all without using exception raising.

To try it yourself add the release candidate to your mix.exs

{:ok, "~> 1.7.0-rc.1"}

Comments welcome.

My motivation for trying this was to separate unhappy paths due to code errors ( I still raise exceptions for them) and the unhappy path due to bad input.

First 7 of 7 Posts Switch mode

Eiji

Eiji

@Crowdhailer:

  1. You have double :user_not_found in example code :slight_smile:
  2. I suggest to change else to something … else :smiley:
    How about: catch_error?
  3. and also maybe: <- to for example: <=
  4. Maybe also do not override def and defp and add edef and edefp
    where: e <= extended :smiley:

Imagine:

defmodule Example do
  use OK.Kernel
  
  def sample do
    result <- do_something()
    string_results = for something <- result, do: to_string(something)
    result2 <- do_something_else(string_results)
    if result2, do: :success
  else
    :an_error ->
      IO.puts "An error occurred"
      nil
  end
  # retunrs :success or nil
end

Compare it to:

defmodule Example do
  use OK.Kernel
  
  edef sample do
    result <= do_something()
    string_results = for something <- result, do: to_string(something)
    result2 <= do_something_else(string_results)
    if result2, do: :success
  catch_error
    :an_error ->
      IO.puts "An error occurred"
      nil
  end
  # retunrs :success or nil
end

What do you think about it?

aseigo

aseigo

Personally, I think this leads to hard to reason about code due to the loss of locality between the function body and the else that may or may not be triggered. It’s the same reason goto is “considered harmful” (caveats and exceptions to that aside :slight_smile: )

This feels like a place where with would be a good solution. A bit of syntatic sugar around it to tease the behavior of with to automatically succeed on {:ok, anything} and fail on {:error, anything} would be nicer imho, and iirc someone recently did exactly that.

Also, I find that if I have long(er) functions where there are conditions somewhere in the middle that need to branch, such as on failure or success, that this is a signal that perhaps I should break that function there and send processing on to another function which will continue on success or return on failure.

IOW, each function goes as far as it can until need to react to a success/failure and at that point calls another function. So each function ends up being a bit-sized bit of code that does execute completely, allowing each function to be reasoned about simply and clearly.

Exceptions (or, what they really are: early returns) are a tool of “last resort” for me, used when they are really required only. Similarly for case/with/etc. If those start nesting, or a function ends up being a series of them, I take that as a code smell to start chopping that function up into more atomic sets of code to stuff into functions.

Crowdhailer

Crowdhailer OP

Creator of Raxx
  1. Thanks for the typo spot. have edited it.

  2. I have to use else, only certain terms will split up a do block in the ast, i think I could use catch or rescue but that is the end of the choices.

  3. maybe? why?

  4. If you don’t like overriding the OK library has other options including its own version of with.

@aseigo I think you are probably refering to other features in the OK library for the alternative to with

aseigo

aseigo

Indeed it is .. small worlds :slight_smile:

Eiji

Eiji

@Crowdhailer:
2. oh, I see … How about store method name in atom instead of code block?

defmodule MyModule do
  use OK.Kernel

  def sample(arg1, arg2) catch_with :fun_name do
    # block
  end

  defp fun_name(error), do: # ...

Is it (or something similar possible?
2. and 3. because new Elixir developer could be confused when see that some operators do multiple works
I added it in example codes. Compare for item <- items ... with item <- get_items() and that part when if is directly above else - as a … not new developer I don’t have a problem with it, but I can see that it could be confused for new users.
4. yes, I saw that - btw. good job :smiley:

Crowdhailer

Crowdhailer OP

Creator of Raxx
  1. Interesting, I have no idea if that’s possible. an interesting thought.

The <- is deliberately the same, because considering things as a monad it is the same operation (a bind).

This is very pseudocode but eventually you could have something like.

List.for item <- items

Result.for item <- item_or_error # Result monad equiv to OK

Maybe.for item <- item_or_none
aseigo

aseigo

Oooh… catch_with would be rather nice imho .. great idea! Now .. is it possible? :slight_smile: If it is .. +1 from my (largely irrelevant) vote

— All posts loaded —

Where Next? Top

Trending in Announcing Top

bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
387 15136 120
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 11030 135
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
shahryarjb
The Chelekom project is a library of Phoenix and LiveView components generated via Mix tasks to fit developer needs seamlessly. One of i...
New
woylie
Phoenix components for pagination, sortable tables and filter forms with Flop and (optionally) Ecto. pagination cursor pagination sorta...
New
kip
Please say hi to a new lib, Astro that aims to deliver easy-to-consume astronomy calculations of practical use. For now it only calculat...
New

Other Trending Topics Top

akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New
Herve37
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using. We’re particularly inte...
New
mudasobwa
I am seeing a lot of aplications of Argumentum ad Vericundiam in software discussions. They do link some piece of writing and point us to...
New

We're in Beta

About us Mission Statement