Jump to: navigation, search

VJET/Typing Functions with VJETDoc

< VJET
Revision as of 14:08, 5 December 2012 by Earlyster.gmail.com (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

JavaScript gets its work done with Functions. As we already saw, Function is a native type in JavaScript. Similar to Array, JavaScript has more than one way to create them.


Example JavaScript functions declaration/expression

Here are the 3 flavors of Function creation in JavaScript:

// Function declaration
 function max(a, b) {
   return (a>b) ? a : b ;
}
// Function Expression via declaration w/Assignment
 var myMax = function(a, b) {
   return (a>b) ? a : b ;
}
// Function Expression via Function constructor
var f = new Function('a', 'b', 'return (a>b) ? a : b')
var out = vjo.sysout.println ;
out(max(10, 20)) ;
out(f(10, 20)) ;
out(myMax(10, 20)) ;

console>
20.0
20.0
20.0

The last two are really Function expressions. Those expressions could be assigned to a local variable as in the example or could be passed as an argument to another function etc...

VJETDoc Structure for Functions

Here is the Vjet structure for a function declaration:
Optional-Arg-Suffix: "?"

  • Used for optional arguments.
  • Optional arguments must be consecutive
  • An optional argument may be the last argument declaration
  • An series of optional arguments can end with a variable argument declaration


Variable-Args-Suffix: "..."

  • Used for variable arguments.
  • Only one allowed in a function declaration
  • A variable argument may be the only argument declaration
  • If present must be the last argument declaration

Argument: Type [Optional-Arg-Suffix | Variable-Args-Suffix] [Simple-Name]
Arguments: A single Argument or comma delimited list of Argument's.
Throws: A single Type or comma delimited list of thrown Type's
Function-Declaration: [Type] SimpleName "( " [Arguments] ")" ["throws" Throws]
Function-Ref: "(" Function-Declaration ")"

Function Ref and Function Expression

The last syntax, Function-Ref allows us to define a function itself as the return type from another function or be defined as the type of another functions argument(s). The parentheses are used as a grouping mechanism for clarity and are part of the required syntax. When we talk about a function-expression, we mean a function that is declared as an expression. This expression could be assigned to a variable, be an argument to some other function (like a callback) or even be returned from another function (possibly a construction-style pattern).

Typing the return type of a function

Remember that a VJETDoc can be placed before or after the function keyword in our definition. We can use the < or > to say what direction our Vjet declaration should apply to. Let's try some simple examples where we are simply defining the return type of a function.

//> Date now()
function now(){
return new Date ;
}
 
function now2() { //< Date now2()
return new Date ;
}
 
/*> Date now3() ;
* Returns the current date
*/
function now3() { return new Date ; }
 
function now4(){
/*< Date now4() ; return the current Date */
return new Date ;
}
 
var d = now() ; //< Date
 
d = now2() ;
d = now3() ;
d = now4() ;
var out = vjo.sysout.println ;
out(now()) ;
out(now2()) ;
out(now3()) ;
out(now4()) ;

console>
Tue Jan 18 2011 17:02:14 GMT-0800 (PST)
Tue Jan 18 2011 17:02:14 GMT-0800 (PST)
Tue Jan 18 2011 17:02:14 GMT-0800 (PST)
Tue Jan 18 2011 17:02:14 GMT-0800 (PST)

In these last examples, we could easily replace the return type Date with String or Number for example as those are valid types we can declare with. Of course we would need to change the return expression to match those types.

Typing the arguments and return type of a function

We can also type the arguments that a function expects. The following examples combine functions with a return type and typed arguments.

//> Date getDate(String)
function getDate(mmDDyyyy){
  return new Date(mmDDyyyy) ;
}
 
//> Date getDate(String mmDDyyyy)
function getDate(mmDDyyyy){
  return new Date(mmDDyyyy) ;
}
 
//> Date f(String)
function getDate(mmDDyyyy){
  return new Date(mmDDyyyy) ;
}
 
//> Date f(String o)
function getDate(mmDDyyyy){
  return new Date(mmDDyyyy) ;
}

Function and argument names matching VJETDoc?

We can see that from a typing perspective we really care about the return type and what the argument type is. Thus, from the VJETDoc perspective, it is not required to have the function name or argument name(s) match between the comment and the actual function implementation. In the case of the argument, the argument name can actually be omitted. We in general, keep the names matching (reads better etc...) and always include argument names. Also, keeping them in sync is valuable when you start thinking about generated documentation that would come from just your Vjet type information. In those cases it's a good idea to have meaningful/unique names for your functions and to include the meaning/intent of functions arguments supplied. There are cases when you are typing a function expression that there is no name of the function. This is another reason we relax the requirement that names match.

More about Function Expressions and their usage

How about function expressions? We already saw a function expression typed and assigned to a variable (global or local). We also have the cases where a function expression can be passed as an argument, be assigned to an Object Literal member or even be returned from another function. Vjet structured types also use function expression to assign to globals, properties and prototypical properties. We basically then have Function declaration -- function max(a, b) { ... } and function expression function(a, b) { ... } and new Function('a', 'b', '...'). Function expressions can be created using the function keyword or by using the Function constructor. We include the Function constructor style for completeness since in almost all JavaScript you see you may not come across it. However, you will see function declarations and function keyword style usage all over the place.

Calling one function with another function as argument

The following example shows 4 flavors of passing a function as an argument to another function.

//> void dateProvider( (void f(Date)) needsAdate)
function dateProvider(needsAdate) {
needsAdate(new Date) ;
}
//> void sayDateDay(Date date)
function sayDateDay(date) {
var day = date.getDay() ;
vjo.sysout.println(day) ;
}
var sayDateDay2 = sayDateDay ; //< void sayDate(Date date)
 
// we will now call dateProvider with:
// 1. Our declared function sayDate
// 2. Our function assigned to a local variable sayDate2
// 3. A function expression using keywork function
// 4. A function expression using the Function constructor
dateProvider(sayDateDay) ; // declared function
dateProvider(sayDateDay2) ;// function from local variable
dateProvider( // function expression from function keyword
    //> void function(Date date)
    function(date){
      var day = date.getDay() ;
      vjo.sysout.println(day) ;
}
);
dateProvider( // function expression from Function constructor
   //> void function(Date date)
   new Function(
   'date',
   'var day = date.getDay() ;
   vjo.sysout.println(day) ;')
);

console>
2.0
2.0
2.0
2.0


A function returning a function

We can type the return type of a function with VJETDoc.

/*> (boolean f(int)) maxer(int max) ;
* Our function generator will return a function that will return
* true if the passed in value is greater than max, else returns false
*/
function maxer(max) \{
//> boolean f(int) ; this is the function we return
function f(value) {
var mymax = max ; //< int
return value > mymax ;
\}
return f;
\}
var out = vjo.sysout.println ;
var mymaxer10 = maxer(10) ; //< boolean f(int)
out(mymaxer10(9)) ; // should be false, 9 < 10
out(mymaxer10(11)) ; // should be true, 11 > 10

console>
false
true

Typing a function in an Object Literal

A function can be assigned to the member name in an Object Literal. Since the member name is the actual function name, we use the function expression syntax.

var contract = {
rate: 22.45, //< Number
location: 'Boston', //< String
open: true, //< boolean
//> boolean isCostly()
isCostly: function(){
return this.rate > 30.00 ;
}
}
var costly = contract.isCostly() ; //< boolean
var out = vjo.sysout.println ;
out(costly) ; // false.  rate is < 30.00
contract.rate = 58.70 ;
out(contract.isCostly()) ; // true.  rate is > 30.00

console>
false
true


Function Assignment

We saw where we declared a function and assigned it to a local variable in a single statement. This was a convenience Vjet supports (kind of a 2-for-1) declaration; you get to declare the function and variable in one shot. We can however, declare a variable (local or global) and in another statement assign it a function.

var max ; //< Number max(Number a, Number b)
function f(a, b) { //< Number max(Number, Number)
return (a > b) ? a : b ;
}
max = f ; // should be ok since function signatures are compatible
var out = vjo.sysout.println ;
out(f(10, 20)) ;
out(max(10, 20)) ;

console>
20.0
20.0


Note that we should be able to use the force-cast VJETDoc style to help us reduce the amount of physical typing we do. We could define the function first, which gives us a better type blueprint to verify against. We can then simply say the variable, max, is that type. The example following shows this approach.

//> Number f(Number, Number)
function f(a, b) {
return (a > b) ? a : b ;
};
var max ; //<< ; we don't need to retype the type comment
var out = vjo.sysout.println ;
out(f(10, 20)) ;
out(max(10, 20)) ;

console>
20.0
20.0

Functions and Globals

Just as we were able to assign a function expression to a local variable, we use the exact same VJETDoc syntax when assigning to a global variable.


Optional Arguments

An optional argument means it can be omitted. The syntax for identifying optional arguments: Type-Name "?" (the type name followed by a question mark) Examples of the optional argument w/type are: Date?, int?, String[]? An optional argument can appear in any poisiont argument declaration. However, once an optional argument is defined a few rules must be followed:

  • Any subsequent arguments must also be optional
  • A special case of the last argument being a variable-argument is allowed


All of the following are valid declarations using optional arguments:

//> void f(int?)
//> void f(int,  String?)
//> void f(int,  String,  Date?, Date?)
//> void f(int?, String?)
//> void f(int?, String...)
//> void f(int,  String?, Date...)

The following are examples of invalid declarations using optional arguments. The issues will be with optional args must be consecutive and a consecutive set of optional args can have the last argument being var-args:

//> void f(int?, String)
//> void f(int,  String?, Date)
//> void f(int?, String..., Date?)

A key to understanding arguments in general is to think that each argument is a column. Each column has a type. In the case of var-args, you can think of a never-ending set of columns each with the type. The notion of final applies to the argument declaration. If the final is on a var-args then all of those columns will be final. The final qualifier does not change any of the previously mentioned ways the arguments are declared. Let's go through some examples to see exactly what a given signature actually means. One technique is to flatten out the signature so that each permutation is directly expressed with its own comment. When doing this flattening, we need to understand how to expand a given argument.

//> void f() ; no interpretation necessary
f() ;
f(1) ; // error since f() takes no arguments
 
//> void f(int, String)
f(0) ; // error since f() takes 2 arguments
f(1) ; // error since f() takes 2 arguments
f(1, 'hello') ; // ok, 2 args and they are compatible types
f(1, 'hello', 2) ; // error since f() takes 2 arguments
 
//> void f(int?)
f() ; // ok since we handle 0 args
f(1) ; // ok since if we have 1 arg, it must be an int
f(1, 2) ; // error since f() can take 0-1 arguments
 
//> void f(int?, String)

This signature is invalid. If we have an optional argument, any following arguments must be also optional, or end with a var-args.

//> void f(int?, String?)
f() ;           // ok
f(1) ;          // ok
f('abc') ;      // error - we must have concrete previous optional args
f(1, 'abc') ;   // ok
f(1, 'abc', 2); // error - function takes at most 2 arguments
//> void f(int, String?)
f() ;           // error - f() takes 1 or 2 arguments
f(1) ;          // ok
f(1, 'abc') ;   // ok
f('abc') ;      // error - String not compatible with int
f(1, 'abc', 2); // error - f() takes 1 or 2 argument

Let's take a simple optional-args declaration and flatten its permutations out into a series of equivalent VJETDocs. The ability to have more than 1 VJETDoc per function is called overloading, is supported by Vjet and is described later in this document.

//> void f(String?

is the same as

//> void f()
//> void(String)
//> void f(int, String?)

is the same as


//> void f(int)
//> void f(int, String)
//> void f(int?, String?)

is the same as

//> void f()
//> void f(int)
//> void f(int, String)

Variable Arguments

JavaScript naturally supports variable arguments. The full set of arguments is always available via the built-in variable, "arguments". Thus a JavaScript programmer will then look at the number of arguments (and often use the typeof or instanceof operators) on the arguments to determine how to process the varying amount coming in. From a typing standpoint, we want to identify that a variable number of arguments is possible and what type they should be. The syntax for identifying variable arguments is: Type-Name "..." (the type name followed by 3 dots) ex: Date..., int..., String[]... etc... If variable arguments are specified a few rules apply:

  • There must be only one in a given function signature
  • Must be the last position of the set of arguments
  • It is possible to have just variable arguments and no preceding arguments


void f(Number...)
void f(Number... ids)
void f(boolean, Number...)
void f(boolean ok, Number... ids)

examples of incorrect variable arguments:

void f(Number..., int) - variable arguments must be in last position
void f(Number..., Date...) - only one variable arguments per declaration
void f(Number..., Date?) - variable arguments must be in last position

We follow with some variable arguments declarations with sample of valid and invalid JavaScript statements using that function.

//> f(int...)
f() ; // ok
f(1) ; // ok
f(1, 2, 3) ; // ok
f(1, 2, 'abc) ; // error - 'abc' is not an int
 
//> f(String..., int)

Signature is invalid. If we have var-args, it must be the last declaration.


//> f(int, String...)
f() ; // error - f() takes 1 or more arguments
f(1) ; // ok
f(1, 'abc') ; // ok
f(1, 'abc', 'jdk') ; // ok
f('abc', 'jdk') ; // error - first argument must be int
f(1, 'abc', 2) ; // error - int (2) is not compatible with String
 
//> f(int?, String...)
f() ; // ok
f(1) ; // ok
f('abc') ; // error first arg, if any must be int
f(1, 'abc') ; // ok
f(1, 'abc', 'xyz') ; // ok
f(1, 'abc', 2) ; // error - int (2) is not compatible with String

ll of the following are valid combinations of fixed-args, optional-args and var-args:

//> void f()
//> void f(int)
//> void f(int, String)
//> void f(int?)
//> void f(int?, String?)
//> void f(int, String?)
//> void f(int, String, Date?)
//> void f(int...)
//> void f(int, String...)
//> void f(int, String?, Date...)
//> void f(int?, String?, Date...)

Multi-type Arguments

VJETDoc syntax supports the notion of a single argument having more than one valid type. This can be expressed in an overload as such:

//> void f(int)
//> void f(String

We see that the first argument can be an int or String. We can also declare this scenario as:

//> void f({int|String})

We allow for this syntax to enable the easier typing of functions that often vary by one argument but have multiple arguments after them. Also, existing libraries such as Dojo have many functions related to DOM processing that nearly always take a Node or String type. We can cut down on the amount of physical typing and space required to declare such functions. A multi-type argument is always a physical position in the signature and in the cases with rules regarding optional-args and variable-args is the same as if the argument only supported one type. The following are example of illegal VJETDoc regarding multi-type arguments, optional-arguments and variable-arguments.

//> void f(String..., {int | boolean
variable-arguments must be last
//> void f(int?, {int | boolean}

optional arg must be followed by another optional arg or end with a variable-args

Relationship of physical function declaration and VJETDocs

JavaScript always has the internal built-in arguments available for a function to access. Thus, not all functions that really take arguments formally declare them. What set of arguments a function can really handle is based on its internal implementation and not necessarily the arguments in the function(...) portion of the declaration. If this is so, then should VJETDoc about functions require the physical functions number of arguments to match? The answer is no. The following example shows how two functions with the same Vjet signature have differing number of arguments in their physical declaration but have exactly the same behavior.


//> Number sumTwo(Number, Number)
function sumTwo(){
var total = 0 ;
// we use internal built-in variable arguments
if (arguments[0]) total += arguments[0] ;
if (arguments[1]) total += arguments[1] ;
return total ;
}
//> Number sum(Number a, Number b)
function sum(a, b){
var total = 0 ;
// we use the formal arguments from the function declaration
if (a) total += a ;
if (b) total += b ;
return total ;
}
var out = vjo.sysout.println ;
out(sumTwo()) ;
out(sumTwo(1)) ;
out(sumTwo(1, 2)) ;
out(sumTwo(1, 2, 3)) ; // third arg is ignored
out('-------------') ;
out(sum()) ;
out(sum(1)) ;
out(sum(1, 2)) ;
out(sum(1, 2, 3)) ; // third arg is ignored

console>
0.0
1.0
3.0
3.0



0.0
1.0
3.0
3.0


Also, a function that is declared as vjo.NEEDSIMPL never defines any arguments, yet you can declare the argument(s) types in the VJETDoc . vjo.NEEDSIMPL is a placeholder value when declaring an abstract Vjet type function. Abstract types/functions are described in another document. Suffice to say that the placeholder function does not have any arguments.

vjo.ctype('Ex')
.protos({
add: vjo.NEEDS''IMPL, //< Number add(Number, Number)
sum: vjo.NEEDS''IMPL //< Number add(Number...)
})
.endType();

Can a physical function declaration, define more arguments than are in the VJETDoc? Yes. Arguments beyond those declared in the comment are given the type Object.

vjo.ctype('abc.Ax5') //< public
.props(
{
//>public void main(String... args)
main : function(args){}
 
//> void f(int)
function f(a, b, c) {
    a++
}
}
}) .protos({
}) .endType();

Overloaded Functions

In Object Oriented programming, there is a concept called overloading. Without delving into why this is a good idea, let's say that Vjet allows you to define more than one VJETDoc to describe your function. As we saw with optional-args and var-args, you get a similar effect; more than 1 type signature is behinds the scenes in use for a single function. By allowing more than 1 VJETDoc per function, we can do similar declarations. The following function f() is overloaded with 3 function signatures. Function g(), has only 1 function signature, but handles the exact same argument combinations that f() does. If this is so, then why have overloading? Well, it turns out that there are combinations of arguments that we could not be able to describe with optional-args and/or var-args.


//> void f()
//> void f(int)
//> void f(int, String)
function f() { }
//> void g(int?, String?)
function g() {

The following overloaded function shows that not only can we have differing argument combinations but that we also can have differing return types.

//> Number add(Number, Number)
//> String add(String, String)
function add(a, b){
return a + b ; }
e thus can invoke the add(...) function with the following arguments and be type safe:
add(10, 20) ;
add('cat', 'bird') ;

Note that having more than 1 VJETDoc per function does not produce a permutation of argument types at each position. Each signature is evaluated itself for completeness and does not impact any other signatures that are present. In our last example we would not allow the following:

add(10, 'cat') ;
add('cat', 10) ;

It's possible to have overloads where some signatures return a type and others don't (they return void)

More on function signatures

We can have cases where a function may or not return something based on what arguments it takes. When we have these conditions, it is not possible for Vjet to determine if you have properly coded your function implementation to honor such permutations. It will however, verify that if you do have a return, it will not violate the known return types (the function could be overloaded). In the following example, the return new Date, would be an error since the return types for f() are either int or String.


//> int f(boolean)//> String f(String)
function f(a) \{
if (typeof a == 'boolean') return 10 ; // ok from int f(boolean)
if (typeof a == 'string') return 'xxx' ; // ok from String f(String)
return new Date ; // should fail since neither int or String
}

Access Control with Overloading

You must have the same access control with all of your overloaded signatures. The following are examples of overloading and having the same access control:

//> public void f()
//> public void f(int, String)
function f()
{ ... }
 
//> private int f()
//> private void f(Number)
function f()
{... }

The following are examples of illegal overloading due to mismatched access control. Note that having no access control means all of your overload signatures must also have no access control:

//> void f()
//> public void f(String)
function f()
{ ... }
 
//> public int f()
//> private String f(Number)
function f()
{ ... }

Return types ambiguous for same args

When you are defining your overloads, you should be thinking that each signature is unique. Thus when calling the function for a given set of arguments that match that signature you get the return type associated with that signature.

We have seen that with optional arguments and/or variable-arguments, we get an implied overload. Thus a single signature can really expand to multiple signatures behind the scenes. Because of this you need to make sure that at the single declaration level you remain unique.

Here are some example where you get ambiguous bindings:

//> void f(int)
//> String f(int?


We can see that String f(int?) is really the same asString f() String f(int)

Since we already have a signature for f(int) saying it should return void, we now have a collision.