If you could change one thing in Elixir language, what you would change?

Erlang already has a syntax for this, the ? prefix. i.e. ?assert(Value). I like the idea of keeping close to Erlang where possible

3 Likes

Long time reader, first time poster, and by no means an expert, but I’d like to have something that amounts to a a long running “Task”.

I suppose what I’m thinking of is essentially a Genserver, but sometimes, the idea of writing and wiring up a Genserver for (as an example) a persistent DB connect, feels like overkill.

What I’m thinking of, is probably an Agent (facepalm)…

But if that’s the case, I’d suggest that the Agent module probably needs a name change, or more explicit documentation and some better examples.

Didn’t know about the ? prefix on erlang, Not sure if that’s possible though, since ? prefix is already used for chars:

iex(1)> ?a
97

I was actually not even aware erlang had macros, does it?

1 Like

So GenServer is the most generic long running server you have available, so it’s understandable it’s not very focused and might need boilerplate for some tasks. Agent and Tasks are more specialized processes, where Agent is for keeping data around and Tasks are for doing stuff, which does not require sticking around for longer than the task needs. There are also specialized processes from 3rd parties like GenStage or DBConnection. So if you have a need for a more specialized server you could build it on top of a generic genserver.

2 Likes

Test public functions. Yeah I know it is not supposed to be tested but you end up messing with TDD and forces what could be a single public function into multiple-ones in order to have them tested. Moving that to a separated module just to test them without exposing to the API is very boring and very repetitive.

You mean private right?

I do actually like this because it forces me to separate the public API from the private one by have a module for each one. A feature related to that I’m eager to see is the @module_visible_to discussed here: Proposal: Private modules (general discussion)

1 Like

Yes, Private!

The problem is when you only have to do simple tasks.

Imagine that you have are writing an adapter that needs to requests the CSV, parse and then transform one of the columns that have a string like “Technology > Programming > Elixir” into a nested map.

Your adapter only need to have a single public function call like “fetch_categories/0” but for testing (mainly TDD) you want to test each piece in isolation. So you end-up needing to create a separated module just to test those functions even if they were never supposedly to be reused. The compiler could just ignore the privacy in the test environment and made life easier.

1 Like

I think that EUnit approach there is quite good one, where you can define test functions within module itself, so you have possibility to call all private functions and unit-test them, ex.:

-module(foo).

-export([foo/1]).

foo(Num) -> bar(Num, 5).

bar(A, B) -> A + B.

-ifdef(TEST).

-include_lib("eunit/include/eunit.hrl").
-include_lib("kernel/include/assertions.hrl").

bar_test() ->
  ?assertEqual(2, bar(1, 1)).

-endif.
2 Likes

Pretty interesting! I saw some people doing this on other languages, and you’re right, it totally works to test private funcs. But I think it also could be implemented as a library, maybe I take some time to do a PoC on that! :smiley:

That’s more similar to C preprocessor macro’s then AST macro’s though. Erlang’s form of AST macro’s are parse-transforms, which don’t require any prefix or suffix (and, well, they can arbitrarily change any ast form).

C Preprocessor style macro’s, not lispy/elixiry macros.

A common pattern I did in Erlang was to add a test function to each module to test internal things, and that would be called by the main test scripts. The test function was not compiled in at all in a non-testing environment. I actually quite liked the tests being in-file with the code.

Yep, like that.

1 Like

Done PoC it provides one macro unit_tests that allows you to define tests that are available only when Mix.env() == :test (however this is configurable).

Example from README:

defmodule Foo do
  import ExUnitEmbedded

  defp foo, do: :ok

  unit_tests do
    test "foo/0 returns :ok" do
      assert :ok = foo()
    end
  end
end

Then in your test module invoke Foo.ex_unit_register/0:

defmodule FooTest do
  use ExUnit.Case

  Foo.ex_unit_register()
end

Changed API a little bit:

defmodule Foo do
  use ExUnitEmbedded

  defp foo, do: :ok

  test "foo/0 returns :ok" do
    assert :ok = foo()
  end
end

Then in your test module invoke unittest Foo:

defmodule FooTest do
  use ExUnit.Case
  import ExUnitEmbedded

  unittest ExUnitEmbedded
end
5 Likes

Your adapter only need to have a single public function call like “fetch_categories/0” but for testing (mainly TDD) you want to test each piece in isolation. So you end-up needing to create a separated module just to test those functions even if they were never supposedly to be reused. The compiler could just ignore the privacy in the test environment and made life easier.

GitHub - wistia/ex_ex: A collection of functions that extend the capabilities of Elixir's standard library modules includes a macro called defp_testable which expands to def if the Mix env is :test, and defp otherwise. Maybe my “one thing” would be changing the standard definition of defp to act like this. :smile:

2 Likes

Technically macros and functions are different, syntactically they are the same, otherwise the parse would have to know which is a macro and which is a function.

The best solution is to always have parentheses but a have a better syntax for using them in both macros and functions. A better syntax like lisp. :wink:

6 Likes

I wish we could define any operator we want

1 Like

If you haven’t seen the recent updates to Dialyxir via transformations of the error messages by my Erlex library, I encourage you to seek them out. We now control all error messages, so any PRs to address deficiencies in language should be straightforward to add and not require any Erlang knowledge. If you have an editor’s hand, please consider doing so =)

If it fails to transform the message, it falls back to the Erlang output, so there’s no reason to not use it, if you’re already using Dialyzer.

Additionally, there’s a --explain flag that allows you to see what a particular error class would mean. We do not get enough information from Dialyzer currently to be able to give you an explanation of the specific error, but we can tell you what no_return means and give an example of what would cause that type of error. Warnings are listable by calling Dialyxir.Warnings.warnings/0.

11 Likes

Ok, so it make sense.
Thank u for answer

I’ll check it out, thanks!

This might go against the grain, but I’d love some OOP. I suppose in Elixir this is done through “processes”. Still trying to wrap my head around functional. I’ve found a hybrid approach provides much more flexibility.

Should I be looking at processes for this? I’ve been able to simplify a great deal through functional approaches.

That plus static typing (though we are 25% of the way there through dialyzer). Sadly, doesn’t look like either will make an appearance due to nature of underlying architecture.

https://www.theerlangelist.com/article/spawn_or_not

This is a good read to help wrap your head around the concepts of modeling with processes.

5 Likes

Appreciate it!