[HN Gopher] ElixirNitpicks
       ___________________________________________________________________
        
       ElixirNitpicks
        
       Author : thunderbong
       Score  : 88 points
       Date   : 2024-02-07 10:53 UTC (12 hours ago)
        
 (HTM) web link (wiki.alopex.li)
 (TXT) w3m dump (wiki.alopex.li)
        
       | xrd wrote:
       | Interestingly, though I am not a great Rust programmer, nor a
       | great Elixir programmer (I have written programs in both
       | languages). And, gotten a little ways through the amazing
       | Exercism (https://exercism.org/tracks/elixir).
       | 
       | What I loved about this writeup was the way I could review both
       | by comparing two languages in which I have a moderate
       | understanding. The comparison of imports was very engaging to my
       | brain.
       | 
       | I really feel like I could accelerate learning a new language if
       | the tutorial was written by comparing that language to another
       | language I already know. Partially I just think that would keep
       | my interest. I'm never excited when I start learning a new
       | language and read the description of how to import a module in
       | that new language; my brain would say "doh, I'll review this when
       | I actually start writing code, let me zone out here..." I feel
       | like most language tutorials feel obligated to cover this
       | minutiae and there is always a lot to review when you are writing
       | a tutorial. People would probably get upset if you skipped that
       | stuff, but for me as a reader, I really prefer this approach
       | here.
        
         | jddj wrote:
         | https://www.languagetransfer.org/ tries to do this with spoken
         | languages
        
         | ljaothasdfs wrote:
         | This is why a survey in programming languages is beneficial.
         | You want a variety to fill in the gaps and this increases
         | capacity to learn new ones but you are also easily able to
         | identify short comings and foot guns where people who are
         | siloed to a single language could never be aware of which leads
         | to instances of NIH syndrome like we've seen in Python & Go.
        
         | drewbug01 wrote:
         | There was something like this a few years ago, "Elixir for
         | Rubyists": https://thoughtbot.com/blog/elixir-for-rubyists
        
           | arrowsmith wrote:
           | I wrote something similar here:
           | 
           | https://phoenixonrails.com/blog/elixir-for-ruby-
           | developers-t...
           | 
           | https://phoenixonrails.com/blog/elixir-syntax-for-ruby-
           | devel...
        
             | freedomben wrote:
             | _Disclaimer: This is my talk /video_
             | 
             | If you like the presentation format rather than written
             | format, I did what was called by one reviewer "a
             | delightfully thorough" intro to Elixir for Rubyists:
             | https://www.youtube.com/watch?v=uPWMBDTPMkQ
        
           | mike1o1 wrote:
           | I'm not the author of the book, but I bought and enjoyed it
           | quite a bit.
           | 
           | https://pragprog.com/titles/sbelixir/from-ruby-to-elixir/
           | 
           | From Ruby to Elixir, by Stephen Bussey. He's also written a
           | book named Real-time Phoenix which I also found pretty
           | helpful.
        
         | namaria wrote:
         | You bring up something interesting. Structured learning is very
         | helpful but I sometimes hate that I can't or feel I shouldn't
         | skip around. Videos are the worst because it's very linear. I'd
         | love to be able to see abstract concepts such as the
         | interpreter/compiler for a language and it's syntax and
         | important concepts and models etc the way you can just turn a
         | 3d model around in a CAD system. That would be truly amazing.
         | Let me explore it with my visual cortex, instead of of the need
         | to follow along some story I'm not really interested in.
        
           | tomjakubowski wrote:
           | Edward Tufte advocates handouts over slides in presentations
           | for just this reason. Your audience can refer back to the
           | written and illustrated content of your presentation at their
           | leisure.
        
         | killthebuddha wrote:
         | FWIW this is the #1 best use case I've found for ChatGPT. I
         | recently started learning golang and as an experiment decided
         | to use nothing but ChatGPT for reference/documentation. So far,
         | ChatGPT has satisfied roughly 100% of my questions. A lot of
         | the questions were like "what's the go equivalent of X in
         | JS/TS?".
         | 
         | The returns seem to be diminishing after ~6 weeks, but the
         | first 2-3 weeks were the least friction I've ever encountered
         | learning basically any new topic. It was kind of wild, I don't
         | remember getting stuck even a single time, it was just super
         | high-density progress, non-stop.
         | 
         | More generally, the situation where "I have a good amount of
         | generic knowledge, so I know what I want, but I don't have the
         | specific knowledge I need" seems to be a sweet spot for
         | ChatGPT.
        
       | pmontra wrote:
       | I add my own nitpick about the with statement.
       | 
       | If I start with this code                 true =
       | is_email_address?(email)       true =
       | EmailAddresses.is_available(email)
       | 
       | and I decide to turn it into a with like this
       | with true <- is_email_address?(email),            true <-
       | EmailAddresses.is_available(email) do
       | 
       | then I have to go line by line replacing = with <- and adding a
       | comma at end of line. Sometimes there is a long list of lines to
       | wrap. Surely it can be automated by an IDE or a macro of the
       | editor. Anyway it's still a bad choice IMHO.
       | 
       | I understand why with could not work with the = matching operator
       | (it's a macro) but that's about language design, ergonomics and
       | in part not making developers do the job of the compiler.
        
         | pentacent_hq wrote:
         | Your two examples aren't quite equivalent.                 true
         | = false
         | 
         | raises a match error while                 true <- false
         | 
         | in a with statement doesn't.
         | 
         | You can actually use true = false in your with statement, but
         | it will still raise the match error.
        
         | arrowsmith wrote:
         | > I understand why with could not work with the = matching
         | operator (it's a macro)
         | 
         | Not sure what you mean - `with` _can_ use the `=` operator,
         | e.g. you could write:                     with true =
         | is_email_address?(email),              true =
         | EmailAddresses.is_available(email) do             # something
         | end
         | 
         | Although this is rather pointless as it's functionally
         | equivalent to just writing:                    true =
         | is_email_address?(email)          true =
         | EmailAddresses.is_available(email)          # something
         | 
         | `=` is useful inside `with` if you have a clause that _must_
         | return a particular value or it's an error, i.e. you don't need
         | to match on any other possible return value. But if you don't
         | have at least one clause with `<-` then `with` achieves
         | nothing.
         | 
         | Also, why would the fact that `with` is a macro mean that it
         | can't use `=`?
        
       | salzig wrote:
       | For me it would be the Pin-Operator. Which is only needed cause
       | variables can "mutate". IMHO it's not that common that we need to
       | "reassign" variables, we could life without the looks-like-
       | reassignment.
       | 
       | I touched Erlang before, it's hard to get my brain to accept
       | elixir is different in regards of variables :)
        
         | weatherlight wrote:
         | iex(1)> a=10         10         iex(2)> a=11         11
         | 
         | Because underneath, it's doing                   A0 = 10.
         | A1 = 11.
         | 
         | You might not like it, because it feels like mutation, but it's
         | not, it's rebinding. Just consider this as a syntactic sugar.
         | It's useful when doing                   conn = conn |>
         | apply_some_change()
         | 
         | In the end, it does generate valid bytecode for the BEAM, and
         | immutability is respected.
         | 
         | BTW You might prefer Erlang syntax, but You would lose |>
         | 
         | Jose Valim, has a great writeup about this here.
         | 
         | https://blog.plataformatec.com.br/2016/01/comparing-elixir-a...
        
           | killthebuddha wrote:
           | Genuine question: From an application developer's
           | perspective, what's the difference between mutating a value
           | and transparently rebinding an old name to a new value? Is it
           | just that in the latter case other references don't pick up
           | the changes? So with rebinding we don't have something like
           | a = 10       b = a       a = 11       print(b) // 11
           | ?
        
             | OkayPhysicist wrote:
             | def func() do            a = 10            def func2() do
             | a + 1            end            a = 20            func2()
             | end
             | 
             | Mutation would have func() return 21. Rebinding has it
             | return 11.
             | 
             | Likewise, mutating languages typically allow for a method
             | to modify its arguments when those arguments are objects
             | public void modify(MyObject a){              a.changed =
             | true;         }              public bool test_modify(){
             | MyObject b = new MyObject();              b.changed =
             | false;              modify(b);              return
             | b.changed         }
             | 
             | test_modify will return true in languages that allow
             | mutation.
        
               | Miner49er wrote:
               | This is a great example of why the pin-operator is bad,
               | IMO. Rebinding isn't worth this complication.
        
               | OkayPhysicist wrote:
               | On the contrary. I'd like the pin operator regardless of
               | whether rebinding is allowed or not.
               | def func() do           s = g()           ...several
               | lines of code           l = t()           case l do
               | {s, q} -> #blah              {q,q} -> #blah
               | {p, r} -> #blah           end         end
               | 
               | Without the "^s", you need context to determine "s"'s
               | behavior. If s is currently unbound, then it'd be
               | assignment. If it's unbound, it'd be pattern matching.
               | It's a rare enough operation that it's nice to make it
               | explicit.
               | 
               | Erlang didn't have rebinding, and Elixir learned from the
               | mistake. Rebinding is not complicated: the scope rules
               | are simple, you learn them and then you're done. It's Day
               | 1 type stuff, which is absolutely not worth optimizing
               | for if you're trying to build a useful language. Without
               | them, you end up coming up with a bunch of bogus variable
               | names that don't add anything to the conversation.
               | Imagine modifying an entry in a struct (or, more exactly,
               | you're making a copy of a struct with one value changed)
               | 
               | With rebinding, it's easy:                   def
               | func(dict) do           dict = dict |> Map.put("apple",
               | 1)         end
               | 
               | Without rebinding, you need a pointless new variable
               | name:                   def func(dict) do
               | dict_with_one_apple = dict |> Map.put("apple", 1)
               | end
        
         | marcandre wrote:
         | Interesting. In our codebase we do this all the time. A quick
         | search revealed 280 occurrences of `some_var = some_var |>
         | ...`.
         | 
         | I also find the pin operator much more readable, as the meaning
         | of `{foo, ^bar} = result` doesn't require to know the context.
         | `foo` is being assigned, `bar` is being matched on. No need to
         | know the code before this line to interpret it.
        
           | arrowsmith wrote:
           | Yes, good luck writing a non-trivial Phoenix and/or LiveView
           | app without ever writing `socket = something(socket, ...)` or
           | `assigns = assign(assigns, ...)`.
           | 
           | I do remember finding the pin operator confusing when I first
           | started learning Elixir, probably because I'd never seen
           | anything like it in another language. But the confusion
           | didn't last long; it's really not hard to understand. I've
           | never felt like the pin operator was bad for readability.
        
         | lolinder wrote:
         | Nitpick of the nitpick: name shadowing is one of the most
         | important QOL improvements a functional language can add. The
         | pipe operator can alleviate _some_ of the pain of lack of
         | shadowing, but sometimes you really do want to have a chain of
         | transformations to a value that don 't fit well as a pipe, such
         | as when there's more than one intermediate value being used in
         | parallel. In those situations, forcing programmers to give each
         | intermediate value a new name each time is pointless overhead
         | and doesn't actually improve "purity" in any meaningful way.
        
         | lvass wrote:
         | I love how pins make it explicit you are not assigning
         | something without having to look at the context. I prefer it
         | being there even if you couldn't shadow a variable.
        
       | mrcwinn wrote:
       | Nice write up. Thank you! Do not avoid LiveView - it is
       | excellent.
        
       | ch4s3 wrote:
       | > I do wish migrations were just generated from schemas though, a
       | la Django.
       | 
       | This is a weird one to me. I really dislike the magical way
       | Django does this and I'm glad Ecto doesn't. It also allows you to
       | have separate Ecto structs representing different parts of a
       | table in scenarios where that is desirable.
        
         | arrowsmith wrote:
         | Agreed, I hate the way Django does it. I don't want my database
         | migrations to be tightly coupled to my models - what's the
         | point?
         | 
         | I'm not sure that the Django approach would make sense in Ecto
         | anyway because Ecto schemas are explicitly _not_ "models". The
         | function of a Django/Rails "model" is divided between different
         | concepts like schemas, Ecto.Query, Ecto.Changeset, the Repo
         | etc.
        
           | freedomben wrote:
           | Same. I was annoyed at first with what felt like duplication
           | (and indeed is duplication for the first time the table is
           | created), but over time the Ecto/Elixir method really showed
           | it's merit and is IMHO the cleary superior way for a
           | maintainable long-term application. The magic and the
           | convenience in the short term it brings are not worth it. The
           | straight forward, explicit and crystal clear declarative way
           | for migrations and the schema are IMHO a joy.
        
         | bmelton wrote:
         | Ash Framework gives you the ability to define the schema in
         | code, via                   mix
         | ash_postgres.generate_migrations --name name_of_my_migration
         | 
         | but of course now you're using a framework (albeit a really
         | good one) that is perhaps a bit less mature than Elixir
        
           | ch4s3 wrote:
           | I'm aware of that, and can see why people like it in Ash, but
           | as with Django I think it makes things less explicit, harder
           | to break up domain specific structures backed by the
           | database, and more magical. It's fine IMHO as an add on with
           | something like Ash, but a data mapper like Ecto shouldn't
           | strive to be an ORM like Django especially in a functional
           | language.
        
         | sergiotapia wrote:
         | Prisma does the same thing and it sucks to me. Always a pain to
         | figure out what happened when things don't work as you expect
         | them to.
        
         | conradfr wrote:
         | I mean that could just be a mix task that you would be free to
         | use or not.
         | 
         | Currently it's true (for me) that writing the migration is copy
         | pasting the fields from the ecto schemas and modifying them
         | because the dsl is "almost the same but not totally".
        
       | pg_bot wrote:
       | The following code could be written much better by using the cond
       | operator.                   with {:is_email, true} <- {:is_email,
       | is_email_address?(email)},              {:is_available, true} <-
       | {:is_available, EmailAddresses.is_available(email)} do
       | ...         else           {:is_email, false} ->
       | {:error, :bad_request}                {:is_available, false} ->
       | {:error, :conflict}         end              cond do
       | !email_address?(email) -> {:error, :bad_request}
       | !EmailAddresses.available?(email) -> {:error, :conflict}
       | true -> {:ok, email}         end
       | 
       | This gets rid of unnecessary duplication, and I think is easier
       | to understand.
        
         | regulation_d wrote:
         | i agree that with statements aren't the easiest to understand,
         | especially for a beginner, but I think the value in having the
         | entirety of the happy path in the initial block is very helpful
         | for understanding the flow of the feature, once you grok the
         | syntax.
        
         | ollysb wrote:
         | Keathley did a good job discussing this in
         | https://keathley.io/blog/good-and-bad-
         | elixir.html#:~:text=Av.... The preferred style is to specify
         | the errors in separate functions e.g.                   def
         | main do           with {:ok, response} <- call_service(data),
         | {:ok, decoded}  <- decode(response),                {:ok,
         | result}   <- store_in_db(decoded) do             :ok
         | end         end
         | 
         | where call_service, decode and store_in_db return the specific
         | errors like {:error, :bad_request}, {:error, :conflict}.
        
           | karmakaze wrote:
           | Isn't there a way of piping the ok results through? e.g.
           | data       |> call_service       |> an_ok_unwrapper(decode)
           | |> an_ok_unwrapper(store_in_db)
           | 
           | Or is `with` the way of doing this for ok/error results?
        
             | karmajunkie wrote:
             | `with` is more widely used, i think, but there's also
             | `Kernel.then/2`:                   data         |>
             | call_service         |> then(fn {:ok, decode} ->
             | decode_file(decode) end)         |> then(fn {:ok, file} ->
             | store_file(file) end)
             | 
             | your solution also works if you define the function headers
             | with a pattern match against the tuple, but then you have
             | this extra function hanging around. Feels like a style
             | thing more than anything else.
        
               | xanthor wrote:
               | This approach is not equivalent since it uses a strict
               | match in the function head inside `then`. It will raise a
               | `FunctionClauseError` if a value not matching `{:ok, _}`
               | is passed in.
        
           | dc0d wrote:
           | Thank you for the reference (not finished it yet).
           | 
           | Worth mentioning functions that can error, should follow the
           | {:ok, response} | {:error, reason} pattern. Because if such a
           | function returns response | {:error, reason}, then if we are
           | inside a with clause and we want to capture the response and
           | use it in the next with clause, such capture value can be
           | either response or {:error, reason} - which goes around the
           | pattern matching.                 with response_f1 <- f1(),
           | {:ok, response_f2} <- f2(response_f1) do         # do
           | something       else         {:error, reason_f1} ->
           | # we will never come here           # because the returned
           | value from f1           # is already matched to the variable
           | response_f1        end
        
       | marcandre wrote:
       | A nitpick of mine is how filtering with `for` is not explicit.
       | arg = [1, 2, 3]       # This doesn't crash:       for {key,
       | value} <- arg, do: ...       # But this will:       Enum.map(arg,
       | fn {key, value} -> ... end)
        
         | denvaar wrote:
         | That's interesting, I never noticed that subtlety. I think the
         | docs for `for` kind of get at that:
         | 
         | > Generators can also be used to filter as it removes any value
         | that doesn't match the pattern on the left side of <-
        
       | __jonas wrote:
       | > Unit tests are more of a pain
       | 
       | > ...though I really have a hard time expressing why.
       | 
       | This one is interesting, I'm new to Elixir an I really enjoy
       | writing tests for my Elixir app, in a way I kind of never have
       | before.
       | 
       | This might just be an effect of the ecosystem being built more
       | with testing in mind than I would have expected. For instance
       | when I wanted to add email sending to my Phoenix App, the Swoosh
       | library came with a Test adapter and an assertion to use in tests
       | for it. I would have normally expected to have to write some mock
       | of the email library or use some testing SMTP service, but it was
       | so surprisingly painless instead.
       | 
       | Same with testing functions that interact with the database in
       | Ecto, I feel like the way to do this is well thought out as part
       | of Ecto and I never have to work around any testing-specific
       | issues.
        
       | dc0d wrote:
       | > There's a cultural split between Erlang and Elixir that makes
       | life harder than it needs to be.
       | 
       | On that note I have witnessed: "Finding Erlang developers was
       | hard. And I hated Elixir. So, we ported everything to Java."
       | 
       | Maybe that was just one such occasion. Maybe there are more.
        
       | eclark wrote:
       | Testing is one of the areas that we have felt the most in Elixir
       | while building Batteries Included. ExUnit is pretty good, but
       | bare bones. That combined with Phoenix (most popular web
       | framework in elixir) made for some places we didn't test. So we
       | created a test library that does polaroid snapshot testing of
       | Phoenix components. We called it Heyya and added other utilities
       | to test phoenix live view too.
       | 
       | Does anyone have solutions for Ecto testing with processes?
       | 
       | - https://www.batteriesincl.com/ - https://github.com/batteries-
       | included/heyya - https://hex.pm/packages/heyya
        
         | cpursley wrote:
         | What do you mean testing with processes?
         | 
         | I won't suggest these are the best written tests, but I test
         | various processes, supervisors, etc like this:
         | 
         | -
         | https://github.com/cpursley/walex/blob/master/test/walex/sup...
         | 
         | -
         | https://github.com/cpursley/walex/blob/e13a9cbf9aca1a2a2d4ed...
        
           | eclark wrote:
           | If have an `Application` that starts processes that cache
           | ecto state, or periodically write to db, etc, it's hard to
           | test the whole process tree. Additionally Umbrella projects
           | start all applications in the project with one config. All of
           | those combined mean you have to change your code structure
           | quite a lot to make that testing possible.
           | 
           | Testing single processes is pretty easy. Making sure that two
           | processes and ecto can tolerate failures, delays, etc
           | requires too much.
        
       | aeturnum wrote:
       | I fully agree about how hard it is to understand what the DSL
       | features of elixir are doing. It was a major pain point for me
       | and I wish that Elixir had a mode where it would expand all
       | macros and output the resulting code so you can just see what
       | your code looks like. I think that would help a lot.
       | 
       | On the flip side, most of these complaints feel like the author
       | is fighting the language and wants it to behave more like a
       | language that makes different tradeoffs. Maybe akin to
       | complaining that Rust is annoying because "you need to use unsafe
       | to code normally."
       | 
       | For example pairing with statements with specific atoms is
       | specifically called out as an anti-pattern in the latest elixir
       | docs[1] (this is an example of the elixir devs trying to talk
       | more about how best to do things!). I don't mean say that the
       | author "should have known not to do that" - I use it from time to
       | time. I mean that the language is not setup to support this code
       | structure well. The language wants you do use different
       | structures for doing a series of things (the Ecto approach of
       | returning a structure with the error embedded in it is an
       | example).
       | 
       | I'm also sympathetic about the confusion around umbrella projects
       | (and other such features) but I kind of feel like the current
       | state is the optimal one? There's a tutorial showing you when you
       | might want an umbrella project, but also showing you a similar
       | structure that avoids using one[2]. On some level choosing which
       | approach you want depends on a ton of details about "how elixir /
       | erlang works"...but that seems pretty unavoidable? I think
       | umbrella projects make very little sense until you grok the
       | application system - but ultimately you can't save people from
       | skimming over why they might use something and diving into
       | learning it. I think this is just an inevitable drawback of
       | languages that have fundamentally different tradeoffs than ones
       | that generate native code with mutability.
       | 
       | [1] https://hexdocs.pm/elixir/main/code-anti-
       | patterns.html#compl...
       | 
       | [2] https://hexdocs.pm/elixir/dependencies-and-umbrella-
       | projects...
        
       | foldr wrote:
       | Glad I'm not the only one who doesn't get Ecto.Multi. I can see
       | how it's useful in theory in some cases, but I've always found it
       | much easier just to use Repo.transaction.
        
         | ch4s3 wrote:
         | I think Multi could use some improvements but it has its uses
         | distinct from transactions especially around he ability to do
         | introspection.
        
       | IceDragon200 wrote:
       | Well, I had a comment, but was apparently too long, so I've
       | placed it into a gist for now
       | https://gist.github.com/IceDragon200/b71cafd052ee03f65d1cadc...
       | 
       | For the email validation I would have used an ecto schema, since
       | most cases you won't just be validating an email address in
       | isolation:                 defmodule EmailSchema do          use
       | Ecto.Schema              import Ecto.Changeset
       | @primary_key false              embedded_schema do            #
       | here is your type validation right off the bat           field
       | :email, :string         end              def validate(email) do
       | %__MODULE__{}           |> cast(params, [             :email,
       | ])           |> validate_required([             :email,
       | ])           |> validate_change(:email, fn :email, value ->
       | cond do               not is_email_address?(value) ->
       | [email: {"invalid email address", [validation: :email]}]
       | not EmailAddresses.is_available?(value) ->
       | [email: {"is unavailable", [validation: :email]}]
       | true ->                    []             end           end)
       | |> apply_action(:insert)         end       end            case
       | EmailSchema.validate(email) do          {:ok, %{email: email}} ->
       | {:error, %Ecto.Changeset{} = changeset} ->
       | changeset.errors[:email]            # Can be all of these in the
       | same list, or be any one depending on the validations
       | #=> [{"is required", [validation: :required]}]           #=>
       | [{"invalid email address", [validation: :email]}]           #=>
       | [{"is unavailable", [validation: :email]}]       end
        
       | kipcole9 wrote:
       | > Lots of other tools are a bit short of the "first-class" level
       | of polish; Image/Vix
       | 
       | I'm the author of Image and I'd welcome feedback on improving
       | areas where you see lack of polish (there's a reason its not yet
       | 1.0, but it is getting closer - primarily rewriting the color
       | model).
       | 
       | Comments here are fine, or in the repo at
       | https://github.com/kipcole9/image
        
       | josevalim wrote:
       | I enjoyed reading the previous articles, so I was excited to read
       | this one too. I really appreciate this style of feedback.
       | Comments below.
       | 
       | -------
       | 
       | ERROR HANDLING
       | 
       | In my opinion, the "with {:is_email, true}" style is missing the
       | forest for the trees. The whole point of "with" is to match on a
       | consistent result. If you need to distinguish individual clauses,
       | then you should either use case/cond or normalize the result
       | types, in the same way you would do in Rust. So in your case I'd
       | add two functions: "validate_email" and
       | "check_email_availability" that returns ":ok" or "{:error,
       | reason}". Then you end-up with:                   with :ok <-
       | validate_email(email),              :ok <-
       | check_email_availability(email) do           ...         end
       | 
       | We give similar examples in our anti-patterns docs:
       | https://hexdocs.pm/elixir/main/code-anti-patterns.html#compl...
       | 
       | -------
       | 
       | STATE MANAGEMENT
       | 
       | Generally agreed. Just one nit:
       | 
       | > Sure the state is all encapsulated into processes, but then
       | those processes are hidden behind an abstraction layer that makes
       | them invisible, so really you're just touching global variables.
       | 
       | They are not invisible. You can use Observer, the Phoenix Live
       | Dashboard, and many other tools to traverse, explore, and
       | navigate the supervision tree, processes, and see where the state
       | is!
       | 
       | -------
       | 
       | IMPORTS
       | 
       | Agreed. We had several discussions on how to improve this but
       | nothing satisfactory. Maybe it is time for another tango.
       | 
       | -------
       | 
       | MIXED MESSAGES
       | 
       | I'd say we actually do a good job on the official docs on the
       | topics that are directly related to Elixir:
       | 
       | * On umbrella projects, the official guide discusses trade-offs:
       | https://hexdocs.pm/elixir/dependencies-and-umbrella-projects...
       | 
       | * Live upgrades are covered in our release docs:
       | https://hexdocs.pm/mix/Mix.Tasks.Release.html#module-hot-cod...
       | 
       | * On macros: https://hexdocs.pm/elixir/macro-anti-
       | patterns.html#unnecessa...
       | 
       | The trouble is in finding this information, as it can be a lot to
       | absorb. If anyone finds we should link to them from other places,
       | pull requests are welcome. In general, PRs to improve docs are
       | always gladly received, be in Elixir, Ecto, or elsewhere!
       | 
       | -------
       | 
       | OTHERS
       | 
       | > Anecdotally, when Elixir started off there was some bad blood
       | between them and the Erlang community, which is the origin of
       | this schism
       | 
       | No bad blood, really. I asked the Rebar team (not the current
       | Rebar3 team) if they would accept PRs to also compile Elixir,
       | they said no (which is understandable) and then we move forward
       | with Mix (which was a contribution from a Clojure developer
       | inspired by Lein). The projects drifted apart but we often share
       | whatever we can in other places (such as
       | https://github.com/hexpm/hex_core).
       | 
       | > In fact the Elixir compiler almost never gives you an outright
       | error, basically it only fails if a file can't be parsed. This
       | feels spooky as hell... but its warnings are basically always
       | correct and seldom miss anything
       | 
       | Yes! Our goal is to avoid halting compilation as much as possible
       | and instead rely on precise warnings. It is easier to debug a
       | program that compiles (and then raises) than one that does not
       | compile at all.
       | 
       | If you ever get to what is bothering you on unit tests, I'd love
       | to hear (feel free to reach out).
        
       ___________________________________________________________________
       (page generated 2024-02-07 23:01 UTC)