vic

vic

Asdf Core Team

Expat is a tiny experiment I did for extracting patterns and being able to reuse them (compose and share patterns between elixir libraries). Look for zombies in the README.

It was born as some of my first attempts at creating composable things like the just released data specification library Spec.

Hope anyone finds it interesting at least.

Showing Posts 1 to 10

OvermindDL1

OvermindDL1

Huh, looks to save a lot on readability. Is it mostly just for maps?

vic

vic OP

Asdf Core Team

not just maps, anything that can be a pattern is valid.

ibgib

ibgib

Wow. I find it very interesting to say the least.

I notice in your readme (and in your tests) that you are defining the pattern with the string key but matching using the pattern(key: value) which uses the atom keyword syntax. I looked at your macro code, but I’m just not versed enough with macros to quite pin it down…are you somehow converting the keys to atoms in order to match in this way?

Also, am I reading it correctly that the keywords are flattening the keys, e.g. the lat/lng vars in the readme?

And just to “pinch myself” before using this to refactor my controller code, I would be able to do something like the following?:

Before (difficult to read with more form fields):

def fork(conn, %{"fork_form_data" => %{"dest_ib" => dest_ib, "src_ib_gib" => src_ib_gib} = form_data} = params)

to After (composable, and readable):

# Reused patterns
defpat src_ib_gib_p(%{"src_ib_gib" => src_ib_gib})
defpat dest_ib_p(%{"dest_ib" => dest_ib})

# Pattern for just this one function
defpat fork_params_p(%{"fork_form_data" => dest_ib_p(...) = src_ib_gib_p(...)})

def fork(conn, fork_params_p(fork_form_data: form_data, dest_ib: dest_ib, src_ib_gib: src_ib_gib) = params)

Also, I am planning in the future on switching to structs for changeset validation. Would using expat preclude me from using structs?

vic

vic OP

Asdf Core Team

There aint any key conversion, when you have

defpat foo %{"bar" => baz}

# and then

def something(foo(baz: qux)), do: qux
# Actually you are just replacing one pattern `baz` for another `qux`
# in this example turns out both patterns variables (since assignment is matching an unbound name pattern)
# so instead of a variable `baz` you get a variable `qux`

# (lets use some other kind of things besides maps for examples)

# And because It's just pattern replacement you could place any other pattern instead of `baz`, say:
def something(foo(baz: [first | _tail] )), do: first

# Actually that's how composition works in `expat`,
# if you have something that looks like a function call (its expat-expanded) since anyways function calls
# are not allowed in an elixir pattern.

defpat batman({:bruno, sidekick})
def something(foo(baz: batman(sidekick: robin))) when robin in ["demian"], do: "oh son"
# batman(sidekick: robin) gets expanded to {:batman, robin} you replaced pattern `sidekick` by `robin`

# If you notice, the previous example had batman expanded with an explicit keyword `sidekick: robin`
# that means, that of all `variable` patterns inside `batman` you only care for replacing `sidekick` all others
# we dont care and get replaced by `_` placeholders

# composition takes advantage of this, for example:
defpat latlng {lat, lng}
defpat trip {origin = latlng(), destination = latlng()}

# remember you are just replacing variable-patterns
trip(destination: {0, 0}) # would expand to:  { _ = {_, _}, {0, 0} = {_, _} } notice only `destination` got replaced.


Hope this can explain a bit better how expat works.
Also if you want to help by improving the README that would be great, as my english is almost always a bit weird. :smiley:

Cheers :slight_smile:

vic

vic OP

Asdf Core Team

Oh and one distadvantage of how things are right now, is that

defpat latlng {lat, lng}
defpat trip {origin = latlng(), destination = latlng()}

# if you replace lat from trip directly  it will be replace in both
trip(lat: 99) # exapnds to { _ = {99, _}, _ = {99, _} }

but you could then define trip like

defpat trip {origin = latlng(lat: orig_lat, lng: orig_lng), destination = latlng(lat: dest_lat, lng: dest_lng)}

but that doesnt seems to have much advantage over a normal pattern tho

vic

vic OP

Asdf Core Team

oh btw, there’s defpatp for private patterns (wont be visible from other modules it’s defmacropd)

vic

vic OP

Asdf Core Team

haha sorry for the response by pieces, ^^ last one: using structs, no problem, as previously shown expact doesnt care, if you can pattern match it you can use it.

ibgib

ibgib

Ah, so in foo(baz: qux), you’re actually accessing the variable assigned to the value of the key in the original map? In looking at your readme, this confusion arose I think because all of your examples have the same name for both the key/value, e.g. "iq" => iq and "email" => email. I had errantly latched onto the keys being converted. But now I see that you are pulling the variables (which are atoms) out of the pattern and using those for composition. That’s awesome! :+1:

Yes, this makes it much clearer to me. Thanks! :smile:

vic

vic OP

Asdf Core Team

Thanks to you! Will definitely update the README with better examples. Thanks !

ibgib

ibgib

Possibly. It depends I would think on how much reuse you’re getting from the pattern. The pattern itself looks relatively complex, but you’re giving it a name and then composing it more granularly, and if you reuse it even once, I think it would be a win.

In the OO world (where I came from), I would use granular interfaces and gradually build more complex data structures with them. This looks like a lightweight way of implementing similar interfaces, which is also partly why I was asking about how they’d jive with structs.

I love helping with documentation, but I don’t think I could add anything to your readme as it stands - it is really entertaining. :laughing:

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
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
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
CodeSync
Code BEAM Europe 2026 – Call for Talks is Open! We’re excited to announce that Code BEAM Europe 2026 is now open for talk submissions :t...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews