What would you remove from Elixir?

language-implementation

#21

There’s a whole field of study called Numerical Analysis that deals with the problem of using floating point to solve engineering equations. Floats aren’t a problem if you understand the limitations and use of precision.

For science the input data almost always has some level of fuziness or imprecision, so learning to deal with that in an appropriate way makes sense. Floating point was designed by scientists to do science. It’s a reasonable compromise between accuracy and speed.

Even today, for large science computations speed and memory use are primary constraints on the kind of science you can do. Having 10 digits of precision in the results is generally not the primary constraint.

And yes, I am very biased as I have spent 30+ years helping scientists get floating point “right” in one way and another.


#22

I think you will like Ratio, because it does much of this.

As for using symbolic math: The reason it is not used frequently in practice is that it is multiple orders of magnitude slower than using floating-point operations (which modern CPUs are very much optimized for), and for most computational problems, working with (matrices of) floats is ‘good enough :tm:’.

Still, symbolic(/arbitrary precision) math certainly has its use cases. I did start writing something akin to a symbolic math engine in Elixir a while back (abusing the power of macros to read Elixir’s AST as symbolic formulas). But my mathematical skills are not something to write home about :sweat_smile:, so it is very much incomplete and probably bug-ridden.


#23

I like tuple keyword pairs, I do however hate the [foo: 1] syntax, I prefer [{foo, 1}], not only because that is the erlang way, but because propertylists are more powerful than elixir kwlists. For example in erlang-style property-lists both of these are the same thing (using elixir syntax of course) [{:blah, true}, :blah] which means that you can have very succinct option lists for arguments, but in elixir you have to do [{:blah, true}] (or the traditionally elixir syntax, which saves you 3 chars [blah: true]) instead of just being able to do [:blah]. Consequently I use the erlang’s property list functions in Elixir a lot more than I use Elixir’s Keyword module just because the Keyword module is a sub-set of the property list module as an Elixir keyword list is a property list, but a property list is not necessarily a keyword list, property lists are more succinct and more powerful. In addition the ‘key’ of a property list can be anything, not just atoms.

I’m still iffy on the colon-atom syntax in Elixir, but I think I’m warming up to it. And atoms are interned strings, they are not strings, just like you cannot compare a flyweight string in C++ to a normal string, same thing here. Think of Atoms as a globally named enumeration (because that really is what they are), do not think of them as strings. I absolutely would *NOT*EVER* want atoms removed, enumerations are valuable.

That is in any language though (that does not do magical and bug-hiding auto-casting), the bit representation of 1.0 and 1 are different (well, it might for 1.0 and 1 depending). Absolutely would not want this removed either. Emulating floating point with integer math is sloooooooow.

I mostly prefer fixed-point though, but floating point is sometimes necessary.

Hehe, what I said above, you want property lists too. :wink:

if is a nice wrapper around cond (wish it were a literal wrapper around cond), but it and unless are nice macro’s, they should just not be part of the language proper, but should be macro’s inside Kernel is all, if so then all good.

Keyword lists are not inheritence from Erlang, they are entirely an Elixir construct, just a reduced property list (which ‘is’ from Erlang). I’d prefer property lists over keyword lists any day.

Tagged tuples are indeed not only idiomatic but *very*fast*, they are awesome in general.

Function head matching just compiles into a case statement in a single function in CoreErlang anyway, so they are just a case.

case is a core branching conditional, it should be front-and-foremost.
cond is a core sequential conditional, it should be front-and-foremost.

case and cond cannot do what the other does as efficiently as the other does it.

Everything else after that is sugar.

There are libraries for that. The compiler absolutely should not do that, absolutely! They have two very different use-cases and they are not equivalent.

So you want it how it is in Erlang then. :wink:
In Erlang you have no if, just cond, and if you have no ‘else’ condition (via true ->) then you get a match error. I prefer that.

Elixir has one very very very what I consider monumentally stupid decision that is hitting me to this day (in mostly macro work), but I’ll get to that at the bottom…

Very very much. For every one time I use if/cond I use case at least a dozen times. if/cond is not a common construct on the BEAM for well written code.

Honestly I’d think elixir should toss out if/unless, if someone wants those let them use and/or/||/&& as those are obviously expressions and have defined results. Try to push people to case/cond instead.

I’d honestly prefer if the a: 42 syntax was removed entirely to be honest, it is inconsistent, it should just be :a => 42 as it would be a lot less confusing to newbies. This is not a big thing to me though, but I do like unified constructs, and having two ways to do the same expression for something so simple does bug me.

We already have the Decimal and Ratio libraries for that. :slight_smile:

Except those cannot be emulated efficiently by a library, floating point has to be a fairly native language construct unless you can write assembly in the language (which we obviously cannot).

And it has great uses by far! Just a lot of people try to do stupid things like represent money as floating points. :wink:

If you can come up with an efficient representation of floating point that is at least as fast as floating point (since its purpose is for efficiency, not accuracy) then I’d say sure, but if you came up with such a style you would also likely become a millionaire near overnight.

Standard markdown syntax:
![Image title, can be blank](https://url/to/image.png)
Like a link, just with ! at the start. :slight_smile:

Not necessarily. If you only need, oh, 4 decimal places of accuracy and your work will not cause the error value to exceed that then floating point can let you get it done in, oh, 10 seconds, where fixed-point might take 2 hours. SSE is a heck-of-a-drug. ^.^

Ditto, there is *no*reason*whatsoever* to have with be a special language construct. It can easily be done as a macro instead, and can indeed be done ‘better’ as a macro. Elixir has a few ‘special constructs’ that just break the syntax rules or are just wtf’s, and with is one.

I think something like with-as-a-macro should be included in Kernel, but it should absolutely not be a special construct.

What should be removed

And for my vote of what should be removed:

BLOODY-FREAKING-VARS-LEAKING-OUT-OF-SCOPE!

iex> case :blah do
...>   :blah ->
...>     a = 42 # From function or so
...>     a * 2
...>   _ -> :bloop
...> end
84
iex> a
42
iex> # WTF?!?!?
nil

Grrrr, and sometimes this is really REALLY freaking hard to fix in macro’s without resorting to holding some state in ETS tables or the procdict or so, grrrrrrrr…

WHY was this EVER a thing?!? This is the one thing that bugs me most of all and just makes the language feel like it is bug-ridden-waiting-to-explode kind of thing (even though I know better)…

Bonus second thing

Why the frick is the atom nil used as the nil value when [] is better for it in every way. It is ‘slightly’ faster for comparisons, it is also falsey to the BEAM, it is even called the nil type in the Erlang documentation, and there are a half-dozen other reasons it should have been used.

Also nil is used way too much! The undefined atom is often better in most places it is used as it is a great indicator of this is an ignorable return, or the ok atom is often better in most places it is used as it is a better indicator of success-but-no-useful-return, or the error atom is often better in most places it is used as it is a better indicator of uhhh-wtf, or any other number of things. I cannot thing of a single place anywhere in any function in any place that I would prefer nil over one of []/undefined/ok/error or something else more specifically descriptive. I have no clue where this nil wart even came from, probably some ruby-horror…

Bonus-bonus what I want added

An option and result types, though they would really be {:ok, value}/:error and {:ok, value}/{:error, reason} but with helpers in a module. This could easily be a library sure, but this should be baked into the language stdlib/kernel or so thus the usage is ubiquitous and unified. I’d love to do something like this (assume each returns an option/result tuple):

import OptionResult, only: [|>: 2, ~>: 2] # I'd prefer these in kernel though...
do_something(blah)
|> operate_on_value(bleep)
~> operate_on_error_to_return_new_result(bloop)
|> more_stuff()
|> yet_more_stuff()

And at the end you have an ok or error tuple that is the result of the pipeline, success values are passed in at |> and skipped on error, and error values are passed in on ~> and skipped on successes. You could even have a final call of something like ~> throw if you want to throw on error so you only have a success too.

Yes I know there are libraries for these, exceptional is the library I use for this (although it does ‘more’), but it really should be part of the base standard library so its use is ubiquitous.


#24

I assume you know but if not check out OK. It is very similar to exceptional; However I am trying to keep it really small so it could act as a blueprint to what would support for result tuples could be added to the language. As a bonus it has a with macro.[quote=“Qqwy, post:22, topic:5622”]
I think you will like Ratio, because it does much of this.
[/quote]

That also looks good. I have just played around and 30mins allowed me to do the following.

use Num.Rational

a = 1/2
b = 3/4
assert "2/3" == "#{a / b}"

#25

I just have to repeat what José said in IRC yesterday:

José Valim: honestly, i only added nil because i heard it was the one billion dollar mistake
but i was thinking that the person made one billion dollar with the mistake
so i was waiting for my one billion dollar but alas :’(

:'D

As to why use nil over [], I think the reason is to show intent more clearly, as [] might mean ‘no results’ rather than ‘nothing’. nil is more search-able than [], just like Enum.map is more searchable than <$>.


#26

Lol, that is awesome! ^.^

I still hold that nil and [] both are not appropriate in most places they are used, properly named returns are always better. :slight_smile:


#27

I would remove nil.


#28

I’m reminded of old days in Erlang (decade+ ago) where there was discussion about some of the API’s (especially ETS) about some things taking or returning magic values (things like :"$ENDOFTABLE$" or something like that) to indicate empty and so forth, it is unlikely to be a valid value, but it ‘could’ be a valid value, and that they should have returned tagged-tuples instead, it was a wart on the system.

Right now Elixir is doing the same, it is returning the value nil, which actually is a valid value in many cases right now (due to how ubiquitous Elixir uses nil all over the place) so sometimes you cannot tell what is valid and what is not, there have been multiple conversations on the Issue Tracker and here and other places talking about those API’s and how to figure out if the nil is the non-value nil or if it a valid return from what they were trying to get. It is interesting how things are repeating in the Elixir world without learning from the old Erlang days. ^.^

Tagged tuples really should be far far more ubiquitous in Elixir, it could have made a better syntax for those perhaps, or just use them as-is is perfectly fine.


#29

Turning to tuples everywhere, unfortunately, isn’t a viable solution. Just couple days ago, there was an issue about replacing the “awfully nil-ridden” Enum.find_value (and friends). This would mean, however, in many places replacing code that is very readable and simple with something very complex.

Let’s look at some examples I posted in the issue:

In Phoenix here we would change from:

content =
  Enum.find_value(sources, fn source ->
    File.exists?(source) && File.read!(source)
  end) || raise "could not find #{source_file_path} in any of the sources"

to

res = 
  Enum.seach(sources, fn source ->
    if File.exists?(source) do
      {:ok, File.read!(source)}
    else
      :error
    end
  end) 
content =
  case res do
    :error -> raise "could not find #{source_file_path} in any of the sources"
    {:ok, content} -> content
  end

Another similar use case is present just couple lines lower in the same file. I also found a couple more uses with an || after find_value/2 in Phoenix, but those could be arguably replaced by passing the default value to find_value/3. It’s not possible in case we wanted to raise, though.

Another use that would require more convolution would require changing from:

Enum.find_value(headers, fn({k, v}) -> k =~ ~r/^origin$/i && v end)

to

Enum.search(headers, fn({k, v}) -> if k =~ ~r/^origin$/i, do: {:ok, v}, else: :error end end)

with additional modification of the consumer, from a simple if to a case match on the ok tuple.

Last use I found after a brief grep of deps of my app is in ecto (and that one I wrote myself). It would require changing:

defp check_operations_valid(operations) do
  Enum.find_value(operations, &invalid_operation/1) || {:ok, operations}
end

defp invalid_operation({name, {:changeset, %{valid?: false} = changeset, _}}),
do: {:error, {name, changeset, %{}}}
defp invalid_operation(_operation),
do: nil

to
```elixir
defp check_operations_valid(operations) do
  case Enum.search(operations, &invalid_operation/1) do
    :error -> {:ok, operations}
    {:ok, invalid} -> {:error, invalid}
  end
end

defp invalid_operation({name, {:changeset, %{valid?: false} = changeset, _}}),
do: {:ok, {name, changeset, %{}}}
defp invalid_operation(_operation),
do: :error

While it does not introduce more code, it’s much harder to understand, since we’re looking for an error, but have to return it tagged in an ok tuple, just to switch tags at the end.

The situation is far more nuanced than “nil is bad”. I urge some of you that propose removing it, to try to remove it from your code and get back to us with results.

Also, switching from atom nil to [] would make the issue far, far worse. An empty list is so much more ubiquitous than atom nil, it would require that much more attention when used. It would also lead to conflating two notions - empty collections and “no value”.


With do(:)?
#30

About if you can use with for the same effect :stuck_out_tongue:

But in general, i highly prefer function pattern matching because

  1. it makes the scoping evident
  2. it gives the test a name which explains really well what you are doing
  3. it is extendable
  4. it is really not a way of thinking about your flow that i want to offer to people.

#31

Why use boolean operators in the befores but not the afters? Seems unfair.

content =
  case Enum.search(sources, &(File.exists?(&1) && {:ok, File.read!(&1)} || :error)) do
    {:ok, content} -> content
    :error -> raise "could not find #{source_file_path} in any of the sources"
  end
Enum.search(headers, fn({k, v}) -> k =~ ~r/^origin$/i && {:ok, v} || :error end)

#32

I wrote why I dislike hiding an if behind && and else behind || in this article.

When you think about teaching programming, what’s the first thing you want someone to learn? It’s if! It’s the most basic construct in nearly every programming language. Every programmer knows if, every programmer wrote thousands upon thousands of ifs. Every programmer knows what if means and what was the intention of the coder who wrote it.
[…]


#33

I disagree with your article but that’s not the point. The point is that if you rewrote your befores using if they would be verbose too.


#34

The thing is, I didn’t have to. I feel completely comfortable with a single && operator. I feel completely not comfortable with a combination of && and || to emulate an if.


#35

I often find myself writing multiline statements that depend on boolean values, which however do not warrant their own named function. For this, if/else is very useful.

(For the min example of @michalmuskala’s blog post, I think I’d be most happy with this form:

def min(a, b) when a < b, do: a
def min(_a, b), do: b

)


#36

Also, I don’t think {:ok, _} and :error are very appropriate in this situation. It’s not an error if the element doesn’t match what you’re looking for. Something more descriptive would be better, like :notfound.

It’s unconventional (as far as I know), but I think an idiom where a single element tuple holds a value in cases where only a single value must be returned could be introduced to reduce complexity.

Enum.search(list, fn(item) -> ... && {item} || :notfound end)

#37

Your arguments against boolean operators seem to be a) that they don’t make long conditionals like

  if (vehicle.type == :car)
    drive(vehicle)
  elsif (vehicle.type == :plane)
    fly(vehicle)
  else
    sail(vehicle)
  end

any better, and b) that there’s nothing wrong with if in the first place. However, wouldn’t you agree that the complexity in your after examples is cause by the ifs? The boolean operators in my example are not long conditionals either. From your article, I don’t see any reasons not to choose && || over if in this situation.


#38

I don’t know about others, but whenever I see foo? && bar || baz I have to “parse” it to an if before I can understand the code. That’s why I call it “emulation” - for me it looks like a fancy way to make your code “if-free” (that seems to be a popular metric lately) without actually giving any real benefits.


#39

Firstly I would like to remove bugs :smiley:


#40

I only do that if I’m doing ElixirGolf and trying to save a few characters for fun. Do you really see that in production code?!? :scream: