Easily in which sense? Have you tried to move them out? It is not just a matter of moving a file from a directory to the other.
For example, could inspect be a separate library? Sure, it could. But if we do so, how would Elixir core, which cannot depend on any external package, do something as simple as:
def some_function(other) do
raise ArgumentError, "expected an integer, got #{inspect(other)}"
end
The best we would be able to do is say “expected an integer, got something else” or we would have to reimplement the inspect functionality by hand.
What if you move a GenServer out? Then how would we implement the functionality in Elixir that relies on GenServers today?
So the answer why a lot of those things are in core is because building a reasonably useful application without them, including Elixir itself, would be very hard. Removing them is not easy because it means you would have to implement some good chunks of them to implement Elixir’s core itself. As others said, without ex_unit
we couldn’t even write tests for core.
So what about mix and eex? They are in Elixir because although they are not needed in Elixir itself, they are needed for Elixir developers. You want to start a new project? Well, you need mix. You want to compile a project? Well, you need mix as well. But they are not required, to the point those are typically stripped out when running your software in production. In fact, if you want to ditch Mix and maintain your whole project with a Makefile, you can do it (and what’s how we did it before Mix).
This leaves us with Logger and IEx. We probably wouldn’t have a Logger in Elixir if Erlang/OTP didn’t have a Logger. But Erlang/OTP does ship with a Logger, as part of its kernel/stdlib, and if we didn’t have a Logger in Elixir, then it means you would get error reports and log messages with everything as Erlang terms. So Logger is needed at least as a translation layer. Same thing for IEx. It comes as part of Erlang/OTP’s kernel/stdlib, so providing a shell that understands Elixir is quite reasonable and relatively low effort. If we didn’t have one in Erlang/OTP, maybe we wouldn’t have one in Elixir too (although I would consider IEx to be an essential development tool).
So you can think of this as an onion. Inside core, we have a bunch things that are essential for writing software and often they are dependent on each other. For example, if Enum depends on GenServer and GenServer depends on Enum, how can you compile them in the first place? This is common in all programming languages that are mostly implemented in terms of themselves, which is why such languages need to find a way to bootstrap themselves. Some languages even require a previous version of the language in order to compile the new version. If you don’t want this, then the alternative is to write your core in another language.
Then on top of this core, we have another layer with applications which are necessary for developers to bootstrap their own applications and run them in production with good user experience. That implies a build tool, interactive elixir, properly formatted log messages, etc. At this point, it is much easier to organize those parts into applications because the “core” is ready. We can use almost all conveniences any Elixir developer would, except by the build tool itself.
Then the third layer is the one provided by Hex (which is not part of core) and the community.
If you really want to take the idea of a minimal core to the extreme, you can try this: in your next project, you can only use Kernel.SpecialForms
. Nothing more. You will see that, in order to get anything meaningful done, you will have to reimplement a good chunk of Elixir’s core.