Following the Adding Pages tutorial, I addded a new route, without creating the controller:
get("/hello", HelloController, :index)
And this compiles w/ no warnings! Even after doing mix clean ; mix compile. However, I can get a compile error if I use an unknown lowercased keyword in router.ex:
snackTime
But the following inside a router.ex scope compiles fine:
SnackTime
Is there some kind of lack of type safety for module names?
Well first there is no static typing in elixir. Second, module names are just atom, no different from any other atom:
iex(4)> Blah == :"Elixir.Blah"
true
So the only way to check it would be to actually call something on it (module_info/0 is a common thing to test). What the get is basically doing is just taking the atom you pass in for the module and the atom you pass in for the function (function names are just atoms too) and basically calling it like apply(moduleAtom, functionAtom, [conn, params]).
Simply calling NonexistentThing isn’t doing anything. It is just an atom. There is nothing to evaluate. It does not know that you are trying to reference a module. It is just like any other literal. You would not get an error if you replaced it with 4. If instead you tried using NonexistentThing.foo, you should get an error stating that it does not exist.
On the other hand, nonexistentThing is trying to reference a variable that does not exist.
So it could be seen as a language syntax issue. I think it’d be hugely beneficial to get warnings for mis-spelled symbols. I hadn’t realized that an uppercase “bare word” is accepted as a valid expression.
The problem with this syntax is that it would introduce a compile-time dependency on the HelloController module. You don’t want all of your controllers recompiled when you change the router.
When I found anew statically typed languages (at least for me, I started with them ~13y ago) like for example Rust or Elm (just examples), I was really, really hard asking myself why would I ever go back to dynamically typed languages. And what really is keeping my with Elixir (aside from community etc.) is how easily you can write truly concurrent and fault tolerant software.
Although Dialyzer won’t find that you have non existing module name in your path definition get("/hello", IdontExist, :index) i still think that mentioning about Dialyzer might help you with some problems you may ecounter.
Precisely the same with me, erlang was the only really big dynamically typed language I used before (bits of python at times), elixir mostly replaced erlang due to it’s macros and ecosystem (I still prefer erlang’s syntax overall).
It’s only a positive typer though, so it can catch egregiously wrong uses pretty often, but it defers to assuming that what the user wrote was correct.
And this is one of those cases. It only knows that an atom is being passed in, it doesn’t know that it needs to be a valid module as one example.
It’s not only an issue of static typing, though. Late binding means that the module can be introduced at run-time at any point. Even if you did somehow say “Yeah, this is a module and I know that”, it seems to me you’d have the issue of basically saying “No, I’m going to force all modules to be defined at compile time and they should remain the same”, which may or may not be desirable.
The current situation is “I’m gonna call whatever I have and what exists at that moment is what I’m going to get”, which is about as far from forcing all modules to exist and stay static at compile-time as you can get.
Well releases on the BEAM do atomic updates of a set of modules at a time, and different processes inside the BEAM can be running 2 different versions of the code, so as long as messages are block-boxed then it would work fine in 99% of cases (and in the rest then you should know what you are doing).
Is this philosophy reflected in other areas of the Phoenix ecosystem like Ecto? I’m wondering if the framework is a good fit for me and my projects: I want the compiler+linker to do as much work as possible, not less. I’m looking for a framework that checks anything that can be checked, without imposing coding or conceptual overhead.
E.g., Swift Vapor’s type safe routes appeal to me a lot, but the framework isn’t very mature at the moment.
Understood! But since I don’t know the libraries well, maybe you can weigh in: is this pattern of “soft references” common? - modules referred to by name?
It’s built in to the way the BEAM VM works. Anything one wants more than that needs to be done by whatever compiler they use or via other passes either before or after.