How To Write A `pick` Function?

I’m sure part of the answer here is something to the effect of “you don’t”, but I’m trying to understand limitations and work arounds. I’m trying to write a function that will pick properties out of an “Object” (not a record). I’m trying to use Js.Array.reduce but it’s not happy about the type difference. I don’t understand how to tell it that my acc will be an object- it seems to always want an array. Is it possible to do something like this? Is there a library or function in Belt that will help me achieve this?

let pick = (props, data) => Js.Array.reduce<>((acc, prop) => {
    acc[prop] = data[prop]
    acc
  }, Js.Obj.empty(), props)

{ "foo": 1, "bar": 2 } |> pick(["foo", "bar"])

Dynamic object access is not possible. field access syntax (ident[e]) is parsed into array access if e is not a string literal.
but you can achieve simliar behavior via Js.Dict

let pick = (xs, keys) =>
  xs
  ->Js.Dict.entries
  ->Js.Array.filter(((key, _)) => Js.Array.includes(key, keys), _)
  ->Js.Dict.fromArray

let xs = [("foo", 1), ("bar", 2), ("baz", 3)]->Js.Dict.fromArray
// { foo: 1, bar: 2, baz: 3 }

let ys = xs->pick(["foo", "baz"])
//{ foo: 1, baz: 3 }
2 Likes

Thank you for your reply and example! I figured that was going to be the case after tinkering with it for a bit. Appreciate you explaining why it’s not possible so I stop trying :sweat_smile:

1 Like

Also, consider something like

{ "foo": 1, "bar": "hello" } |> pick(["foo", "bar"])

The problem is that the type of the subexpression data[prop] in your code has to vary it’s type through each iteration. Some times it’s an int and some times it’s a string. And what type should give the parameter data in the first place? After all, it can come from anywhere in the program. Type checking isn’t a local property anymore as a result. Finally, what is the type of acc? It’s not easy to give a type there either, because it is highly dependent on the types which are input.

Languages with static typing usually erases symbols when the program is executed. That is, the foo or bar in the record

type t = { foo : int, bar : string }

doesn’t even exist at runtime. One escape hatch is reflection which allows the programmer to use strings, or other data types, to manipulate such symbols at run-time. But the flip side of reflection is that a lot of the safety is thrown out as well. Another common strategy is for the programming language to provide automatic derivation of field accessors. This strategy is taken in a lot of OCaml programs. The underlying problem here, however, is that a JS Object has no type information. Thus, it isn’t even possible to make automatic derivation, and you are stuck with reflection strategies.

In general, I’d keep manipulations such as these on the JS side, and then factor it through a verification process as you lift it into Rescript. That way, you can rely on safety properies of Rescript in your code.

1 Like