Pathex

Fast. Really fast.

What is Pathex?

Pathex is a library for performing fast actions with nested data structures in Elixir. With pathex you can trivially set, get and update values in structures. It provides all necessary logic to manipulate data structures in different ways

Why another library?

Existing methods of accesssing data in nested structures are either slow (like Focus) or do not provide much functionality (like put_in or get_in). For example setting the value in structure with Pathex is 70-160x faster than Focus or 2-3x faster than put_in and get_in

You can checkout benchmarks at https://github.com/hissssst/pathex_bench

Check out Pathex documentation!

You can find complete documentation with examples, howto's, guides at https://hexdocs.pm/pathex

Installation

def deps do
  [
    {:pathex, "~> 1.0"}
  ]
end

Usage

  1. Import it

    require Pathex
    import Pathex, only: [path: 1, path: 2, "~>": 2]

    Or you can just use Pathex!

    # This will require Pathex and import all operators and path/2 macro
    use Pathex
  2. Create path

    path_to_strees = path :user / :private / :addresses / 0 / :street
    path_in_json = ~P"users/1/street"json

    This creates closure which can get, set and update values in this path

  3. Use the path!

    {:ok, "6th avenue" = street} =
        %{
          user: %{
            id: 1,
            name: "hissssst",
            private: %{
              phone: "123-456-789",
              addresses: [
                 [city: "City", street: "6th avenue", mail_index: 123456]
              ]
            }
          }
        }
        |> Pathex.view(path_to_streets)
    %{
      "users" => %{
        "1" => %{"street" => "6th avenue"}
      }
    } = Pathex.force_set!(%{}, path_in_json, street)

Features

Speed

Paths are really a set of pattern-matching cases. This is done to extract maximum efficency from BEAM's pattern-matching compiler

# Code for viewing variables for path
path(1 / "y", :map)

# Almost equals to
case do
  %{1 => %{"y" => res}} -> {:ok, res}
  _                     -> :error
end

Reusability

Paths can be created and used or composed later with rich set of composition functions

# Takes username from user structure
username = path(:personal / :fname)

{:ok, "Kabs"} =
  %{
    personal: %{fname: "Kabs", sname: "Rocks"},
    phone: "123-456-789"
  }

# Takes all usernames!
all = Pathex.Lenses.all()

{:ok, ["Kabs", "Blabs"]} =
  [
    %{
      personal: %{fname: "Kabs", sname: "Rocks"},
      phone: "123-456-789"
    },
    %{
      personal: %{fname: "Blabs"},
      phone: "123-456-790"
    }
  ]
  |> Pathex.view(all ~> username)

Rich toolkit

Perform create, update, select operation with different behaviours using Pathex.Lenses module High level operations like filtering and updating nested values have never been this easy

import Pathex; import Pathex.Lenses

# Change first username in a list
[
  %{personal: %{fname: "Alabs", sname: "Rocks"}},
  %{personal: %{fname: "Blabs"}}
] =
  [
    %{personal: %{fname: "Kabs", sname: "Rocks"}},
    %{personal: %{fname: "Blabs"}}
  ]
  |> Pathex.set!(some() ~> path(:personal / :fname), "Alabs")

Powerfull abstraction

Pathex is built around simple primitive called path, therefore can be simply extended.

path or path-closure is just a closure with special primitives. Anything complying with Pathex.t() spec can be used within Pathex

Safe and simple

All path-closures are pure and macro are hygienic. There is no magic

Contributions

Welcome! You can check existing TODO's


By the way

  • If you have any suggestions or want to change something in this library don't hesitate to open an issue
  • If you have any whitepapers about functional lenses, you can add them in a PR to the bottom of this readme