【问题标题】:What is the best practice of iterating record keys and values in Reasonml?在 Reasonml 中迭代记录键和值的最佳实践是什么?
【发布时间】:2020-09-25 03:45:09
【问题描述】:

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

【问题讨论】:

    标签: record reason bucklescript rescript


    【解决方案1】:

    我完全同意@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")])]),
    ])
    

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

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

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

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

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

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

    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 表达式中的任何一个(在运行时会出现意外结果和可能的崩溃),但不能同时使用这两种表达式,因为在 Reason 中没有可推断的未装箱 string | int 类型。

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

    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 文档已被删除(是的,现在一团糟,我知道。希望它最终会有所改善。)

    【讨论】:

    • 哇,太棒了。我最终使用了记录,并迭代使用 Belt.map 将原始数据转换为适当的反应组件。帮助很大!
    【解决方案2】:

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

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

    【讨论】:

    • 感谢您的回复。我想做的是迭代一个类似 json 的数据类型来生成反应组件。我可以使用'switch'进行类型匹配,但想使用'map'或'obj.keys'之类的功能来解决它​​。您能否就此提供更多建议?
    • 例如给定一个数据 {"test1":["a","b","c"], "test2":["c":{"innerTest":"d "}]},我想阅读要在每个反应组件(
        元素的子元素)中使用的键及其适当的值。
    • 我对 bucklescript API 不是很熟悉,但是那里有 JavaScript 特定的函数可供您使用。 Js.Dict.t(我在上面发布了 API 文档的链接)会做你想做的事。它有一个keys 函数可以列出所有键,还有一个get 函数可以获取某个键的值。
    猜你喜欢
    • 1970-01-01
    • 2013-11-19
    • 1970-01-01
    • 2011-03-09
    • 2012-07-15
    • 2017-11-17
    • 2013-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多