erg-lang / erg

A statically typed language compatible with Python
http://erg-lang.org
Apache License 2.0
2.68k stars 55 forks source link

Feature Request: Per Iteration Variable #418

Open anzhi0708 opened 1 year ago

anzhi0708 commented 1 year ago

In this code snippet, the scope of the iteration variable i is global, and the anonymous function captures the variable by reference instead of by value during each iteration, which is consistent with Python but different from functional languages such as OCaml and Scala. To some extent, this behavior goes against intuition.

mut_arr = ![]
for! 0..<5, i =>
  mut_arr.push! ((x: Int)->i)
print! mut_arr

for! mut_arr, f =>
  print! f
  print! f(233)

print! "i = \{i}"

Output:

[<function <lambda> at 0x102d69b20>, <function <lambda> at 0x102d69bc0>, <function <lambda> at 0x102d69ee0>, <function <lambda> at 0x102d69f80>, <function <lambda> at 0x102d6a020>]
<function <lambda> at 0x102d69b20>
4
<function <lambda> at 0x102d69bc0>
4
<function <lambda> at 0x102d69ee0>
4
<function <lambda> at 0x102d69f80>
4
<function <lambda> at 0x102d6a020>
4
i = 4

Scala 3

import scala.collection.mutable.ArrayBuffer
  var functions = ArrayBuffer[()=>Int]()
  for i <- 1 to 10 do
    functions.append(()=>i)
  println(functions)
  println(functions(0)())
  println(functions(1)())
  println(functions(2)())

Output of Scala

ArrayBuffer(hello_world$package$$$Lambda$1391/0x00000008014a1c00@1f521c69, hello_world$package$$$Lambda$1391/0x00000008014a1c00@2b3abeeb, hello_world$package$$$Lambda$1391/0x00000008014a1c00@3aeb267, hello_world$package$$$Lambda$1391/0x00000008014a1c00@13a9cdae, hello_world$package$$$Lambda$1391/0x00000008014a1c00@1c972ae6, hello_world$package$$$Lambda$1391/0x00000008014a1c00@62a41279, hello_world$package$$$Lambda$1391/0x00000008014a1c00@146fa9c0, hello_world$package$$$Lambda$1391/0x00000008014a1c00@49f6c25e, hello_world$package$$$Lambda$1391/0x00000008014a1c00@6c13019c, hello_world$package$$$Lambda$1391/0x00000008014a1c00@2e66f1bd)
1
2
3
mtshiba commented 1 year ago

I noticed that de-optimization changes the behavior with for!.

for_! = for!

arr = ![]
for_! 0..<5, i =>
    arr.push!(() -> i)

for! arr, f =>
    print! f, f()
<function <lambda> at 0x000001E1B8A3C720> 0
<function <lambda> at 0x000001E1B8B68540> 1
<function <lambda> at 0x000001E1B8B68680> 2
<function <lambda> at 0x000001E1B8B8B060> 3
<function <lambda> at 0x000001E1B8B8B100> 4

De-optimized, pure procedure for! is defined here.

https://github.com/erg-lang/erg/blob/3b9f56f53f9a17300b18c5d1d782fc56ef3480dc/crates/erg_compiler/lib/std/_erg_control.py#L8-L10

This is what we wanted, but de-optimized version is much slower.

# iteration with 0..<1000000
❯ hyperfine "cargo r test.er" # optimized (normal) version
Benchmark 1: cargo r test.er
  Time (mean ± σ):      1.981 s ±  0.016 s    [User: 1.493 s, System: 0.158 s]
  Range (min … max):    1.964 s …  2.014 s    10 runs

❯ hyperfine "cargo r test.er" # de-optimized version
Benchmark 1: cargo r test.er
  Time (mean ± σ):      3.168 s ±  0.120 s    [User: 2.562 s, System: 0.255 s]
  Range (min … max):    3.088 s …  3.475 s    10 runs

I will try to see if we can get the desired behavior without performance penalty.

anzhi0708 commented 1 year ago

Thanks for your reply! This is what I was looking for :-)