🟡 Handling argument errror for adder function :
var guard = require("nutzen").guard
var add = (x,y) => x + y
var adder = guard
.arn(2,() => console.log("Error: only accepts 2 arugument"))
.ar(2,add)
.def(null) // always provide a default value when all match fails.
adder(1,3) // 4
If you notice we do not check if x, y are numbers, we can fix this by using .whn
(when not ) in our error handling :
var bothNum = (x,y) => (((typeof x) is "number") && ((typeof y) is "number"))
var argE = () => console.log("only accepts 2 arugument")
var typeE = () => console.log("argument type has to be number")
var add = (x,y) => x + y
var adder = guard
.arn(2,argE)
.whn(bothNum,typeE)
.ar(2,add)
.def(null)
adder(1,2) // 3
This now allows us to cover both typeError
and argumentError
for the adder function.
Guards are wrappers that are commonly found in functional programming languages, which help in making sure error handling code does not clutter core procedures, these come especially handy in languages such as javascript that have virtually no type checks.
They also encourage efficient use of pattern matching to structure code and external API.
The API surface is kept large to cover various forms of requirements :
CORE : ar,wh,arwh,cap,arcap,def,clone
EXTRAS : whn,arn,arwhn,arnwh,arnwhn
The document makes use of Hindley-Milner notation, the below notes are provided for some quick guide :
-
number|[num...]
👉🏼 This means either a number or an array of numbers. -
pos_int|[pos_int...]
👉🏼 same as (1) but instead of number you have positive integers. -
(function|any)
👉🏼 it means either a function or any value, the last argument of allnutzen.guard
is of this type. -
{ validator },{ execution }
👉🏼 this shows that the function accepts two arguments, one if called the validator, and the other is calledexecution
function. -
{arglen}
👉🏼 describes arguments that specifyargument.length
values to match. -
(-> bool),(function|any)
👉🏼 here the function accepts two arguments. The first one accepts a function with a return value of typebool
while the second argument isany
function or justany
value. -
A summary of API and their argument specification has been provided in two tables for quick reference.
[ LEGENDS ]
PI = pos_int | [pos_int,...]
FT = validator | nutzen.types
FT = (-> bool) | nutzen.types
FA = function | any
F = function
✅ ar :: {arglen},{execution}
↪️ ar :: (pos_int|[pos_int...]),(function|any)
↪️ ar :: PI,FA
✅ ar :: {object}
First argument can be an array of positive integer or just single positive integer, which describes how many arguments are acceptable before running the function in the second argument.
Second argument can also just be an any
, in which case, we just return an any
without executing a function.
✅ wh :: {validator},{execution}
↪️ wh :: (nutzen.types|function),(function|any)
↪️ wh :: (nutzen.types|(-> bool)),(function|any)
↪️ wh :: FT,FA
First function should return a boolean, which determines if second function is run or not.
nutzen.types
validator can also be used.
✅ cap/2 :: {validator},{execution}
↪️ cap/2 :: (nutzen.types|function),(function|any)
↪️ cap/2 :: (nutzen.types|(-> false|any)),(function|any)
↪️ cap/2 :: FT,FA
✅ cap/3 :: {validator},{handleError},{execution}
↪️ cap/3 :: (nutzen.types|function),function,(function|any)
↪️ cap/3 :: (nutzen.types|(-> false|any)),function,(function|any)
↪️ cap/3 :: FT,F,FA
There are situations where the validator function does some side-effects ( eg. finding a file in a directory ) and there is a need to capture the result of these side-effects as values.
It's important to ensure two things :
-
validator doesn't run multiple times.
-
provide captured value from validator to the execution function.
.cap
is just like .wh
but gives us the option of ensuring both these conditions are met.
- return value of the validator function is sent to the execution function, as the first argument.
If the validator function in .cap
returns false
then nutzen.guard
jumps to the next validator, in any other value type including undefined, nutzen
adds this value as the first argument to the execution function.
✅ arcap/1 :: {object}
✅ arcap/2 :: {arglen},{execution}
✅ arcap/3 :: {arglen},{validator},{execution}
✅ arcap/4 :: {arglen},{validator},{lastview},{execution}
arcap
combines the operations of .ar
and .cap
while also accepting object notation.
✅ arcap/3 :: {arglen},{validator},{execution}
↪️ arcap/3 :: (pos_int|[pos_int,...]),(nutzen.types|function),(function|any)
↪️ arcap/3 :: (pos_int|[pos_int,...]),(nutzen.types|(-> false|any)),(function|any)
↪️ arcap/3 :: PI,FT,FA
First argument can be an array of positive integer or just single positive integer , which describes how many arguments are acceptable before running the validator function in the second argument.
{validator}
can return false|any
where any
is treated as value to be captured to be used by {execution}
.
in case {validator}
is nutzen.types
object, then the corrosponding .value
is used as data to be captured by {execution}
.
✅ arcap/4 :: {arglen},{validator},{handleError},{execution}
↪️ arcap/4 :: (pos_int|[pos_int,...]),(nutzen.types|function),(function|any)
↪️ arcap/4 :: -----------------------,(nutzen.types|(-> false|any|[bool,any])),(-> void|any),(function|any)
↪️ arcap/4 :: PI,FT,F,FA
In the trivial case, validator functions return just true
or false
, but as we have to deal with more complicated error handling scenarios, a better return signature would be a tuple, where the second value is relevant metadata (in case of error) or just data.
If the {handleError}
function returns void
then nutzen.guard
jumps to the next validator, for any other value X it terminates the loop and returns X.
✅ whn :: {validator},{execution}
↪️ whn :: (nutzen.types|function),(function|any)
↪️ whn :: (nutzen.types|(-> bool)),(function|any)
↪️ whn :: FT,FA
Same as wh
but {execution}
runs if {validator}
return false
.
✅ arn :: {arglen},{execution}
↪️ arn :: (pos_int|[pos_int...]),(function|any)
↪️ arn :: PI,FA
Same as ar
but the functions added is only run if the arguments.length
doesn't match the values provided in {arglen}
.
✅ arwh :: {arglen},{validator},{execution}
↪️ arwh :: (pos_int|[pos_int...]),(nutzen.types|function),(function|any)
↪️ arwh :: ----------------------,(nutzen.types|(-> bool)),------------
↪️ arwh :: PI,FT,FA
A combination of ar
and wh
operators, first argument is number of argument we are ready to accept, first function is a validator just like what we would use with .wh
and last function is what would run if the first two conditions are met.
✅ arwhn :: {arglen},{validator},{execution}
↪️ arwhn :: (pos_int|[pos_int...]),(nutzen.types|function),(function|any)
↪️ arwhn :: ----------------------,(nutzen.types|(-> bool)),------------
↪️ arwhn :: PI,FT,FA
Just like arwh
but only runs if the validator function return false
.
✅ arnwh :: {arglen},{validator},{execution}
↪️ arnwh :: (pos_int|[pos_int...]),(nutzen.types|function),(function|any)
↪️ arnwh :: ----------------------,(nutzen.types|(-> bool)),------------
↪️ arnwh :: PI,FT,FA
Just like arwh
but runs if the ar
does not match and validator returns true
.
✅ arnwh :: {arglen},{validator},{execution}
↪️ arnwh :: (pos_int|[pos_int...]),(nutzen.types|function),(function|any)
↪️ arnwh :: ----------------------,(nutzen.types|(-> bool)),------------
↪️ arnwh :: PI,FT,FA
Just like arwh
but runs if both ar
and wh
do not match.
def :: (function|any)
In case nutzen
is unable to match anything, the return value of the function added to .def
is used.
It's also possible to just provide a static value or object as default.
clone :: void
Alongside the nutzen.guard.immutable
namespace, nutzen.guard
also has a handy .clone
operator in case there needs to be seperation in the validator chain.
When using fluent API pattern, the underlying object is kept by default to be mutable, to aid in efficiency, but there are rare situations where validator chains share a common parent chain.
-
Each
nutzen.guard
object always has to end with a.def
. -
all the methods also accept non-functions as their last value, in case only static values are returned.
-
nutzen.guard
also accepts validators created usingnutzen.types
.
It's important to write as few primitive functions as possible and reduce the overall number of if..else
.
These functions also completes the algebra of the core operators.
Instead of matching on arguments on different validators, we can use a single objects to match against argument number.
This feature is available to be used on ar
,arwh
,arcap
and arwhn
.
Lets suppose we have the following example :
var xop = nutzen.guard
var show = xop
.ar(1,() => console.log("one"));
.ar(2,() => console.log("two"));
.def()
show(null); // one
show(null,null); // two
we can rewrite it using an object:
var xop = nutzen.guard
var ob = {
1:() => console.log("one")
2:() => console.log("two")
}
var show = xop
.ar(ob);
.def()
show(null); // one
show(null,null); // two
[ LEGENDS ]
PI = pos_int | [pos_int,...]
FT = validator | nutzen.types
FT = (-> bool) | nutzen.types
FA = function | any
F = function
🟢 Table 1 - method names and their types.
METHOD NAME EXPANDED NAME INPUT TYPE
----------------------------------------------------------------
ar argument PI,FA
wh when FT,FA
whn when not FT,FA
cap/2 capture FT,FA
cap/3 ... FT,F,FA
arn args not PI,FA
arcap/3 argument capture PI,FT,FA
arcap/4 ... PI,FT,F,FA
arcap/2 ... PI,FA
arwh argument when PI,FT,FA
arnwh argument not when PI,FT,FA
arwhn argument when not PI,FT,FA
arnwhn argument not when not PI,FT,FA
----------------------------------------------------------------
def default FA
----------------------------------------------------------------
🟢 Table 2 - method types displayed with argument columns.
METHOD PIME TYPES
ARG 1 ARG 2 ARG 3 ARG 4
---------------------------------------------------------
ar arglen execution
wh validator execution
whn validator execution
cap/2 validator execution
cap/3 validator execution
arn arglen execution
arwh arglen validator execution
arcap/3 arglen validator execution
arcap/4 arglen validator handleError execution
arnwh arglen validator execution
arwhn arglen validator execution
arnwhn arglen validator execution
def function|any
In case immutable chain is needed, nutzen.guard
offers immutability through nutzen.guard.immutable
namespace.
var guard = nutzen.guard.immutable
var init = guard
.def(=> console.log ("wrong number of arguments"))
var add2 = init.ar(2,(x,y)=> x + y)
var add3 = init.ar(3,(x,y,z)=> x + y + z)
console.log (add2 == add3) // false
It's common enough to want to apply the .ar
counting on a specific argument itself.
nutzen.guard.unary
is a namespace where the .ar
counting is done on the first argument.
The condition of course is that the first argument has to be array like.
By default exit function doesn't have debug logging enabled.
In case debug message is needed then .debug
(nutzen.guard.debug
) namespace can be used.