wolf4earth
Babel was born out of a desire to simplify non-trivial data transformation pipelines. To focus on the “happy path” instead of having to write a bunch of boilerplate error handling code.
But don’t listen to me, take a look for yourself:
pipeline =
Babel.begin()
|> Babel.fetch(["some", "nested", "path"])
|> Babel.map(Babel.into(%{atom_key: Babel.fetch("string-key")}))
data = %{
"some" => %{
"nested" => %{
"path" => [
%{"string-key" => :value2},
%{"string-key" => :value2},
%{"string-key" => :value2}
]
}
}
}
Babel.apply(pipeline, data)
=> {:ok, [
%{atom_key: :value1},
%{atom_key: :value2},
%{atom_key: :value3}
]}
Since you’ll most likely build non-trivial transformation pipelines with Babel - which can fail at any given step - Babel ships with elaborate error reporting:
Error Reporting
pipeline =
Babel.begin()
|> Babel.fetch(["some", "nested", "path"])
|> Babel.map(Babel.into(%{atom_key: Babel.fetch("string-key")}))
data = %{
"some" => %{
"nested" => %{
"path" => [
%{"unexpected-key" => :value1},
%{"unexpected-key" => :value2},
%{"unexpected-key" => :value3}
]
}
}
}
Babel.apply!(pipeline, data)
Which will produce the following error
** (Babel.Error) Failed to transform data: [not_found: "string-key", not_found: "string-key", not_found: "string-key"]
Root Cause(s):
1. Babel.Trace<ERROR>{
data = %{"unexpected-key" => :value1}
Babel.fetch("string-key")
|=> {:error, {:not_found, "string-key"}}
}
2. Babel.Trace<ERROR>{
data = %{"unexpected-key" => :value2}
Babel.fetch("string-key")
|=> {:error, {:not_found, "string-key"}}
}
3. Babel.Trace<ERROR>{
data = %{"unexpected-key" => :value3}
Babel.fetch("string-key")
|=> {:error, {:not_found, "string-key"}}
}
Full Trace:
Babel.Trace<ERROR>{
data = %{"some" => %{"nested" => %{"path" => [%{"unexpected-key" => :value1}, %{"unexpected-key" => :value2}, %{"unexpected-key" => :value3}]}}}
Babel.Pipeline<>
|
| Babel.fetch(["some", "nested", "path"])
| |=< %{"some" => %{"nested" => %{"path" => [%{"unexpected-key" => :value1}, %{...}, ...]}}}
| |=> [%{"unexpected-key" => :value1}, %{"unexpected-key" => :value2}, %{"unexpected-key" => :value3}]
|
| Babel.map(Babel.into(%{atom_key: Babel.fetch("string-key")}))
| |=< [%{"unexpected-key" => :value1}, %{"unexpected-key" => :value2}, %{"unexpected-key" => :value3}]
| |
| | Babel.into(%{atom_key: Babel.fetch("string-key")})
| | |=< %{"unexpected-key" => :value1}
| | |
| | | Babel.fetch("string-key")
| | | |=< %{"unexpected-key" => :value1}
| | | |=> {:error, {:not_found, "string-key"}}
| | |
| | |=> {:error, [not_found: "string-key"]}
| |
| | Babel.into(%{atom_key: Babel.fetch("string-key")})
| | |=< %{"unexpected-key" => :value2}
| | |
| | | Babel.fetch("string-key")
| | | |=< %{"unexpected-key" => :value2}
| | | |=> {:error, {:not_found, "string-key"}}
| | |
| | |=> {:error, [not_found: "string-key"]}
| |
| | Babel.into(%{atom_key: Babel.fetch("string-key")})
| | |=< %{"unexpected-key" => :value3}
| | |
| | | Babel.fetch("string-key")
| | | |=< %{"unexpected-key" => :value3}
| | | |=> {:error, {:not_found, "string-key"}}
| | |
| | |=> {:error, [not_found: "string-key"]}
| |
| |=> {:error, [not_found: "string-key", not_found: "string-key", not_found: "string-key"]}
|
|=> {:error, [not_found: "string-key", not_found: "string-key", not_found: "string-key"]}
}
Trending in Announcing
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...
New
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
The Chelekom project is a library of Phoenix and LiveView components generated via Mix tasks to fit developer needs seamlessly.
One of i...
New
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
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Other Trending Topics
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
@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
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
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
Is there a word for the ~> symbol used in Version strings?
Do you also just call it a Squiggle Arrow™ ?!
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










First 10 of 10 Posts
wolf4earth
If you’re wondering whether or not
Babelis production-ready: we’ve been using a pre-release version for nearly a year at this point at work in production, and it made external API integrations a lot easier and smoother.kasvith
babel can be misinterpreted as the popular js transpiler
Eiji
Sorry, but your example does not looks really good …
Elixirlanguage and related hex packages maintained by core team members are as fast as other packages if not fasterLater in the main module’s documentation the
Error handlingis mentioned, but in this case all we have to do is to change a getter function like:KeyErrorand it’s messageBabel.fetch/1is confusing as it does not uses a trailing bang naming conventionSo far I did not found anything in documentation which is not handled by
Enum,Map,List,KernelandAccessmodules.wolf4earth
I agree, the simple example I laid out can all be done easily with builtin functions.
But
Babelisn’t meant to be used to simple data transformations. It’s meant to be used for non-trivial transformations. But putting that in a code example would make it a lot harder to grok and fail in it’s task to clarify the basic usage.Babel’s strength is in its composability, combining many simple elements into something that’s no longer simple but still understandable.Doing these non-trivial transformations with standard library mechanisms is feasible but if you don’t want to raise and instead collect errors and be able to explain them, it becomes very complex - and also very hard to read - very quickly.
I’m planning to add a LiveBook which showcases how to use
Babelto transform responses from GitHub’s GraphQL, which will lay out how the library shines for these non-trivial transformations.wolf4earth
Just to give an example: here’s an excerpt from how we use
Babelat work. I’ve changed model names and omitted some functions but the basic idea should be clear. The strength I’d argue is that it’s composable and readable in how it specifies the desired outcome, and if it fails at any given step it will tell you exactly what went wrong:This can then be used to parse like this:
Or if you only want to parse a single
Piece:wolf4earth
I’m not sure what to say to that. I guess it can.
Eiji
It would be perfect if you could give us also some example data and expected output as so far it looks simple as well, see:
For casting a complex structs
ectoshould be more than enough. Perhaps somebody reading this topic may not even heard about@field_source_mapperinecto.wolf4earth
As I mentioned before, I’m working on a LiveBook example.
But beyond that I can only repeat what I wrote earlier:
Maybe the confusion stems from this?
Babel does not raise unless you call
apply!/2(which callsapply/2under the hood and raises in case it returns an error). At no point during the transformation exceptions are involved. It’s all results that get accumulated.Your example is actually a good one to compare against because it’s largely optimized for the happy path. Defaulting to
nilvalues is not something that flies in production. Neither is usingMap.fetch!/2from my experience. Those are cases where gracefully handling errors is preferred. Babel is optimized for exactly this. That’s where most of the work is. But you get that for free.A comparable solution would check at every step if an error occurs, collect it, and return the accumulated error. It would be able to explain why that step failed, what the step was, and what the input data was. That version of the code will be a lot more complex. Often enough code is written that only focuses on the happy path, which then becomes hard to debug when something does not work. I’d like to refer to the initial error handling example to point out how much information Babel provides in error cases. You could say that Babel not only transforms but also asserts on the expected data shape.
Ecto is excellent when you have control over the shape of the incoming data. If you don’t, you will have to write a non-trivial amount of code to transform the data into a shape ecto understands. If Ecto fits your use-case: great, use that. But Ecto and Babel do not cover the same use-cases.
wolf4earth
What I’m taking away from this, is that more examples would be helpful and a comparison against libraries like Ecto.
Eiji
This one was only for the example you shared as you said it’s more complex one it’s used in production, so I have tried to re-produce it.
I didn’t have a problem with that at all, but who am I to speak about it when
Gentoowas my firstLinuxdistribution.Perhaps you mean cases like
Map.fetch/2returning:erroratom? It could be solved with a simple wrapper function.All
Elixircore features including debugging is more than enough for me and I rarely need to write any wrapper. As said ifElixircore is not enough at most I have usedecto.Ignore that, I got something wrong when quickly browsing documentation.
That’s a good point … for a
Rustdevelopers.I’m writing pipelines in pure
Elixirsupporting not single, but all “happy paths” and either “let it default” tonilin case some part of data is expected to be nullable or let it fail when it’s not supported. Especially the “let it fail” isErlangandElixirconcept, so speaking generally about “happy paths” have not sense in general context in such languages. Therefore at the very end the code is very specific to your needs. I believe that your package may therefore be or not be used even in very similar cases. Just speaking it’s a good for “Rust” was 50-50 for fun.Sounds like latest
Elixirimprovements likedbgmacro, improved error messages and using part of code in error output. I guess that I personally would be more interested in such package before mentioned improvements, but at the end I would stop use said package after that improvements would be added.Again who am I to speak about that when in many answers I gave few possible solutions where the biggest one is based only on the pattern-matching. I have no idea, but for some reason I like how things works now.
Oh, at first I did not expanded it, so I did not saw it. Hmm …
More seriously … it’s really long and maybe that’s because it somehow reminds me a terrible long stacktrace in
JVM-based languages. I’m exhausted by just seeingand XX moreat the bottom of already too long stacktrace. Again I like how things works now, so I’m not sure if I can see a real-world usage in my personal case.Why you mention control? You have only control if your app is generating the data, right? As said for all those years I have used pure
Elixirand at mostectoto cover almost all if not all cases of casting a data from for exampleJSONAPI response.What? You are developing the “shape” as same as you are creating structs. Regardless if data have proper format or not you simply pass
"audioCollection"value tochangesetfunction and in worst case you use some mapper like the default one supported byectoi.e.@field_source_mappermentioned previously.Yeah, I guess that I did not get non-ecto use cases.
I can see that posting:
without explaining what exactly use-cases you mean is really confusing. That makes me only more interested to hear “your story”.
Maybe answering such questions could help you describe your point of view more easily:
ectostarts causing trouble?@field_source_mapperare not enough for a data with different structure?Edit: When writing all of this I got some idea. If it’s not “something like”
JSONAPI that have a well known structure then the source could have a dynamic structure. Ok, but how to parse a dynamic structure? How about creating a complex htmlformfor dynamically querying data of unknown for the app structure (but known by user)? That’s interesting, but how such data would be used? How about using dynamic rules to fetch dynamic data in order to present it in a predictable format like a graph? Am I going into the right direction or by any change I have messed it up completely? Maybe I think about it too much and there is much simpler use case …