Coder Social home page Coder Social logo

expede / exceptional Goto Github PK

View Code? Open in Web Editor NEW
293.0 293.0 10.0 326 KB

Helpers for Elixir exceptions

Home Page: https://hex.pm/packages/exceptional

License: MIT License

Elixir 100.00%
convenient elixir elixir-exceptions exception-flow exception-handler exceptions macros

exceptional's Introduction

Brooklyn Zelenka

handle Pronouns location Editor

Bluesky Profile GitHub followers

Hey there, I'm Brooke ๐Ÿ‘‹ I'm a composer-turned-programmer living in beautiful, rainy Vancouver.

Depending on the community, I'm best known for:

I sometimes give conference talks. You can find many of them on my public notebook.

What I'm (Currently) Excited About ๐Ÿ™Œ

  • Programming languages / PLT
  • Distributed systems
  • CRDTs-as-databases
  • Tech community
  • Startups & advising

Live Stats ๐Ÿ“Š

Trophies

GitHub Streak

exceptional's People

Contributors

ddevlin avatar expede avatar haljin avatar martinos avatar tsubery avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

exceptional's Issues

[Question] Do you have FromTaggetStatus in Exceptional?

Background

I have been reading about Exceptional and I have already read the Medium blog post as well as the docs.
Namely, one thing jumped to my attention:

Exceptional.TaggedStatus
View Source

Convert back to conventional Erlang/Elixir {:ok, _} tuples

You seem to have the functionality "ToTaggetStatus", but I was unable to find anything to convert from it.

Example

Consider the following code:

#A bad JSON string we cannot decode into elixir
iex> bad_order_str = "{\"a\": 1, b: 2}"
"{\"a\": 1, b: 2}"

#A good JSON string we can decode into elixir
iex> good_order_str = "{\"a\": 1, \"b\": 2}"
"{\"a\": 1, \"b\": 2}"

#standard JSON encoder/decoder
iex> Jason.decode(bad_order_str)
# KABOOM

iex> Jason.decode(good_order_str) ~> IO.inspect 
{:ok, %{"a" => 1, "b" => 2}}

Lets say I want to use JASON to encode and decode data. I am pretty much forced to use the bang (!) notation because Jason will give me a tagged tuple otherwise, thus defeating one of the main selling points of Exceptional (no more bangs!).

Alternatively, I have to make all my functions that pipe from functions returning tagged tuples to be able to cope with tagged tuples, which then forces me to use them throughout the rest of the code, which again defeats the purpose of using this library.

Questions

So, with this in mind, I am rather confused on how to proper use this library and if it fits my use case at all (involves a lot of JSON and side effects).

  • how do you handle tagged tuples in your code?
  • why don't you have a "FromTaggedStatus" module? (Is it because of the inconsistencies explained in your article?)
  • am I missing the true purpose/power of the library?

Convert from tagged tuples to exceptional

I really like the error handling approach exceptional takes, but I do have a concern about it's practical use. It has support for converting from exceptions to tagged tuples via to_tagged_status, but is there a strategy for going the other direction? It seems like a pretty common scenario since most of the Elixir ecosystem currently uses tagged tuples. Your medium article definitely made the case for this style of error handling, but also glossed over this issue by using a "hypothetical rewrite of File.read". How have you dealt with this while using the library?

Add Phoenix error view helpers

Might want them in another lib, not totally sure. Sorry that the details here are super slim: I have code in a personal project that I'm considering binging over, and this is mostly a note to self ๐Ÿ˜œ

A small request for an idea

Hello,

I just discovered exceptional and I see you're trying to introduce a few ideas from Haskell to Elixir, which is nice. With that given said, I was wondering if you could give me a few ideas on something I'm trying to accomplish.

I'm currently working with Phoenix and I was thinking about creating a library that will allow me to write my controllers using the following flow:

defmodule AuthController do

    # --- types -----------------------------

    defmodule SMS do
        defstruct code: nil,
    end

    defmodule Flow do
        defstruct conn: nil, params: nil, data: nil
    end

    # --- controller methods -----------------------------

    def authenticate(conn, params) do

        flow = %Flow{conn: conn, params: params, data: %{}}

        flow
        |> initialize(Convertion.params_to_sms_struct)

        # The following steps will apply a series
        # of transformations to flow.data. When a step fails
        # the pipeline will be stopped and the error will be
        # written to conn

        |> success(Data.sanitize)       # Sanitize data
        |> otherwise("bad data")        # Stop, write error to conn

        |> success(Auth.Verify.sms?)    # Verify match in DB
        |> otherwise("invalid code")    # Stop, write error to conn

        # All steps were successful, write final output
        |> finish(:json, fn flow ->
            conn |> json(%{token: flow.data.token})
        end)

    end

end

The main reason I want to do this is because it improves readability, and allows one to think about the flow as a series of linear transformations.

What do you think could be a good approach? My intuition tells me a monad would do the trick, but I must admit, I'm not extremely familiar with monads, monoids and the likes. So I'm still exploring possible solutions, and also learning. (:

Thank you!

Compile warnings on 2.1.0 on Elixir 1.8.0-otp-21.2.2

When depender project is compiling with warnings as errors then it fails to build:

warning: found quoted keyword "quality" but the quotes are not required. Note that keywords are always atoms, even when quoted. Similar to atoms, keywords made exclusively of Unicode letters, numbers, underscore, and @ do not require quotes
  /home/<user>/<project>/deps/exceptional/mix.exs:23

==> exceptional
Compiling 8 files (.ex)
warning: module attribute @lint was set but never used
  lib/exceptional/raise.ex:79

warning: module attribute @lint was set but never used
  lib/exceptional/safe.ex:82

warning: module attribute @lint was set but never used
  lib/exceptional/value.ex:104

Generated exceptional app

Capturing true/false

It would be quite convenient to be able to wrap up true/false into something that returns an exception if on false (maybe if on true via configurable option) or else passes the input value unaltered. An example:

# Assuming test_bool is a something like `safe` is in usage:
request_ip
|> get_client_ip(params)
|> Geoip.lookup_ip() |> normalize # normalize takes a tagged-tuple and converts it to an ErlangError or unwraps an :ok tuple
~> bool_test(should_search?/1).(parse_exclusion_zone(params)) # Here
~> build_search_query(%SearchQuery{}, params)
~> NGSearch.search() |> normalize
~> Analytics.track(client_ip, "api", "search_results")
~> (&json(conn, &1)).()
|> ensure!

Although with macro work could clean it up (and could clean up others, like safe as well).

Basically just return an ErlangError (preferably saying what failed, by saying the passed in 'should_search?/1' failed its test), else it returns the prior passed in value.

Questions about design of some parts

A couple questions on design decisions or perhaps requesting new helper functions.


First, why does safe return a fun instead of branching at the point of call to be used as normal. Right now at https://github.com/expede/exceptional/blob/master/lib/exceptional/safe.ex#L86 it is doing a large branching and an apply call for every call. If instead if it (or a new name) were made a macro, then it could be fully inlined instead as a calling site, so you could do this for the examples in the README.md instead:

[1,2,3] |> safe(Enum.fetch!(1))
#=> 2

[1,2,3] |> safe(Enum.fetch!(999))
#=> %Enum.OutOfBoundsError{message: "out of bounds error"}

safe(Enum.fetch!([1,2,3], 999))
#=> %Enum.OutOfBoundsError{message: "out of bounds error"}

This could help readability and especially the usage while piping. Easily implementable as well.


Second, if_exception and such have a similar setup, they take functions, where if they were macro's then the if_exception last example could become:

ArgumentError.exception("error message")
|> if_exception do
  %{message: msg} -> msg
else
  value # if the default variable name was value, could always make it configurable
end
#=> "error message"

Also easily implementable, just stuff the do body in a case statement (maybe with a default to continue passing unmatched exceptions) and the else as normal. Could even optimize it so if the do body macro list has only one element and it is not keyed on a :-> then it could be a simple pass-through, so these would both work:

ArgumentError.exception("error message")
|> if_exception do
  %{message: msg} -> msg
else
  value
end
#=> "error message"

# Or this, if the passed in variable was named something like 'exc', could give an option to rename it too if you want:
ArgumentError.exception("error message")
|> if_exception do
  handle_error(exc)
else
  value
end
#=> "error message"

Or a configurable variable name could be used like:

ArgumentError.exception("error message")
|> if_exception myvar, do
  %{message: msg} -> msg
else
  myvar
end
#=> "error message"

# Or this, if the passed in variable was named something like 'exc', could give an option to rename it too if you want:
ArgumentError.exception("error message")
|> if_exception myvar, do
  handle_error(myvar)
else
  myvar
end
#=> "error message"

And of course the else clause could be optional if it is just a pass-through anyway.


And so forth on the other parts, just to help reduce the typing required and increasing readability by removing all sorts of things like .() from many calls.

Idea: `pipe_safe/2` for cleaner piping.

For this I'll use a similar example to the one you used for Make Safe in the readme.

def max_of_row(nested_array, x, y) do
  toothless_fetch = safe(&Enum.fetch!/2)
  toothless_max = safe(&Enum.max/1)
  
  array
  ~> toothless_fetch(index)
  ~> toothless_max()
end

I think it would be great for readability if I could just say:

def max_of_row(nested_array, x) do
  array
  ~> pipe_safe(&Enum.fetch!(&1, x))
  ~> pipe_safe(&Enum.max/1)
end

If safe/2 didn't exist, then we would have been able to adapt the existing function to serve that purpose, but we would have to introduce a new name (or a new operator) to do it from where it is now.

As an operator (which I'm not necessarily advocating for, just including it for completeness' sake), that might look like

def max_of_row(nested_array, x) do
  array
  ~~> Enum.fetch!(x)
  ~~> Enum.max()
end

"Unbang"

I'm not 100% sold on this idea yet, but have come across a few times that it would have been useful. Essentially wrap a !ed function in a rescue, and return the exception struct, and return it as a value.

Ex.

unbang do: File.read!("no_file.txt")
#=> %File.Error{
#     reason: "could not read file \"no_file.txt\": no such file or directory"
#     action: "",
#     path: nil
#   }

# ROUGH outline of in flow
path
|> unbang(File.read!).()
~> upload_file

Note that the above syntax would likely be changed. Just a sketch of the idea.

ensure!

Alias for maybe_ok >>> id

This would help when you actually do want to explode. For compatibility with systems that depend on such behaviour (like Plug). Recommendation is to use this at the boundary of a system only, and handle everything inside as pure excerption structs.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.