nickel icon indicating copy to clipboard operation
nickel copied to clipboard

Consider adding common contracts to the standard library

Open twitchyliquid64 opened this issue 1 month ago • 2 comments

Spawned off a discussion in discord: https://discord.com/channels/1174731094726295632/1179430745727586407/1432836461505417448

TL;DR: We should provide commonly-used contracts in the standard library. These should be

  • Composable
  • well thought-out
  • somewhat minimal (i.e. not 'the whole kitchen sink')

The two things I find myself reaching for are this:

1. "If X, apply contract Y"

Nickel snippet - what im using now
    RuntimeDep | doc "Possible values for a runtime dependency entry." = fun Contract =>
        std.contract.custom (fun label value =>
            match {
                {ty = 'Builder, ..} => std.contract.check BuildSpec label value,
                {ty = 'Subset, ..} => std.contract.check Subset label value,
                _ => 'Error {
                    message = "Expected Input type",
                },
            }
            value
        ),

2. "field X or field Y but not both"

Nickel snippet which @jneem cooked up
let OneOrTheOther | String -> String -> Dyn = fun fld1 fld2 =>
  std.contract.from_validator (fun value =>
    if !std.is_record value then
      'Error { message = "expected a record" }
    else
      let
        has1 = std.record.has_field fld1 value,
        has2 = std.record.has_field fld2 value,
      in
      if has1 && has2 then
        'Error { message = "`%{fld1}` and `%{fld2}` cannot both be defined" }
      else if !has1 && !has2 then
        'Error { message = "either `%{fld1}` or `%{fld2}` must be defined" }
      else
        'Ok
  )
in
{
  foo = 1,
  bar = 1,
} | OneOrTheOther "foo" "bar"

twitchyliquid64 avatar Oct 28 '25 21:10 twitchyliquid64

I'd say the Nullable contract from the docs is another nice candidate.

NicholasPini avatar Oct 29 '25 08:10 NicholasPini

Yeah, nullable is a basic commodity that should be available. The only reason why it's not the case yet is because I had a secret plan to try to make it possible to handle nullable value in a type-safe way as well (using equi-recursive types and flow-sensitive typing), but I'm not sure how relevant it is these days with algebraic data types (and it would also be rather non-trivial to design and implement properly). Maybe we can just have Nullable in the stdlib, a to_option: forall a. Nullable a -> [| 'Some a, 'None |], and call it a day.

yannham avatar Oct 29 '25 13:10 yannham