This looks really nice Kamil. It is definitely a necessary project! :D
> Do you think the API would benefit from using just plain Elixir datatypes, instead of relying on macros for providing easy to understand, remember and use API?
A DSL must always exist on top of a plain API that works with data types. When working on projects that end up having a DSL, I usually explain them in one of the two ways:
1. Start with the plain API and at the end say: "ok, all those patterns can now be written a bit more cleanly like this" and then I show the DSL;
2. Or start with the DSL and then say: all we have been doing so far just translates to this.
At the end of the day, building the DSL on top of an actual API helps keep you honest. If all you have is a DSL, it becomes very easy to just to throw more dirty under the carpet, add hidden functionality, and inject code when you are not supposed to.
(Btw, every time I say DSL in this post, I am talking about internal language DSLs targeted to developers.)
Also, I would like to point out that data structures compose really well. DSLs do not as much. Let's consider an example from the README:
use Mailman.ExternalSmtpAdapter
What if I actually have two similar SMTP adapters where only the port changes? The DSL does not give me anyway to share this information. I have to duplicate it or define a module with a macro that is going to inject the content that I want. If all it is a data structures, I can put it in any function, call it, merge the new values, and call it a day. That's the main reason Mix only asks for data structures when defining your project configuration.
Also we need to be careful with the pattern of using "use" to introduce API into the target module. Here is a counter-proposal:
Mailman.ExternalSmtpAdapter.deliver(envelope, @config)
It is a tiny bit more verbose but now I know exactly the API my module provides. The focus should not be on what I should "use" to get a behaviour but on what interface I should implement to expose some behaviour.
How does this help keep you honest? If you hide everything under "use", you can inject N functions into the user module. This can potentially lead to adapters with big API surfaces, which leads to more code, corner cases, etc. However, if instead you say, "all an adapter needs to implement is X and Y", you are specifying the contract publicly.
In the OO world, someone would be saying "composition over inheritance".
Let's look at another example:
defmodule ErrorNotifiersEmails do
use Mailman.Emails, composer: EmailsComposer
compose :general_error_notifier, error do
There is a special DSL for defining e-mails (which is really sweet) but, as every DSL, it presents issues. What if there is a complex logic to define the subject? Or what if a bunch of e-mails have the same headers, how can I share them? I believe moving it to a private function is not going to work.
Also, why do I need to retrieve my e-mails with get/2? I already know how invoke functions passing arguments, why do I need to learn a new construct? What if the functions that compose my e-mails requires 3 arguments?
Of course, I am not saying we should get rid of all DSLs. But it is very important to keep in mind that, as everything else, DSLs impose trade-offs and we need to be quite aware of what those trade-offs are. In fact, I believe DSLs make APIs less elastic, because now I need to abide to the DSL rules.
My $0.02 :)
José Valim
Skype: jv.ptec
Founder and Lead Developer
On Sat, Mar 29, 2014 at 1:33 PM, Kamil Ciemniewski
<ciemniew...@gmail.com> wrote:
Y'all
I've created a working sketch proposal of a library that is meant to be providing means of defining and sending emails from your Elixir applications.
It provides a clean way of defining mailers. It also allows you to send multi-part email messages containing text and html parts. It encodes messages with a proper quoted-printable encoding.
There are two mailing adapters included:
- ExternalSmtpAdapter
- TestingAdapter
I'd like with that to start a discussion if it heads in a right direction. Especially I'd like to address the following:
- Do you think the API would benefit from using just plain Elixir datatypes, instead of relying on macros for providing easy to understand, remember and use API?
- How do you think the API could be simplified while preserving its elasticity?
You're all welcomed to contribute, critisize, work towards a nice and solid solution we could use in our projects.
Thanks!
--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.