From 3d3dbff36263633034f6de97879890ba999cb2d6 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Fri, 16 Feb 2024 17:49:04 +0100 Subject: [PATCH] Update stdlib for ADTs Update `std.enum` and various places of the stdlib to take into account that now `std.typeof foo == 'Enum` might mean an enum tag or an enum variant. Adds `std.enum.is_enum_tag` and `std.enum.is_enum_variant` to the stdlib as well. --- core/stdlib/std.ncl | 70 ++++++++++++++++++++++++++++++++++++------- doc/manual/merging.md | 7 ++--- 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/core/stdlib/std.ncl b/core/stdlib/std.ncl index 7c64e04823..36bf678aac 100644 --- a/core/stdlib/std.ncl +++ b/core/stdlib/std.ncl @@ -1549,7 +1549,7 @@ error ``` "% - = std.contract.from_predicate std.is_enum, + = std.contract.from_predicate is_enum_tag, TagOrString | doc m%%" @@ -1557,8 +1557,8 @@ converted to an enum tag. `TagOrString` is typically used in conjunction with an enum type, to - accept both actual enum tags and tags represented as strings (e.g. coming from a - JSON serialization). + accept both actual enum tags and tags represented as strings (e.g. + coming from a JSON serialization). **Warning**: contracts are applied in-order. The pattern described here requires that `TagOrString` is applied before the corresponding enum @@ -1567,7 +1567,7 @@ # Examples - ``` nickel + ```nickel let Schema = { protocol | std.enum.TagOrString @@ -1589,15 +1589,63 @@ ``` "%% = fun label value => + let label = + std.contract.label.with_message + "expected either a string or an enum tag" + label + in %typeof% value |> match { 'String => %enum_from_str% value, - 'Enum => value, - _ => - std.contract.blame_with_message - "expected either a string or an enum tag" - label, + 'Enum => + if is_enum_variant value then + std.contract.blame label + else + value, + _ => std.contract.blame label, }, + + is_enum_tag + : Dyn -> Bool + | doc m%" + Checks if a value is an enum tag. Enum variants (applied to an argument) + aren't considered enum tags. + + # Examples + + ```nickel + std.enum.is_enum_tag 'foo + => true + std.enum.is_enum_tag 'FooBar + => true + std.enum.is_enum_tag "tag" + => false + std.enum.is_enum_tag ('Foo "arg") + => false + ``` + "% + = fun value => std.is_enum value && !(%enum_is_variant% value), + + is_enum_variant + : Dyn -> Bool + | doc m%" + Checks if a value is an enum variant. Bare enum tags (not applied to an + argument) aren't considered enum variants. + + # Examples + + ```nickel + std.enum.is_enum_variant ('Foo "arg") + => true + std.enum.is_enum_variant ('Http {version = "1.1"}) + => true + std.enum.is_enum_variant 'foo + => false + std.enum.is_enum_variant [1, 2, 3] + => false + ``` + "% + = fun value => %enum_is_variant% value, }, function = { @@ -2360,7 +2408,9 @@ || type == 'Number || type == 'Bool || type == 'String - || type == 'Enum + # note that `type == 'Enum` isn't sufficient, as it includes enum + # variants + || std.enum.is_enum_tag value ), NonEmpty diff --git a/doc/manual/merging.md b/doc/manual/merging.md index 54014dd4c8..77298846fe 100644 --- a/doc/manual/merging.md +++ b/doc/manual/merging.md @@ -621,9 +621,9 @@ error: missing definition for `required_field2` 8 │ & { foo.required_field1 = "here" } │ ------------------------ in this record │ - ┌─ :3012:18 + ┌─ :3062:18 │ -3012 │ = fun x y => %deep_seq% x y, +3062 │ = fun x y => %deep_seq% x y, │ ------------ accessed here ``` @@ -640,8 +640,7 @@ let Port ( fun value => std.is_number value - && value - % 1 == 0 + && value % 1 == 0 && value >= 0 && value <= 65535 )