1

我是 ReasonML 的新手,但我阅读了大部分官方文档。我可以为此经历偶然的试验和错误,但由于我现在需要在 ReasonML 中编写代码,我想知道迭代原因记录类型的键和值的最佳实践。

4

2 回答 2

3

也许我不理解这个问题或用例。但据我所知,没有办法迭代记录的键/值对。您可能想要使用不同的数据模型:

通过记录,所有键和值类型都是已知的,因此您只需编写代码来处理每一个,无需迭代。

于 2020-09-24T04:15:32.360 回答
3

我完全同意@Shawn 的观点,您应该使用更合适的数据结构。例如,元组列表是传入用户定义的一组同构键/值对的好方法:

fooOnThis([
  ("test1", ["a", "b", "c"]),
  ("test2", ["c"]),
])

如果您需要异构数据,我建议您使用变体来指定数据类型:

type data =
  | String(string)
  | KvPairs(list((string, data)));

fooOnThis([
  ("test1", [String("a"), String("b"), String("c")]),
  ("test2", [String("c"), KvPairs([("innerTest", "d")])]),
])

或者,您可以使用对象而不是记录,这似乎是您真正想要的。

对于记录,记录需要预定义的记录类型:

type record = {
  foo: int,
  bar: string,
};

这就是你构建它们的方式:

let value = {
  foo: 42,
  bar: "baz",
};

另一方面,对象是结构类型的,这意味着它们不需要预定义的类型,并且您构造它们的方式略有不同:

let value
  : {. "foo": int, "bar": string }
  = {"foo": 42, "bar": "baz"};

请注意,键是字符串。

使用可Js.Obj.keys用于获取密钥的对象:

let keys = Js.Obj.keys(value); // returns [|"foo", "bar"|]

现在的问题是获取值。没有Js.Obj用于获取值或条目的 API,因为它要么不健全,要么非常不切实际。为了证明这一点,让我们尝试自己制作。

我们可以轻松地编写自己的绑定到Object.entries

[@bs.val] external entries: Js.t({..}) => array((string, _)) = "Object.entries";

entries这是一个函数,它接受任何对象并返回一个元组数组,其中string键和值的类型将根据我们如何使用它们来推断。这既不安全,因为我们不知道实际的值类型是什么,或者特别实用,因为它将是同质类型的。例如:

let fields = entries({"foo": 42, "bar": "baz"});

// This will infer the value's type as an `int`
switch (fields) {
| [|("foo", value), _|] => value + 2
| _ => 0
};

// This will infer the value's type as an `string`, and yield a type error
// because `fields` can't be typed to hold both `int`s and `string`s
switch (fields) {
| [|("foo", value), _|] => value ++ "2"
| _ => ""
};

您可以使用这些switch表达式中的任何一个(在运行时出现意外结果和可能的崩溃),但不能同时使用两者,因为string | int在 Reason 中没有可推断的未装箱类型。

为了解决这个问题,我们可以将值设为抽象类型并使用它Js.Types.classify来安全地获取实际的底层数据类型,类似于typeof在 JavaScript 中使用:

type value;

[@bs.val] external entries: Js.t({..}) => array((string, value)) = "Object.entries";

let fields = entries({"foo": 42, "bar": "baz"});

switch (fields) {
| [|("foo", value), _|] =>
  switch (Js.Types.classify(value)) {
  | JSString(str) => str
  | JSNumber(number) => Js.Float.toString(number)
  | _ => "unknown"
  }
| _ => "unknown"
};

这是完全安全的,但如您所见,不是很实用。

最后,我们实际上可以稍微修改它以安全地将其与记录一起使用,这依赖于记录在内部表示为 JavaScript 对象这一事实。我们需要做的不仅仅是entries对象:

[@bs.val] external entries: 'a => array((string, value)) = "Object.entries";

let fields = keys({foo: 42, bar: 24}); // returns [|("foo", 42), ("bar", 24)|]

这仍然是安全的,因为所有值都是 JavaScript 中的对象,我们不对值的类型做任何假设。如果我们尝试将它与原始类型一起使用,我们将得到一个空数组,如果我们尝试将它与数组一起使用,我们将获得索引作为键。

但是因为需要预先定义记录,所以这不会很有用。综上所述,我仍然建议使用元组列表。

注意:这使用 ReasonML 语法,因为这是您所要求的,但指的是 ReScript 文档,该文档使用稍微不同的 ReScript 语法,因为 BuckleScript 文档已被删除(是的,我知道现在一团糟。希望它最终会改善的。)

于 2020-09-24T10:57:39.113 回答