【问题标题】:Pattern matching on provided types提供类型的模式匹配
【发布时间】:2015-09-29 12:46:03
【问题描述】:

首先,获取一个schema并解析:

type desc = JsonProvider< """[{"name": "", "age": 1}]""", InferTypesFromValues=true >
let json = """[{"name": "Kitten", "age": 322}]"""
let typedJson = desc.Parse(json)

现在我们可以访问 typedJson.[0] .Age 和 .Name 属性,但是,我想在编译时对它们进行模式匹配,以便在架构更改时得到错误。

由于这些属性已被删除,我们无法在运行时获取它们:

let ``returns false``() = 
  typedJson.[0].GetType()
    .FindMembers(MemberTypes.All, BindingFlags.Public ||| BindingFlags.Instance, 
                 MemberFilter(fun _ _ -> true), null) 
  |> Array.exists (fun m -> m.ToString().Contains("Age"))

...我使用活动模式制作了运行时检查版本:

let (|Name|Age|) k = 
  let toID = NameUtils.uniqueGenerator NameUtils.nicePascalName
  let idk = toID k
  match idk with
  | _ when idk.Equals("Age") -> Age
  | _ when idk.Equals("Name") -> Name
  | ex_val -> failwith (sprintf "\"%s\" shouldn't even compile!" ex_val)

typedJson.[0].JsonValue.Properties()
|> Array.map (fun (k, v) -> 
     match k with
     | Age -> v.AsInteger().ToString() // ...
     | Name -> v.AsString()) // ... 
|> Array.iter (printfn "%A")

理论上,如果FSharp.Data 不是操作系统,我将无法实现toID。一般来说,整个方法似乎是错误的,需要重做。

我知道无法使用类型提供程序生成可区分联合,但也许有更好的方法在编译时进行所有这些检查?

