Fork me on GitHub
#other-languages
<
2022-08-01
>
didibus05:08:32

One feature of Elixir I can't tell is madness or genius or both, but it's the backbone of the Phoenix framework and why they managed to make a framework feel similar to Rails in my opinion, is the use keyword and using macro. You can read a bit about it here: https://brooklinmyers.medium.com/using-use-usefully-in-elixir-and-phoenix-b59a5ea08ad2 I think you can do the same in Clojure, without any changes to the language either, it's kind of just a convention, though you'd need to require some things so it be a little less magic. Basically, it's like calling a macro from another namespace at the top-level of your namespace which will return a bunch of top level code injected into your own namespace. That code could itself require or import more things into your namespace or call more of those injecting macros from other namespaces. Think something like:

(ns foo)
(defmacro using []
  `(do (require [clojure.string :refer :all])
       (defn render [name]
         (join "," ["Hello" name "welcome!"]))))
 
(ns bar)
(foo/using)

didibus06:08:55

It kind of end up being used similar to inheritance in OOP, so one Elixir module inherits from another, but it's done literally by just capturing the code from another module into your own through the use of a macro. It's hard to know if that means it has all the same problem inheritance has or not... But it's magic in that it can turn a module into a convention with a lot of default functionality loaded in and some extension points. And it can also automatically require and refer a bunch of stuff.

didibus06:08:09

Compare:

defmodule TodoWeb.ItemsController do
  use TodoWeb, :controller

  alias Todo.Items

  def index(conn, _params) do
    items = Items.list_items()
    render(conn, "index.html", items: items)
  end
end
with:
package TodoWeb;

import Todo.Items;

class ItemsController extends TodoWebController

  public index(conn, params) {
    var items = Items.listItems();
    this.render(conn, "index.html", items);
  }
}

Martynas Maciulevičius08:08:44

Why not create a module interface and use Java inheritance? Also this could then allow to use some kind of dependency injection or parameter-based extension and not inheritance

didibus16:08:14

What do you mean module interface? In Elixir they don't have inheritance, similar to Clojure. So in order for a namespace to automatically have some functions and variables "inherited" from a base, it uses a macro to copy them over. The result is not just that you can use those from inside the namespace, but that other namespaces can use them from your namespace.

Martynas Maciulevičius18:08:37

I meant to use Java's interface. But I see that you want to use a macro. Which is fine. I haven't used Ruby but I think it's OOP. So why not reuse Java with Clojure's defrecord and reify. :thinking_face:

didibus20:08:16

It's not Ruby, it's Elixir, they share a similar syntax. Interfaces and therefore defrecord/defprotocol doesn't get you the same thing as inheritance. Which is why people in Elixir came up with this strange macro instead. The macro can pull in vars, requires, imports as well as functions.

mauricio.szabo19:08:55

I honestly hate the use macro

mauricio.szabo19:08:39

I had SO MANY libraries I could not use outside Phoenix, or ExUnit, or things like that because these libraries expected me to use SomethingFromPhoenix or whatever

mauricio.szabo19:08:38

Sure, I could track which functions I was not implementing and provide mocked versions for it, but again, this all could be avoided if use didn't exist at all, so library authors would not be able to "cut corners" like that...

didibus06:08:07

That's an interesting data point, I hadn't thought of like other libraries, though I don't know, is that the fault of use or of those pretending to be libraries's libraries?