提供的类型的模式匹配

时间:2015-09-29 12:46:03

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

首先,获取模式并解析:

myuser@my-laptop:~/Documents/Projects/frontend$ npm list --depth 0
frontend@0.0.1 /home/myuser/Documents/Projects/frontend
├── async@0.9.2
├── bower@1.6.3
├── clean-css@2.2.23
├── del@1.2.1
├── gulp@3.9.0
├── gulp-angular-templatecache@1.8.0
├── gulp-concat@2.6.0
├── gulp-cordova-bump@1.3.0
├── gulp-minify-css@0.3.13
├── gulp-ng-annotate@1.1.0
├── gulp-rename@1.2.2
├── gulp-replace-task@0.1.0
├── gulp-sass@1.3.3
├── gulp-useref@1.3.0
├── gulp-util@2.2.20
├── jshint@2.8.0
├── mv@2.1.1
├── ng-annotate@0.15.4
├── q@1.4.1
├── shelljs@0.3.0
├── uglify-js@2.4.14
└── yargs@1.3.3

现在我们可以访问type desc = JsonProvider< """[{"name": "", "age": 1}]""", InferTypesFromValues=true > let json = """[{"name": "Kitten", "age": 322}]""" let typedJson = desc.Parse(json) .Age和.Name属性,但是,我希望在编译时对它们进行模式匹配,以便在架构发生更改时出错。

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

typedJson.[0]

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

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") 不是OS,我将无法实施FSharp.Data。一般来说,整个方法似乎是错误的,并重做了工作。

我知道使用类型提供程序无法生成受歧视的联合,但是在编译时可能有更好的方法来完成所有这些检查吗?

4 个答案:

答案 0 :(得分:3)

据我所知,无法确定Json架构是否已经改变&#34;在编译时使用给定的TP。

这就是原因:

  • JsonProvider<sample>正是在编译时启动的,它提供了一种在运行时操作Json内容的类型。这个提供的擦除类型有几个适用于任何sample和类型Root的运行时静态方法 使用少量实例属性扩展IJsonDocument,包括基于编译时提供的样本(在您的情况下,属性NameAge)。只有一个非常宽松的隐式 Json&#34;架构&#34;在JsonProvider之后 - 提供类型,没有其他这样的实体可以与编译时的更改进行比较;
  • 在运行时只提供带有静态方法的类型desc和带有对应实例方法的Root类型 为您操作任意Json内容提供服务。所有这些爵士乐都非常不可知 &#34; Json schema&#34;,在你给定的情况下,只要运行时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}]]""")
    

    两者都将在运行时被愉快地解析为&#34;键入Json&#34;符合&#34;架构&#34;由给定sample的TP派生而来,虽然显然NameAge缺失,但如果访问则会引发异常。

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

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

    来自JsonProvider

    Fsharp.Data TP缺少此类Json架构处理功能,因此有效负载验证仅在运行时完成。

    答案 1 :(得分:2)

    如果你使用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”。

    答案 2 :(得分:2)

    引用您的评论,以更好地解释您想要实现的目标:

      

    谢谢!但我想要实现的是获得编译器错误   如果我添加一个新字段,例如为json架构着色然后忽略它   而后来处理。如果是工会,它将是即时FS0025。

    和:

      

    是的,我必须处理所有字段,所以我不能依赖_。我想要它   当架构发生变化时,我的F#程序无需添加即可编译   必要的处理功能(而不仅仅是忽略新的领域或   在运行时崩溃。)

    最简单的解决方案是建立一个&#34;测试&#34;对象

    提供的类型带有两个构造函数:一个使用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 :(得分:2)

    <tldr>
    Build some parser to handle your issues. http://www.quanttec.com/fparsec/
    </tldr>
    

    因此...

    你想要能够阅读某些内容并使用它做某事的东西。不知道apriori这些东西是什么。

    祝你好运。

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

    随着说:

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

    请注意,动态不是静态的。 F#有静态类型,很多都基于此。并提供类型提供商。战斗会让你的头疼。这当然是可能的,甚至有可能通过打击类型提供者来实际使用这种方法,但它再次不是真正的类型提供者,也不是它的目的。