Erlang/OTP 21 [erts-10.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
Interactive Elixir (1.6.5) - press Ctrl+C to exit (type h() ENTER for help)
As you have mentioned, yes I am not running the latest Elixir build - I am sorry about that, I will try to figure out how to get Elixir updated. In the meantime I have a few things to say :
I have watched a lot of your talks and would like to say you are doing a brilliant job !
I do not know how to thank you enough for trying to bring Erlang/OTP to the 21st Century, you must have invested a lot of your time and software needs more people like you.
It’s doubly impressive since your project is competing with the likes of golang which has strong financial backing - so you are arguably winning because of pure merit.
However I am having a tough time understanding Elixir, believe it or not I found Erlang easier to understand and get started compared to Elixir !
A few specific quirks (I hope you do not mind constructive criticism ) :
use ExUnit.Case
doctest KV
test "greets the world" do
assert KV.hello() == :world
end
where is test,assert coming from ? is it being imported from ExUnit.Case ?
.. but in your own docs it states:
Since use allows any code to run, we can’t really know the side-effects
of using a module without reading its documentation. For this reason,
import and alias are often preferred, as their semantics are defined by
the language.
I strongly agree with this statement - it reminds me of Python’s:
import * from numpy
which we can all agree is not a good idea, it completely threw me off as a beginner.
In nodejs you do not have to learn how to do TDD before trying to figure out how nodejs works, I understand the immense value of TDD and no serious application in 2018 should release without doing TDD - but if you are a beginner its going to be another extra hill you need to climb.
Imagine this - I have worked with C,C++,nodejs,Lua,haskell and its still quite difficult to see how Elixir is working - imagine a first year Computer Science student is it not a goal
to show them the beauty of Elixir first ?
For example in nodejs the bare bone project has :
node_modules
package.json
main.js
in Elixir its :
_build
config
lib
mix.exs
README.md
test
and my code entry point is lib/foo.exs ? How do I know that ? where is it being made explicit ?
A first year CS student wont know the importance of README.md how it relates to git (what is git ?) and how README.md is used by github.com, what is markdown again ?
Imagine if Elixir just had mix.exs and an entry point file made explicit in mix.exs.
Why is config not specified in mix.exs ?
I am sorry, I might just not have enough knowledge regarding why those decisions were made - but I felt I had to let my opinions be know so that Elixir can truly compete with with nodejs in terms of ease of use. I truly want Elixir to take off in a big way.
Looks like output from SASL. You may want to check whether there is anything in your config/config.exs that could be interfering with the default Logger setup (Error logger configuration).
where is test , assert coming from ? is it being imported from ExUnit.Case ?
Due to some incompatibilities regarding to how file handling works between OTP 20 and 21, elixir up to 1.6.5 does not work properly with OTP 21. You need to update to 1.6.6 at least.
Why would you think that functions are being added to the global namespace? The used module can create functions (via macros) inside the module use-ing it.
use ExUnit.Case
use MyExternalTestingModule
doctest KV
test "greets the world" do
assert KV.hello() == :world
end
MyExternalTestingModule has functions test , describe , assert also defined - its going to result in conflicts. Its always an anti pattern regardless of language. Its even mentioned in the Elixir docs that its not a good idea to use use :
Since use allows any code to run, we can’t really know the side-effects
of using a module without reading its documentation. For this reason,
import and alias are often preferred, as their semantics are defined by
the language
Not following - MyExternalTestingModule essentially also acts as a namespace - so there is no polluting of the global namespace.
and my code entry point is lib/foo.exs ?
mix new kv --module KV
doesn’t even create the equivalent of a main. That is only created with the --supoption and application.ex is the equivalent to a main. But to understand it you need to understand processes and supervisors.
For note, this alone definitely shows that the beam vm is being though like it’s a normal compiled app, it’s not.
There isn’t just one entry point, but rather many, one per loaded application in the vm, and your program can have many applications, whether by you, dependencies you bring in, etc. The beam vm is more like an OS on its own so it is more like you are building a dedicated Linux distribution than a standalone program.
Elixir makes sure of it for you. If test can come from two places and you invoke it, then it won’t compile and you will have to either pick one or use the fully qualified name.
Elixir is lexically scoped. So every time you have a function that you don’t know from where it comes from, then it has to come from somewhere above in the same file. There is no mutable global namespace per-se (unless you consider the atom table but that’s a separate discussion). There is a global namespace in JS but I think using it is mostly discouraged nowadays too?
I do agree the generated project could be simpler but an actual project does require configuration, documentation and tests, and that’s the direction Mix is pushing you to. It is an important nudge, as I believe not following those practices is more harmful to the community in the long term. If the goal is to learn Elixir per-se, without the project scope, then you can use pure files and IEx, like the Getting Started does.
Some of your questions are also answered in the Mix & OTP guide, which seems to be the one you are following, maybe we could improve the guide somehow? Our hope is that the guide answers everything related to the generated application.
defmodule PlugEm do
require Logger
import Plug.Conn
# See plug_em/lib/plug_em/application.ex - children
# --- BEGIN --- for "service" process
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
type: :worker,
restart: :permanent,
shutdown: 500
}
end
def start_link(_arg),
do: Plug.Adapters.Cowboy2.http(PlugEm, [])
# --- END --- for service process
# --- BEGIN --- part for the (module) Plug spec
def init(options) do
options
end
def call(conn, _opts) do
Logger.info(info_text(conn))
conn
|> put_resp_content_type("text/plain")
|> send_resp(200,"You've hit \"#{hostname()}\"")
end
# --- END --- for the (module) Plug
def hostname do
{:ok, hostname} = :inet.gethostname()
hostname
end
def remote_address(conn) do
case conn.remote_ip do
{_, _, _, _} = address ->
address
|> Tuple.to_list()
|> Enum.join(".")
_ ->
~S("unknown")
end
end
def path(conn),
do: conn.request_path
def info_text(conn),
do: ~s(Received request from "#{remote_address(conn)}" for "#{path(conn)}")
end
and finally adjust lib\plug_em\application.ex - the “main”:
defmodule PlugEm.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Starts a worker by calling: PlugEm.Worker.start_link(arg)
# {PlugEm.Worker, arg},
{PlugEm, []},
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: PlugEm.Supervisor]
Supervisor.start_link(children, opts)
end
end
the important bit being:
{PlugEm, []},
This causes the supervisor to start up a new process which is initialized with PlugEm.start_link/1 - which causes
Plug.Adapters.Cowboy2.http(PlugEm, [])
to run which causes the Plug (as implemented by PlugEm.init/1 and PlugEm.call/2) to be installed to serve requests.
You shouldn’t have a single *.exs in your lib folder when writing a mix-project. Well, except you really know what you are doing.
exs files are not compiled by mix at all unless you change the default settings.
In elixirs mix projects you can simply do mix run -e "App.run()", but thats usually for one-offs.
If you do want to do it correctly, you specify mod: {App, []} in your application/0 in the mix.exs. Also you should kick of the creation of a proper supervisiontree in that entrypoint and nothing else.
Thanks everybody for all the input ! I appreciate all the help.
I have received a lot of things to mull over, thanks for that !
I hope you can understand the frustration and growing pains a beginner in the Elixir community might face, You guys are already addressing it ! so its quite positive
Just a final question.
How to do hot reload on file change in Elixir ?
mix compile takes way too long for coding - and we are talking about a bare app ! The only fast solution for Elixir (recompiling in Erlang or nodejs is not a problem since it happens in 1 second for a bare app)
seems to me something that does Erlang’s :
c("./myApp.erl").
but in Elixir, but only on modules that have changed.
cheers !
I have looked at Cortex and test.watch on hex.pm but I couldn’t make Cortex work, it crashes. Is there a built in solution ?