【问题讨论】:

    标签: f# type-providers f#-data


    【解决方案1】:

    据我所知,无法在编译时使用给定的 TP 找出“Json 架构是否已更改”。

    这就是为什么:

  • JsonProvider&lt;sample&gt; 正是在编译时启动的,它提供了一种在运行时操作 Json 内容的类型。这个提供的已擦除类型有几个运行时静态方法,对于任何 sample 和类型 Root 都是通用的 扩展 IJsonDocument 并使用很少的实例属性,包括基于编译时提供的示例的属性(在您的情况下 - 属性 NameAge)。恰好有一个 非常轻松的隐式 JsonProvider-provided 类型后面的 Json“模式”,没有另一个这样的实体可用于在编译时进行更改;
  • 在运行时仅提供类型 desc 及其静态方法和其 Root 类型及其对应的实例方法 随时为您服务,以处理任意 Json 内容。所有这些爵士乐都与 “Json 模式”,在您给定的情况下,只要运行时 Json 内容表示一个数组,它的元素几乎可以是任何元素。 例如,
    type desc = JsonProvider<"""[{"name": "", "age": 1}]"""> // @ compile-time
    
    let ``kinda "typed" json`` = desc.Parse("""[]""") // @ run-time
    let ``another kinda "typed" json`` =
        desc.Parse("""[{"contents":"whatever", "text":"blah-blah-blah"},[{"extra":42}]]""")
    

    两者都将在运行时愉快地解析为符合 TP 从给定 sample 派生的“模式”的“类型化 Json”,尽管显然缺少 NameAge 并且如果访问将引发异常.

  • 建立另一个依赖于正式Json schema 的 Json TP 是可行的。 它可以在创建类型时使用来自模式存储库的给定模式的引用并允许操作元素 仅通过在编译时从模式派生的提供的访问器来解析 Json 有效负载。

    在这种情况下,更改 如果代码中使用的提供的访问器与更改不兼容,则引用的模式可能会破坏编译。 伴随运行时 Json 有效负载验证器或验证解析器的这种安排可以提供可靠的企业级质量 Json 架构更改管理。

    来自Fsharp.DataJsonProvider TP 缺乏这种Json 模式处理能力,因此负载验证只能在运行时完成。

    【讨论】:

      【解决方案2】:

      引用你的 cmets 来更好地解释你想要实现的目标:

      谢谢!但我想要实现的是得到一个编译器错误 如果我添加一个新字段,例如为 json 模式着色,然后忽略它 稍后处理。在工会的情况下,它将是即时 FS0025。

      和:

      是的,我必须处理所有字段,所以我不能依赖_。我想要这样 当架构更改时,我的 F# 程序不会在不添加的情况下编译 必要的处理功能(而不仅仅是忽略新字段或 在运行时崩溃)。

      最简单的解决方案是构造一个“测试”对象。

      提供的类型带有两个构造函数:一个接受一个 JSonValue 并对其进行解析 - 实际上与 JsonValue.Parse 相同 - 而另一个要求填写每个字段。

      这是我们感兴趣的。

      我们还将使用命名参数来调用它,这样我们不仅在添加或删除字段时是安全的,而且在重命名或更改它们时也是如此。

      type desc = JsonProvider< """[{"name": "SomeName", "age": 1}]""", InferTypesFromValues=true >
      
      let TestObjectPleaseIgnore = new desc.Root (name = "Kitten", age = 322)
      // compiles
      

      (请注意,我将示例中name 的值更改为"SomeName",因为"" 被推断为通用JsonValue。)

      现在如果类型提供者使用的示例中突然出现更多字段,构造函数将变得不完整,无法编译。

      type desc = JsonProvider< """[{"name": "SomeName", "age": 1, "color" : "Red"}]""", InferTypesFromValues=true >
      
      let TestObjectPleaseIgnore = new desc.Root (name = "Kitten", age = 322)
      // compilation error: The member or object constructor 'Root' taking 1 arguments are not accessible from this code location. All accessible versions of method 'Root' take 1 arguments.
      

      显然,错误指的是 1 参数构造函数,因为它试图适应那个构造函数,但您会看到现在提供的类型有一个 3 参数构造函数替换了 2 参数构造函数。

      【讨论】:

        【解决方案3】:

        如果你使用InferTypesFromValues=false,你会得到一个强类型返回:

        type desc = JsonProvider< """[{"name": "", "age": 1}]""", InferTypesFromValues=false >
        

        您可以使用desc 类型在您关心的属性上定义活动模式:

        let (|Name|_|) target (candidate : desc.Root) =
            if candidate.Name = target then Some target else None
        
        let (|Age|_|) target (candidate : desc.Root) =
            if candidate.Age = target then Some target else None
        

        这些活动模式可以这样使用:

        let json = """[{"name": "Kitten", "age": 322}]"""
        let typedJson = desc.Parse(json)
        
        match typedJson.[0] with
        | Name "Kitten" n -> printfn "Name is %s" n
        | Age 322m a -> printfn "Age is %M" a
        | _ -> printfn "Nothing matched"
        

        鉴于此处的 typedJson 值,该匹配项将打印出“Name is Kitten”。

        【讨论】:

        • 谢谢!但是我想要实现的是如果我添加一个新字段例如得到一个编译器错误。 Color 到 json 模式,然后在以后处理时忽略它。如果是工会,它将是即时 FS0025。
        • @yuyoyuppe 你说的忽略是什么意思?您必须“触摸”文档中的所有字段吗?
        • 是的,我必须处理所有字段,所以我不能依赖_。我想要它,所以当架构更改时,如果不添加必要的处理功能(而不仅仅是忽略新字段或在运行时崩溃),我的 F# 程序将无法编译。
        【解决方案4】:
        <tldr>
        Build some parser to handle your issues. http://www.quanttec.com/fparsec/
        </tldr>
        

        所以...

        你想要一些可以阅读并用它做某事的东西。事先不知道这些东西是什么。

        祝你好运。

        您确实希望类型提供程序为您执行此操作。类型提供程序的全部目的是“在编译时这是我所看到的,这就是我将要使用的”。

        话虽如此:

        您需要一些其他类型的解析器,您可以在其中检查“模式”(您知道将要发生的事情或您上次看到的内容与实际发生的事情的一些定义)。实际上,某些动态解析器​​将数据转换为具有动态类型的动态结构。

        请注意,动态不是静态的。 F# 有静态类型,很多都是基于它的。并且类型提供者更是如此。战斗会让你头疼。这当然是可能的,甚至可能通过与类型提供者抗争以实际使用这种方法,但话又说回来,它不是真正的类型提供者,也不是它的目的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-07-21
          • 1970-01-01
          • 2020-08-09
          • 2014-02-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多