Live data from Hacker News

ElixirNitpicks

wiki.alopex.li

31–40 of 60 posts

Re: ElixirNitpicks

#31
post #26
post #15

The following code could be written much better by using the cond operator. with {:is_email, true} {: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.

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} where call_service, decode and store_in_db return the specific errors like {:error, :bad_request}, {:error, :conflict}.

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?

Re: ElixirNitpicks

#32
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.

Re: ElixirNitpicks

#33
post #26

Earlier quoted context omitted.

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} where call_service, decode and store_in_db return the specific errors like {:error, :bad_request}, {:error, :conflict}.

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?

`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.

Re: ElixirNitpicks

#34
post #2

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 re…

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.

Re: ElixirNitpicks

#35
post #6

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 :)

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 |> José Val…

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

?

Re: ElixirNitpicks

#36

A nitpick of mine is how filtering with `for` is not explicit. arg = [1, 2, 3] # This doesn't crash: for {key, value} ... end)

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 <-

Re: ElixirNitpicks

#37
post #11

> 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.

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.

Re: ElixirNitpicks

#38

Earlier quoted context omitted.

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?

`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.

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.

Re: ElixirNitpicks

#39
post #25

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 ha…

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...

Re: ElixirNitpicks

#40
post #26
post #15

The following code could be written much better by using the cond operator. with {:is_email, true} {: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.

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} where call_service, decode and store_in_db return the specific errors like {:error, :bad_request}, {:error, :conflict}.

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 
      # we will never come here
      # because the returned value from f1
      # is already matched to the variable response_f1 
  end
Post reply on HN