hst337

hst337

Hi there,

I’ve been working on a Pathex library for quite a while now, and I’ve finally managed to create a release which is able to cover all requirements in nested structures access. Think of Pathex as Elixir’s Access but on steroids, or like Clojure’s Spectre but easier to learn and more efficient.

Features

  1. Efficient in time. It’s 2-5 times faster than Access. HTML manipulations with Pathex are 5 times faster than Floki. To achieve this efficiency, Pathex generates pattern-matching cases at compile-time.

  2. Easy to use. Pathex is like a Map or Enum module, but for any structures. At it’s tiny core, it has everything you might need in your development needs. Errors are very descriptive. And I’ve put some effort into the documentation to be easy to navigate

  3. Functional. Pathex is built around functional lens idea, but it differs in some ways from it.

  4. Rich toolkit. Pathex works fine with lists, tuples, maps, structures, nested structures like AST or HTML. Pathex is reusable, because composition of two paths creates another path and so on.

Feedback

Feedback is really appreciated. I’d like to know what you’re thinking about library design because this library tries to extend the language!

https://github.com/hissssst/pathex

Showing Posts 22 to 13

Asd

Asd

Here you go

defmodule X do
  import Pathex
  import Pathex.Combinator
  import Pathex.Lenses

  def post_lens(post_id) do
    combine(fn recursive ->
      matching(%{id: ^post_id}) ||| path(:children) ~> some() ~> recursive
    end)
  end

  def set_show_reply_form_to_true(tree, post_id) do
    Pathex.set!(tree, post_lens(post_id) ~> path(:show_reply_form), true)
  end
end

And try it out with this


%{
  id: 1,
  show_reply_form: false,
  children: [
    %{
      id: 2,
      show_reply_form: false,
      children: [
        %{
          id: 4,
          show_reply_form: false,
          children: []
        }
      ]
    },
    %{
      id: 3,
      show_reply_form: false,
      children: [
        %{
          id: 5,
          show_reply_form: false,
          children: []
        }
      ]
    }
  ]
}
|> X.set_show_reply_form_to_true(4)
|> IO.inspect()

A few words about this cryptic thing

combine(fn recursive ->
  matching(%{id: ^post_id}) ||| path(:children) ~> some() ~> recursive
end)

I know that combine is hard to get correctly from the first try, especially when combined with logical operators (like |||), but my advise here is to always think of it as of a recursive tree traversal.

Once you get into the node, it might be the node you were looking for (hence the matching(%{id: ^post_id}), but if it is not (hence the |||), you need to check it’s children. First you need to find where the children are (under children key, hense the path(:children)), then you need to traverse all children or just some (here I assume we can encounter post with id=4 only once, so we need to find the single one, hence we do ~> some()), and then you need to treat them just like you did (hence the ~> recursive)

BrightEyesDavid

BrightEyesDavid

This section of the docs shows what I think I’m after, but I’m rather lost and have not managed to replace any nested value yet.

I have a nested structure of unknown levels, generated from Ecto results and Arbor. Simplified:

%Post{
  id: 1,
  show_reply_form: false,
  children: [
    %Post{
      id: 2,
      show_reply_form: false,
      children: [
        %Post{
          id: 4,
          show_reply_form: false,
          children: []
        }
      ]
    },
    %Post{
      id: 3,
      show_reply_form: false,
      children: []
    }
  ]
}

There’s always one root Post struct.

I need to update a couple of keys’ values (e.g., :show_reply_form to true) for the potentially nested struct/map that has a given id. The id is coming from a user-triggered LiveView event value.

As part of stumbling through, it seemed as if I needed to change this line from the documentation:

combine(fn recursive -> some() ~> (recursive() ||| matching()) end)

to the following in order to avoid errors:

combine(fn recursive -> some() ~> (recursive ||| matching(_)) end)

Here’s what I’m trying:

target_id = 4

path_to_post =
  combine(fn recursive -> some() ~> (recursive ||| matching(_)) end)
  ~> matching(%{id: _})
  ~> path(1 / :id) # ???
  ~> filtering(&(&1 == target_id))

test = Pathex.set!(post_tree, path_to_post, "x") # just testing replacement

It’s not erroring, but nothing is replaced.

I do have my own (probably horrible) function that’s working, but I think it’s not memory-efficient and runs through more of the posts than necessary:

  # Prevents recursing into children of target post, but still recurses through the rest
  defp set_post_data(%{id: post_id} = post_tree, target_id, new_values) when post_id == target_id do
    # Root post is target; no recursing required
    Map.merge(post_tree, new_values)
  end
  defp set_post_data(%{children: children} = post_tree, target_id, new_values) do
    case Enum.find_index(children, &(&1.id == target_id)) do
      index when is_integer(index) ->
        # One of the children is the target
        new_children = List.update_at(children, index, &(set_post_data(&1, target_id, new_values)))
        Map.put(post_tree, :children, new_children)
      _ ->
        # None of the children are the target; recurse
        new_children = Enum.map(children, &(set_post_data(&1, target_id, new_values)))
        Map.put(post_tree, :children, new_children)
    end
  end
    new_values = %{
      show_reply_form: true,
    }
    post_tree = set_post_data(socket.assigns.post, target_id, new_values)

So I thought I’d see if I could use Pathex. But I’m very open to using a better function than mine, too. :slight_smile:

Thanks.

Asd

Asd

Try using Pathex.at/3 like this

Pathex.at(stuff, path(:key2) ~> star() ~> matching(%{foo3: _}), &Map.take(&1, [:foo, :foo2]))
RemyXRenard

RemyXRenard

I hope it’s okay to resurrect this thread. I have a quick question about Pathex. I feel like I may be missing something simple.

If I have a map → list → map structure, is there a way to return multiple keys from the inner map?

e.g.

stuff = %{ 
:key => "value",
:key2 => [ %{ :foo => "bar", :foo2 => "bar", :foo3 => "'baz"}, 
           %{ :foo => "baz", :foo2 => "fizz", :foo3 => "baz"}]
}

Pathex.view!(stuff, path(:key2) ~>  star() ~> matching(%{:foo3 => _}))

The above returns [%{foo: "bar", foo2: "bar", foo3: "'baz"}, %{foo: "baz", foo2: "fizz", foo3: "baz"}]
Can I return the values for :foo and :foo2 but not foo3? Is it only matching on the value?

I can add |> Enum.flat_map(& Map.take(&1, [:foo, :foo2])) to get what I need, just checking that I’m not missing some magic in the match.

hst337

hst337 OP

Pathex 2.5.0 release

This release contains performance improvements, bug fixes and special functions for creating for_update-friendly lenses for structures and records. For more information, refer to changelog

For example,

iex> import Pathex.Accessibility
iex> uri_scheme_lens = from_struct(URI, :scheme)
iex> %URI{scheme: "http"} == Pathex.force_set(%{}, uri_scheme_lens, "http")
true

This is useful for doing deeply nested force_set and force_update operations

hst337

hst337 OP

Pathex 2.4.0 release

This release contains bug fixes and improvements in negative indexes for lists and tuples and improvements for force_* operations. For more information, refer to changelog

For example, force_setting a value in list used to result in something like

iex> force_set! [1, 2], path(4), 0
[1, 2, 0]

And now the empty space is filled with nil like this

iex> force_set! [1, 2], path(4), 0
[1, 2, nil, nil, 0]
hst337

hst337 OP

Pathex 2.3 release is here :tada:

Nothing special, just

  1. Pathex.Short for shorter path definition. One can use just :x / : y instead of path :x / :y when using Pathex.Short
  2. Pathex.pattern for creating patterns from inlined paths. For example,
use Pathex, default_mod: :json
import Pathex

pattern(four, path :x / :y / :z / 3) = %{x: %{y: %{z: [1, 2, 3, 4]}}}
  1. Pathex now can be use-d inside functions or anything like this. This is now tested and supported.
  2. Paths inlining is now detected for aliased, imported and macro calls. This is done using Macro.Env.lookup* functions, therefore pathex is compatible only with elixir 1.13 or higher
  3. Spec fixes here and there, project formatting.

By the way, I’ve tested this release against gradient (from gradualizer project) and it seemed to be able to find some errors in specs, while failing to complete checking with exception and a bunch of false positives. And I’ve used mix_unused tool to find some unused functions (which helped me a lot)

hst337

hst337 OP

Hi, I’ve just released 2.2.0, you can now use this function:

hst337

hst337 OP

Yeah, that’s a good idea. I already have this in my TODO list, I’ll try to prioritize it for 2.2 or 2.3 versions then.

egze

egze

Thanks. Missed the matching/1

Do you think there’s a room for Pathex.new/1?

Thinking about:

def new(list \\ [])
  list
  |> Enum.reduce(matching(_), fn r, l -> l ~> r end)
end

I would be heppy to contribute. I get the compile time benefits, but sometimes it’s the way it is :man_shrugging:

Where Next? Top

Trending in Announcing Top

mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
kip
In 2021 I started a new library called Tempo with the objective of modelling time as a set of intervals - not as instants. In 2022 I gave...
New
quatermain
Hello, I’m sharing my plugin here in forum after some time so it has time to mature and proof yourself. I use Claude Code daily on a pr...
New
gtcode
Aloha gang, There’s a new class of AI model out from TypeSafe. Jev is their flagship model. You can learn about it here: ...
New
LostKobrakai
I’ve recently created a small library phoenix_vite integrating the vite build tooling with phoenix. It provides an igniter.installer t...
New
danschultzer
I needed something like premailer for rails, and after searching, I decided to make an easy-to-use library to handle inline styles and co...
New
kraleppa
If you’ve ever had to debug a BEAM node you know it can be a painful process. Parsing raw data, or dealing with clutter often gets in the...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
AstonJ
AI tools and services seem to be the new JS framework - with new ones popping up every 5 minutes - hence it might be worth posting one of...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews