I looked around the world wide wonder to find how to do list comprehsion in Nim, but found rather conflicting info (links to the sugar module which were outdated, etc). I could not get a clear picture, sorry.
Therefore my question: What's the current state of list comprehension in Nim?
But can it do this?
for n <- 10..98,
k <- (n + 1)..99,
rem(n, 10) != 0 && rem(k, 10) != 0,
d = gcd(n, k),
d > 1,
dn = digits(n),
dk = digits(k),
{a, b} <- intersection(dn, dk),
a != 0,
nr = div(n, d),
kr = div(k, d),
dab = gcd(a, b),
ar = div(a, dab),
br = div(b, dab),
nr == ar && kr == br do
{n, k, a, b}
end
from Elixir (taken from https://github.com/greenfork/project_euler/blob/master/problem33.exs).
General ideas:
In Nim it would be the following:
collect(newSeq):
for n in 10..98:
for k in (n+1)..99:
if n mod 10 != 0 and k mod 10 != 0:
let d = gcd(n, k)
if d > 1:
let
dn = digits(n)
dk = digits(k)
for (a, b) in intersection(dn, dk):
if a != 0:
let
nr = n div d
kr = k div d
dab = gcd(a, b)
ar = a div dab
br = b div dab
if nr == ar and kr == br:
(n, k, a, b)
Nim variant is "more powerful" because we can do literally anything inside it and it is much harder to understand because during list comprehension we don't really want to do "literally anything", just a set of common actions.
Yes, Nim can do that but it accomplishes the same very differently. For mutation tracking put the code into a func and use Nim's "strict func" mode.
Nim variant is "more powerful" because we can do literally anything inside it and it is much harder to understand because during list comprehension we don't really want to do "literally anything", just a set of common actions.
Reading through https://elixir-lang.org/getting-started/comprehensions.html I see no restrictions that "for loop comprehensions enforce no mutations / no IO / whatever", one example uses File.stat!(path).size inside a comprehension ...
just a set of common actions.
So what are these "common actions" that we should restrict Nim's collect with? File.stat seems to be just fine for Elixir...
I agree that "purity" is rather hard to ensure and probably not needed, more like a safeguard against bad code.
I think main benefit is semantics: in Elixir it is single block and only 3 (presumably) operations are allowed:
When there are more allowed operations it also complicates the reading in the sense that one cannot read top-to-bottom because we should also account for mid-level else-branches, mutating variables (something with var). And when this complexity is present it really stops being "list comprehension" and more like a shortcut for several symbols:
rs = @[] # replace with `collect(newSeq)`
for ...
...
rs.add n # yay we can omit `rs.add`
I understand that distinction is subtle but I would agree that "cleaner" style of more restrictive set of operations and single indentation level lead to better code. I also understand that in Python and Elixir list comprehension is implemented as a language construct compared to our user-space macro and hence the trade-offs can be reasonable.
Elixir doesn't have enough restrictions to produce the benefits you claim it does -- you can call arbitrary functions which can do arbitrary things, IO included.
Hence I fail to see the advantage of disallowing var and else inside collect (these are tame compared to IO...), but it's very easy to write a collect that enforces these restrictions.
and it is much harder to understand
Not universally. I'm not good in Elixier and not good in Nim. And yet I understand the Nim version much better, due to the indentation.
In the Elixier example all looks linear, but actually there is some kind of hidden "backtracking" happening due to to {a, b} <- intersection(dn, dk), and nr == ar && kr == br do and it looks entirely unclear to me why that would be the case --- and what this Elixier code is actually supposed to do.
And don't misunderstand me -- the Elixir version of your example is nice, because it's shorter than Nim's and yet still very readable. (But not more readable, the fact that three nested loops are involved is easier to see in Nim and is important to see when you care about the "big O" behaviors.)
It would be interesting to try and write a elixirCollect macro that mimics Elixir's design.