Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an easier way to check function params #153

Open
sparkprime opened this issue Apr 1, 2016 · 4 comments
Open

Add an easier way to check function params #153

sparkprime opened this issue Apr 1, 2016 · 4 comments

Comments

@sparkprime
Copy link
Contributor

@huggsboson asked "Any interest in static or dynamically checked type assertions on parameters?"

You're the first to draw attention to this.

We decided pretty early on that dynamic typing would be most approachable. It fits in better with the laziness and dynamic binding, and avoids all the syntactic overhead of type annotations. There was a previous language that used type inference (HM system) but people found the unification errors hard to understand. Adding some form of gradual typing into the language now would probably be a bit incongruous. However some middle ground might be to have a dynamic checking framework, e.g. maybe something that looks like:

local f(x, y) = 
    assert std.check({x: x, y: y}, { ... some spec here ... });
    ...

This is just a library addition That would allow consistent and (relatively) concise checking of function preconditions for libraries. It could also be conservatively interpreted by linting tools. We could add more syntax sugar to make it even more concise:

local f(x, y) =
    checkparams { ... some spec here ... };
    ....

The hardest problem here is defining what the spec should be -- how expressive is it, should it use some existing thing or subset of an existing thing (jsonschema is probably overkill).

@Duologic
Copy link
Contributor

I've played around with this a bit and this is what I came up with:

local checkParameters(o) =
  local failures = [
    'Parameter %s is invalid' % n
    for n in std.objectFields(o)
    if !o[n]
  ];
  local tests = std.all([
    o[n]
    for n in std.objectFields(o)
    if !o[n]
  ]);
  if tests
  then true
  else
    std.trace(
      std.join(
        '\n  ',
        ['\nInvalid parameters:']
        + failures
      ),
      false
    );

local customCheck(value) =
  std.member(['a', 'b'], value);

local f(num, str, enum) =
  {
    assert checkParameters({
      num: std.isNumber(num),
      str: std.isString(str),
      enum: customCheck(enum),
    }),
  };

f(1, 1, 'c')

Output looks like this:

TRACE: a.jsonnet:15 
Invalid parameters:
  Parameter enum is invalid
  Parameter str is invalid
RUNTIME ERROR: Object assertion failed.
	a.jsonnet:(29:5)-(33:7)	
	Checking object assertions	
	During manifestation	

More concise it could look like this:

local f(num, str, enum) =
    checkparams {
      num: std.isNumber,
      str: std.isString,
      enum: function(x) std.member(['a', 'b'], x),
    },
   ...

@Duologic
Copy link
Contributor

I went down the rabbit hole and brought back a jsonnet library: https://github.com/Duologic/validate-libsonnet

@CertainLach
Copy link
Contributor

CertainLach commented Jun 15, 2023

    checkparams {
      num: std.isNumber,

This approach doesn't allows to return what exactly is wrong with the parameter.
What about adding two builtins, and then trying to make syntax sugar on top of them?

std.context(contextStr, value) - adds an "context" line to the stack trace, same as the standard "During manifestation"
std.force(arr) - forces all array/object values, batching errors, i.e. 'std.force([error "1", error "2"])' should print both errors, instead of stopping on first.

This way we can both specify the parameter name on the call site and then throw the correct error message somewhere deeper in the call stack (This allows us to reuse existing asserts in the code).

@CertainLach
Copy link
Contributor

In jrsonnet, I had an experiment on adding such type-system like thing:

function(a: t.int, b: t.string, c: t.schema({...})) a + b

Being evaluated the same way as

function(a, b, c) local _a = t.int(a), _b = t.string(b), _c = t.schema({...})(c); force _a, _b, _c; _a + _b

But I can't find this experiment right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants