How to get to grips with Elixir tooling/IDEs?

Hey all again, I am back → a few months ago I started dabbling with Elixir and Phoenix.

Long story short, I came away a little confused and little jaded. I just couldn’t get the appeal (of phoenix)

I suspect it is simply just a matter of unfamiliarity and not really thinking in the same way as the framework at this time. (I like building things from the ground up as it helps me understand more about what I have built → It is why I like Go so much… I tend to be able to reason a lot more about what I need to do to achieve something)

However, I really like elixir (baring some issues that I would like some help with and the point of this post) So I have been trying to look more into setting up http servers with elixir, and I have got to say I am little lost…

A lot of the docs (for example in Bandit) assume that you are using phoenix.

I guess my main confusion comes from the tooling. Is there a secret sauce so to speak to make the tooling a little more useful in my IDE?

for example: I will install bandit and then… I just don’t know what to do with it.

In go for example it would be:
go mod init <name>
go get <module>
import <module>

Then I can start getting autocompletes within my ide fairly consistently

in Elixir, (I really like the language… just I am really struggling with the tooling)

  • Place my imports into the mix.exs
  • mix deps.get
  • Then I am lost… some libs want to be placed in a config… but that just confuses me as it just seem like values are going everywhere, without me really understanding why (despite looking at all the docs) or sometimes using mix.Install
  • I am really unsure how to start seeing import options/ or autocompletes or documentation. (How to configure)
  • Take Plug for example (which is what I am trying to use over phoenix at this time) I just don’t get why we configure things in the way the docs are configuring them.

I know that this is stemming somewhat from my unfamiliarity with at least the mental model. But I build my own frameworks in php, go, node… so I do feel like I should be able to get this. I am just really struggling dealing with the tooling and setup of a project. And I don’t know where to look or things feel really inconsistent and/or magical… like… you do this thing here for this library… just “because”… And that is the one part of the language (right now… that is just not gelling with me)

Is there a resource and / or book to help with this aspect of Elixir? (I have Elixir in Action (third addition) coming this week so I hope that might help

To address the topic:

  • What IDE are you using?

  • What have you tried so far?

I wish I could really help with the rest of the post (I haven’t tried to make a framework), but I have found config to be pretty straightforward, mostly. Follow the docs, and occasionally break conventions as needed, if I can.

I am using VScode With elixir-ls (nothing too fancy here)

I think my issue comes from the “magic” feeling that I am getting from everything and there being no discoverability of imports (at least for me right now…

let me use a basic supervisor as an example

defmodule HttpServer.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  @impl true
  def start(_type, _args) do
    children = [
      # Starts a worker by calling: HttpServer.Worker.start_link(arg)
      # {HttpServer.Worker, arg}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: HttpServer.Supervisor]
    Supervisor.start_link(children, opts)
  
  end
end

Just in this little bit of code I feel really overwhelmed…

For example

Where is Application coming from?

Where does Supervisor come from? (Standard lib, I know but just using it as an example)

I am sure it might be really obvious to others… it just feels very “magical” and you do it because that is how you do it…

Again… I could be completely out of line here, and just be completely not getting something (thus my questions) → But this might be a good example of what I am feeling. I just feel like a lot of Elixir when dealing with libs is this pre defined dance of semantics but with very little feedback from the ide.

I think I somewhat understand that use is sort of like metaprogramming and injecting things at compile (please correct me if I am wrong on that)

Sorry if this feels like I am just not getting it.

When I hover my mouse over Application in VS Code with ElixirLS I get this:

That’s pretty nice, IMO. And that View on hexdocs link is super-useful.

Yeah, I get that too, it is really good, I agree. I think (at least right now for me, it is a bit inconsistent some libs don’t really have this, like right now I am trying to work through the bandit plug docs, and I just feel stuck.) Again, I think it is coming a lot from my unfamiliarity with the mental model, and I hope that the book I have coming helps me at a more foundational level.

And FYI, that popup-window scrolls down: there’s more to read.

Also, when I add a ‘.’ at the end of Application, it gives me accurate completions. (They don’t really make sense here with the use statement, but they are all things we can reference from Application:

For sure, Go is much more statically typed than Elixir, and I think that does improve the IDE assistance.

Personally, it’s something I accept as a tradeoff for the benefits of the community and technology.

Yeah. I love the idea of Elixir… I really want to learn it just for the BEAM/OTP model (It is a thing of beauty) I think I just got to put my nose to the grind stone a bit more by the sounds of it haha…

I think a lot of your problems will go away when you have some experience under your belt, plus a working LSP.

Typically, imports are aliased, so you’ll at least know that SomeModule.some_function() comes from SomeModule. (There are times where this is not the case, but I’ve found this to be the case most of the time.)

Also yes, use is used to execute a certain type of macro.

Yeah, I think you are right… I guess right now I am in this stage of tension between what I am used to and how elixir works.

How long do you feel did it take you to really start getting things?

Yep. I’ve gotten first-hand great experiences from it because of the OTP & BEAM:

  • An ultra-fast “redirect server” that simply returns 301’s for an old domain. It processes http requests in micro-seconds (a 1/1000th of a millisecond) while using effectively zero CPU and RAM.
  • A queue-based SaaS that uses the built-in technology and is rock solid.

So maybe one reason for your shock: you’re stepping into a large “batteries included” framework - not just an http library. So lots of these concepts like Supervisor means wrapping your head around someone else’s design, and figuring out how to plug your ideas in.

I got familiar with Elixir on its own, without the framework overhead, from exercism.org - you do the coding exercises in a test-driven style, on your own computer. I compare my solutions to the highest rated ones. My profile: dogweather's profile on Exercism

Yes exersim is amazing! It is one of the reasons I can safely say… I love the language of elixir. It is just so refreshing to write. I think you are right in your prognosis it does feel a little more batteries included and having to work through “okay… why was this decision made”

I will step back with the book I have coming later this week and start more at a foundational level in terms of the project organisation.

Also I was being a b it a numpty it seems with auto complete…

I had recompiled everything but I didn’t realise I had yet to include my subfolder in the LSP so it had no clue about Bandit… sigh… haha.

Phoenix was only the second web framework that I really learned in-depth (first was Python’s Django), and it took a few weeks before I felt like I really internalized the main parts.

That’s not to say that Phoenix was really that different at a fundamental level, just that I was so used to doing things the Django way that I had to really bend my brain to see things the Phoenix way. I bet it will take you less time since you probably had more experience, and in a variety of languages, and you’ve made your own frameworks.

For Elixir itself, I got it pretty quickly, but I was already forcing myself to program in a functional style (when possible, preferring pure functions) before I started learning Elixir.

That is so awesome! It is really cool to hear how people can just click with things… It has honestly been taking me a long time to click for me with regards to phoenix and using third party libs in elixir / structuring projects, I wouldn’t say I have more experience though.

I am not sure if I will end up going with phoenix to learn (At least for now, and explore just building things with req / bandit + some templating syntax

But I do appreciate both @dogweather and yourself @arcanemachine for just talking about your experiences, it is already helped me a ton just in this short time and I feel a little more sure moving forwards.

That is another part of this language that I am very drawn too: the community seems so diverse and open for the most part which is really welcoming.

No worries. I kind of enjoy the frustration of learning a framework/language. The satisfaction when everything clicks together is always worth the struggle. :slight_smile:

On the other hand, I hate screwing around with my config just to get the stupid LSP to work…

Others seem to have helped with the tooling and easing into learning side of things. There is a lot to learn about the “how” and “why” for OTP, but I’ll try to answer your specific questions as best I can…

Application and Supervisor are modules provided by Elixir’s standard library. Modules don’t need to be imported/required/aliased to reference them or call functions on them. They are available globally within a single namespace.

The use Application bit invokes a compile time macro. It’s a way to inject functions, implement behaviors, generate additional modules, and otherwise minimize boilerplate.

There are some hard rules about what is done at compile time vs runtime, and that guides most of where libraries instruct you to put things.

  • config.exs/dev.exs - compile time configuration must go here, e.g. config that’s used as module attributes (like constants)
  • runtime.exs - dynamic configuration that’s read from the environment, e.g. the PORT environment variable for a http server
  • application.ex - startup functionality and the supervision tree for your specific application. You can define runtime configuration in here if needed, but it’s not conventional

Beyond that it comes down to convention, really. The only way to absorb the style and convention is by looking at more libraries and applications.

Starting with Plug to make a minimal server, ala go, is starting in hard mode compared to using the components provided by a full framework like phoenix. It’s great for learning, but will require you to dig deeper into OTP behaviours than is needed to start building applications.

I’d encourage you to get started with existing libraries to get a feel for how they’re configured and composed, then start building something minimal from OTP primitives once you have your footing.

That was my #1 problem coming from Python where you know exactly whats available on the current scope. Still confuses me sometimes.

I would take the time to really learn how the module system works as you’ll find it’s likely more sane than you think. It’s not a huge topic but more than I can type out here, but the docs do a good job.

A few points that may be helpful (that expand on @sorentwo’s answer):

import doesn’t work like it does in many languages. As touched on by @sorentwo, you don’t have to import a module to use it, they are all always available. import is for importing functions (and macros), and only functions (and macros), you cannot import variables or module attributes.

There is no way to magically affect what is available inside a defmodule from the outside. The exception is that every module has an implicit import Kernel which contains such important functions/macros as + and def. This is hard coded into the compiler and there is no way to do this with any other module. If you see just the following:

defmodule A do
  def foo do
    B.foo()
  end
end

B is a top level module, full stop. If it’s not in your project it’s either from a library or it’s builtin.

If you see this:

defmodule B do
  def foo do
    foobar()
  end
end

…that’s a compilation error :slight_smile: Without any imports or uses, there is no way to make a random function call like that work.

As you correctly identified, a wrench is thrown into things as soon as you see a use. This means there are macros and therefore metaprogramming at play. It is encouraged to always have a callout block in the documentation to describe what useing your library will do, so usually you can look to the docs to know what’s up.

It’s very important to at least get comfortable with the ideas of macros because from a technical standpoint (not trying to be reductive here, BEAM friends), Elixir’s powerful and well-designed macro system is the thing it’s bringing to the table as a BEAM language. There are other really good reasons to be here like the community, the syntax, and of course the libraries, but a lot of the best libraries and projects are so good because of macros. I’m absolutely not trying to discourage you from getting to know Elixir better, but the cool thing is that if you decide it just isn’t for you, there are other great macro-free BEAM languages like Gleam and, of course, Erlang itself! But I think it’s worth it to stick to Elixir :smiley:

Python does have a good culture around imports and enabling static analysis. But TBH I see the Elixir culture at about 90% of where Python’s at:

Unlike Ruby, you will see the source of every artifact at the top of the module (.ex file).

But unlike Python, the Elixir ecosystem makes frequent use of meta-programming and DSLs.

LOL, you said what I said in 83 million fewer words.