{"action":"created","issue":{"url":"https://api.github.com/repos/xyncro/chiron/issues/81","repository_url":"https://api.github.com/repos/xyncro/chiron","labels_url":"https://api.github.com/repos/xyncro/chiron/issues/81/labels{/name}","comments_url":"https://api.github.com/repos/xyncro/chiron/issues/81/comments","events_url":"https://api.github.com/repos/xyncro/chiron/issues/81/events","html_url":"https://github.com/xyncro/chiron/pull/81","id":217229930,"number":81,"title":"WIP: Chiron 7 & .NET Core","user":{"login":"neoeinstein","id":18165,"avatar_url":"https://avatars1.githubusercontent.com/u/18165?v=4","gravatar_id":"","url":"https://api.github.com/users/neoeinstein","html_url":"https://github.com/neoeinstein","followers_url":"https://api.github.com/users/neoeinstein/followers","following_url":"https://api.github.com/users/neoeinstein/following{/other_user}","gists_url":"https://api.github.com/users/neoeinstein/gists{/gist_id}","starred_url":"https://api.github.com/users/neoeinstein/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/neoeinstein/subscriptions","organizations_url":"https://api.github.com/users/neoeinstein/orgs","repos_url":"https://api.github.com/users/neoeinstein/repos","events_url":"https://api.github.com/users/neoeinstein/events{/privacy}","received_events_url":"https://api.github.com/users/neoeinstein/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":9,"created_at":"2017-03-27T12:29:39Z","updated_at":"2017-08-14T13:50:01Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/xyncro/chiron/pulls/81","html_url":"https://github.com/xyncro/chiron/pull/81","diff_url":"https://github.com/xyncro/chiron/pull/81.diff","patch_url":"https://github.com/xyncro/chiron/pull/81.patch"},"body":"This is a rather large, breaking rewrite of Chiron 7. The API has been altered and the code optimized. \r\n\r\nThis is nearly ready to go. It just needs a bit of API cleanup. API documentation should follow shortly after.\r\n\r\n## What has changed?\r\n\r\n* `serialize` and `deserialize` now refer to the combination of `encode >> format` / `parse >> decode`, while `encode` and `decode` take the place of the existing `serialize` and `deserialize`.\r\n* Encode and decode operations have moved to submodules `Json.Encode` and `Json.Decode`, respectively.\r\n* Operations which work on inference have moved to `Chiron.Inference`. Opening that module will give access to those values.\r\n* All core encode/decode operations are now exposed and easily accessible, including encoders/decoders which require configuration.\r\n* It is a common practice to include a module alias to make it easier to access the encode/decode functions:\r\n\r\n  ```fsharp\r\n  module E = Chiron.Json.Encode\r\n  module D = Chiron.Json.Decode\r\n  module EI = Chiron.Inference.Json.Encode\r\n  module DI = Chiron.Inference.Json.Decode\r\n  ```\r\n\r\n  This makes it easier to invoke functions such as `E.listWith E.int`\r\n* Chiron's result now has cases `JPass` and `JFail`\r\n* Errors are handled as a tree structure. This allows for efficient handling of both short-circuit scenarios and full-error reporting scenarios, as the tree structure allows for efficient concatenation of two existing error trees.\r\n* Chiron uses a set of pre-defined failures to better relay the causes of failures\r\n* Errors can now be tagged so that errors which are deep in a structure can be reported and identified in the source more efficiently.\r\n* JSON Objects are now dealt with specially, in order to optimize the two cases in which they are generally used: encoding and decoding.\r\n  * During encoding, objects are treated as simple append logs, allowing for O(1) addition of properties to the object. These logs do not do deduplication automatically, so that must be done if there is a possibility that multiple properties with the same name may be written to the same object. (This was considered an unlikely case). This also allows preserving of ordering in which values are added to an object to determine ordering when serialized. Lookups on a write object incur a penalty, being O(n).\r\n  * During decoding, objects are read into a Map, allowing for O(log n) lookup of values in the object. Additions to a read object incur a penalty, being O(n log n).\r\n* JSON Arrays are now read in correctly. In Chiron 6, arrays were reversed compared to their appearance in the serialized form.\r\n* Introduces the concept of reading/writing a mixin, which is a function that can read elements from/add elements to a JSON object. Also provides a function to allow converting such an object reader/writer to a Json reader/writer where the object is not serialized as a mixin.\r\n* Specific functional API changes:\r\n  * `Json.readWith` -> `D.required`\r\n  * `Json.readWithOrDefault` when default was `None` -> `D.optional`\r\n  * `Json.readWithOrDefault decoder key def` -> `D.optional decoder key >=> D.withDefault def` (with operators) or `D.optional decoder key |> Decoder.compose (D.withDefault def)`\r\n  * `Json.writeWith` -> `E.required`\r\n  * `Json.writeWithUnlessDefault` when default was `None` -> `E.optional`\r\n  * `Json.writeWithUnlessDefault` -> `E.ifNotEqual`\r\n  * `Json.read` -> `DI.required`\r\n  * `Json.readOrDefault` when default was `None` -> `DI.optional`\r\n  * `Json.readOrDefault decoder key def` -> `DI.optional decoder key >=> D.withDefault def` (with operators) or `DI.optional decoder key |> Decoder.compose (D.withDefault def)`\r\n  * `Json.write` -> `EI.required`\r\n  * `Json.writeUnlessDefault` when default was `None` -> `EI.optional`\r\n  * `Json.writeUnlessDefault` -> `EI.ifNotEqual`\r\n* Encoding is now a simple chain of function calls rather than wrapped in a computation expression\r\n* Decoding objects can be done with a new computation expression: `jsonReader` or by using the provided operators.\r\n* Includes an example of a JSON Schema serializer/deserializer, tested against the Swagger Open API 2.0 JSON Schema spec.\r\n* Many of the serializers/deserializers have been altered to ensure that they create as few allocations inline as possible, generally by pushing such allocations to module initialization as opposed to occurring at runtime.\r\n\r\n## Use of struct `Result`\r\n\r\nThe use of a new struct `Result` type was considered. What I found was that operations using a struct `Result` incurred a performance penalty. While they were nearly allocation free, I found that they could increase the execution time by 100%, resulting in execution times worse than before starting the optimization work. Finding this penalty unacceptable, I chose to use a reference-type `Result` for Chiron.\r\n\r\n## Benchmarks\r\nBenchmarks were run on my local machine.\r\n``` ini\r\n\r\nBenchmarkDotNet=v0.10.3.0, OS=Microsoft Windows 6.1.7601\r\nProcessor=Intel(R) Core(TM) i7-4800MQ CPU 2.70GHz, ProcessorCount=8\r\nFrequency=2630683 Hz, Resolution=380.1294 ns, Timer=TSC\r\ndotnet cli version=1.0.1\r\n  [Host]    : .NET Core 4.6.25009.03, 64bit RyuJIT\r\n  MediumRun : .NET Core 4.6.25009.03, 64bit RyuJIT\r\n\r\nJob=MediumRun  LaunchCount=2  TargetCount=15  \r\nWarmupCount=10  \r\n\r\n```\r\nWhen parsing:\r\n\r\n |     Method |       Name |            Mean |        StdDev | Scaled | Scaled-StdDev |      Gen 0 |     Gen 1 |    Gen 2 | Allocated |\r\n |----------- |----------- |---------------- |-------------- |------- |-------------- |----------- |---------- |--------- |---------- |\r\n | **Chiron_Old** |      **error** |       **8.1883 us** |     **0.0412 us** |   **1.00** |          **0.00** |     **2.9989** |         **-** |        **-** |  **10.57 kB** |\r\n | Chiron_New |      error |       2.6634 us |     0.0088 us |   0.33 |          0.00 |     0.2899 |         - |        - |    1.2 kB |\r\n | **Chiron_Old** |    **fparsec** |      **33.5850 us** |     **0.4901 us** |   **1.00** |          **0.00** |     **7.5765** |         **-** |        **-** |  **28.15 kB** |\r\n | Chiron_New |    fparsec |      15.8960 us |     0.0748 us |   0.47 |          0.01 |     1.2044 |         - |        - |   6.19 kB |\r\n | **Chiron_Old** | **prettyuser** |      **80.4490 us** |     **1.1692 us** |   **1.00** |          **0.00** |    **17.8385** |         **-** |        **-** |  **64.96 kB** |\r\n | Chiron_New | prettyuser |      39.0248 us |     0.1181 us |   0.49 |          0.01 |     3.8818 |         - |        - |  16.59 kB |\r\n | **Chiron_Old** |     **social** | **199,318.9076 us** |   **822.8354 us** |   **1.00** |          **0.00** | **30300.0000** | **2133.3333** | **212.5000** | **183.99 MB** |\r\n | Chiron_New |     social | 107,816.0160 us | 1,767.2977 us |   0.54 |          0.01 |  5025.0000 | 1204.1667 |        - |  30.07 MB |\r\n | **Chiron_Old** |       **user** |      **78.1747 us** |     **0.3920 us** |   **1.00** |          **0.00** |    **17.8385** |         **-** |        **-** |  **64.96 kB** |\r\n | Chiron_New |       user |      39.0666 us |     0.1309 us |   0.50 |          0.00 |     3.8493 |         - |        - |  16.59 kB |\r\n\r\nWhen formatting:\r\n\r\n|     Method |       Name |               Mean |          StdDev | Scaled | Scaled-StdDev |     Gen 0 |    Gen 1 | Allocated |\r\n |----------- |----------- |------------------- |---------------- |------- |-------------- |---------- |--------- |---------- |\r\n | **Chiron_New** |      **error** |        **723.8419 ns** |       **4.5279 ns** |   **0.62** |          **0.00** |    **0.2764** |        **-** |     **949 B** |\r\n | Chiron_Old |      error |      1,174.1918 ns |       4.2226 ns |   1.00 |          0.00 |    0.5435 |        - |   1.86 kB |\r\n | **Chiron_New** |    **fparsec** |      **3,519.4741 ns** |      **21.3731 ns** |   **0.56** |          **0.00** |    **0.5147** |        **-** |   **2.23 kB** |\r\n | Chiron_Old |    fparsec |      6,327.1602 ns |      34.3687 ns |   1.00 |          0.00 |    2.1891 |        - |    8.1 kB |\r\n | **Chiron_New** | **prettyuser** |      **7,513.2868 ns** |      **85.5356 ns** |   **0.53** |          **0.01** |    **0.9603** |        **-** |   **4.24 kB** |\r\n | Chiron_Old | prettyuser |     14,257.2669 ns |      60.9807 ns |   1.00 |          0.00 |    4.1951 |        - |   15.7 kB |\r\n | **Chiron_New** |     **social** | **17,340,841.3744 ns** | **453,392.4016 ns** |   **0.62** |          **0.02** |  **383.3333** |        **-** |    **9.6 MB** |\r\n | Chiron_Old |     social | 28,075,461.0672 ns | 162,895.2767 ns |   1.00 |          0.00 | 4245.8333 | 479.1667 |  33.53 MB |\r\n | **Chiron_New** |       **user** |      **7,464.4183 ns** |      **32.8148 ns** |   **0.53** |          **0.00** |    **0.9928** |        **-** |   **4.24 kB** |\r\n | Chiron_Old |       user |     14,202.8109 ns |      47.7378 ns |   1.00 |          0.00 |    4.2277 |        - |   15.7 kB |\r\n\r\nWhen encoding:\r\n\r\n |                         Method |          Mean |     StdDev | Scaled | Scaled-StdDev |  Gen 0 | Allocated |\r\n |------------------------------- |-------------- |----------- |------- |-------------- |------- |---------- |\r\n |                Inline_Explicit |   120.0114 ns |  2.4677 ns |   0.06 |          0.00 | 0.0876 |     295 B |\r\n |              InModule_Explicit |   119.0491 ns |  0.7453 ns |   0.06 |          0.00 | 0.0877 |     295 B |\r\n |                Inline_Inferred |   114.5410 ns |  0.6669 ns |   0.06 |          0.00 | 0.0798 |     271 B |\r\n |              InModule_Inferred |   112.0574 ns |  0.8763 ns |   0.05 |          0.00 | 0.0801 |     271 B |\r\n | Version6_ComputationExpression | 2,051.8482 ns | 23.7067 ns |   1.00 |          0.00 | 0.8143 |   2.86 kB |\r\n |             Version6_Operators | 2,102.9870 ns |  8.4327 ns |   1.03 |          0.01 | 0.9384 |   3.25 kB |\r\n\r\nAnd when decoding:\r\n\r\n |                                  Method |          Mean |     StdDev | Scaled | Scaled-StdDev |  Gen 0 | Allocated |\r\n |---------------------------------------- |-------------- |----------- |------- |-------------- |------- |---------- |\r\n |   Inline_Explicit_ComputationExpression |   425.6357 ns | 16.5782 ns |   0.45 |          0.02 | 0.1993 |     702 B |\r\n | InModule_Explicit_ComputationExpression |   397.6754 ns |  3.0964 ns |   0.42 |          0.01 | 0.1759 |     630 B |\r\n |   Inline_Inferred_ComputationExpression | 1,138.4921 ns |  6.4098 ns |   1.21 |          0.02 | 0.2106 |     822 B |\r\n | InModule_Inferred_ComputationExpression | 1,115.1637 ns |  8.1718 ns |   1.18 |          0.02 | 0.1897 |     750 B |\r\n |               Inline_Explicit_Operators |   356.4605 ns |  3.0266 ns |   0.38 |          0.01 | 0.1757 |     590 B |\r\n |             InModule_Explicit_Operators |   328.4038 ns |  1.0029 ns |   0.35 |          0.00 | 0.1291 |     447 B |\r\n |               Inline_Inferred_Operators | 1,094.8538 ns |  5.3998 ns |   1.16 |          0.01 | 0.1849 |     734 B |\r\n |             InModule_Inferred_Operators | 1,038.3364 ns |  7.5264 ns |   1.10 |          0.01 | 0.1076 |     495 B |\r\n |          Version6_ComputationExpression |   942.9645 ns | 10.9486 ns |   1.00 |          0.00 | 0.9374 |    3.1 kB |\r\n |                      Version6_Operators |   950.2550 ns |  5.0865 ns |   1.01 |          0.01 | 0.9811 |   3.24 kB |\r\n\r\nI have also tested the new version by implementing a JSON schema parser, and then verifying the amount of time required for each stage: parse, decode, encode, format, parse and decode, encode and format, and a full round-trip. The sample target used was the Swagger 2.0 JSON schema, measuring in at about 39 kiB.\r\n\r\n |          Method |          Mean |     StdDev |    Gen 0 |   Gen 1 | Allocated |\r\n |---------------- |-------------- |----------- |--------- |-------- |---------- |\r\n |           Parse | 1,117.3922 us |  6.4157 us |  28.9063 |       - |  397.4 kB |\r\n |          Decode | 1,999.4045 us | 14.0837 us | 223.9583 | 20.8333 |   1.09 MB |\r\n |  ParseAndDecode | 3,232.3001 us | 49.3304 us | 103.1250 |       - |   1.48 MB |\r\n |          Format |   216.1864 us |  0.8882 us |  26.2370 |       - | 115.24 kB |\r\n |          Encode |   379.0652 us |  1.5211 us |  89.7135 | 14.5182 |  360.3 kB |\r\n | EncodeAndFormat |   567.4594 us |  3.1673 us | 127.2135 |       - | 463.12 kB |\r\n |       RoundTrip | 3,908.8632 us | 78.9472 us | 228.1250 |       - |   1.95 MB |\r\n"},"comment":{"url":"https://api.github.com/repos/xyncro/chiron/issues/comments/322195101","html_url":"https://github.com/xyncro/chiron/pull/81#issuecomment-322195101","issue_url":"https://api.github.com/repos/xyncro/chiron/issues/81","id":322195101,"user":{"login":"neoeinstein","id":18165,"avatar_url":"https://avatars1.githubusercontent.com/u/18165?v=4","gravatar_id":"","url":"https://api.github.com/users/neoeinstein","html_url":"https://github.com/neoeinstein","followers_url":"https://api.github.com/users/neoeinstein/followers","following_url":"https://api.github.com/users/neoeinstein/following{/other_user}","gists_url":"https://api.github.com/users/neoeinstein/gists{/gist_id}","starred_url":"https://api.github.com/users/neoeinstein/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/neoeinstein/subscriptions","organizations_url":"https://api.github.com/users/neoeinstein/orgs","repos_url":"https://api.github.com/users/neoeinstein/repos","events_url":"https://api.github.com/users/neoeinstein/events{/privacy}","received_events_url":"https://api.github.com/users/neoeinstein/received_events","type":"User","site_admin":false},"created_at":"2017-08-14T13:50:01Z","updated_at":"2017-08-14T13:50:01Z","body":"For that line, you can use `D.required (D.option D.string) \"interventionDeadline\"`. That will require that \"interventionDeadline\" exists and is either `null` or a `string`. If you want \"interventionDeadline\" to be optional and also handle `null`, then use `D.optional (D.option D.string) \"interventionDeadline\".\r\n\r\nPart of the API change was to ensure that all three of the optional cases were covered (accept missing, accept null, accept null or missing)."}}
{"actor":{"display_login":"neoeinstein"}}