d5/tengo

Discrepancy between "enum" package documentation and its behavior

KEINOS opened this issue · 3 comments

I would like to PR this issue, but I wanted to ask which I should do between:

  1. Modify the documentation to match the behavior of the script.
  2. Modify the script as written in the document.

TL; DR

TS; DR

  • The document says as below.

    filter(x, fn) => [object]: iterates over elements of x, returning an array of all elements fn returns truthy for. fn is invoked with two arguments: key and value. the key is an int index if x is array. key is a string key if x is map. It returns undefined if x is not enumerable.

  • Script to reproduce (Tengo v2.10.0)

fmt := import("fmt")
enum := import("enum")

filter := func(k, v) {
    return v % 2 == 0
}

// Array Case
arr := [1, 2, 3, 4, 5, 6, 7]
filtered := enum.filter(arr, filter)

fmt.println("filter(array):", filtered)
fmt.printf("  type: %T\n", filtered)

// Map Case
map := {"one": 1, "two": 2, "three": 3, "four": 4}
filtered = enum.filter(map, filter)

fmt.println("filter(map):", filtered)
fmt.printf("  type: %T\n", filtered)

// Output:
// filter(array):[2, 4, 6]
//   type: array
// filter(map):
//   type: undefined

Do you want it to return filtered map, array of values or array of tuples? I think @d5 should decide, in JS there is no filter for map/Object type :)

Script:

enum := import("enum")
fmt := import("fmt")

filter := func(k, v) {
return v % 2 == 0
}

map := {"one": 1, "two": 2, "three": 3, "four": 4}
filtered := enum.filter(map, filter)

fmt.println("filter(map):", filtered)

Possible results

1. Filtered map (order unstable, but for k-v doesn't matter)

filter(map):{four: 4, two: 2}

2. Array of values

This one is unstable (order of values can vary between runs)

filter(map):[2, 4]

3. Array of tuples (array of arrays for this example)

filter(map):[["four", 4], ["two", 2]]

Implementations:

Both 1. and 3. use helper:

is_map_like := func(x) {
  return is_map(x) || is_immutable_map(x)
}

1.

filter: func(x, fn) {
    dst := undefined
    if is_array_like(x)  {
      dst = []
      for k, v in x {
      if fn(k, v) { dst = append(dst, v) }
      }
    } else if is_map_like(x) {
      dst = {}
      for k, v in x {
        if fn(k, v) { dst[k] = v }
      }
    }

    return dst
  },

2.

filter: func(x, fn) {
    if !is_enumerable(x) { return undefined }

    dst := []
    for k, v in x {
      if fn(k, v) { dst = append(dst, v) }
    }

    return dst
  },

3.

filter: func(x, fn) {
    if !is_enumerable(x) { return undefined }

    dst := []
    if is_array_like(x)  {
      for k, v in x {
        if fn(k, v) { dst = append(dst, v) }
      }
    } else if is_map_like(x) {
      for k, v in x {
        if fn(k, v) { dst = append(dst, [k, v]) }
      }
    }

    return dst
  },
d5 commented

I think we should just update the documentation to match the code behavior.

Yeah, that is 4th option :D
I thought about generic helper (to handle map gracefully), but anyone can just implement this filter very easily.