Default arguments (aka parameters with a default value) are often a nice way to provide a complex operation with a lot of variations but allow the client to use it in a simple form when no complexity is required.
Also, it allows to extends the signature of existing operations while being API-compatible with previous versions.
This PR introduces default arguments to Nit as another tool in the toolbox of library designers.
The specification is quite simple:
* a nullable parameter can be ignored, the value of the argument will default to null
~~~nit
fun foo(a: Int, b: nullable Int) do ...
foo(1)
foo(1, null) # equivalent thing
~~~
* default parameters can be anywhere (not necessarily at the end)
~~~nit
fun foo(a: nullable Int, b: Int) do ...
foo(1)
foo(null, 1) # equivalent thing
~~~
* when there is multiple possible associations between arguments and parameters, arguments will be associated to the leftmost (first) parameter first.
~~~nit
fun foo(a, b: nullable Int) do ...
foo(1)
foo(1, null) # equivalent thing
~~~
* if there is a vararg, all other parameters are mandatory (no default)
~~~nit
fun foo(a: Int..., b: nullable Int) do ...
foo(1) # Error: minimum 2 arguments (we chose that varargs require a minimum of 1 argument, but this old choice is a different issue)
foo(1, 2) # OK
foo([1]..., 2) # equivalent thing
~~~
* the last parameter of an assignment method (that is the right value of the assignment) is never default. For POLA reasons, the last argument is distinguished and syntactically mandatory, so the association with the parameter is always direct and unambiguous.
~~~nit
fun foo=(a, b: nullable Int) do ...
foo = 1
foo(null) = 1 # equivalent thing
~~~
I chose this specification because it uses the nullable facility of Nit in a nice way and is somewhat semantic: `null` means nothing to say, or keep it blank.
I prefer it over the specification of other languages that is to declare a specific default value on the declaration of parameters.
* no need not to update the grammar
* no need to specify the language of the default value (any expressions? only literal? other?)
* no need to specify when and by who the default value is evaluated (and what is the value of self? are the other parameters usable in the expression? etc.)
* no need to specify how to deal with default values on redefinitions in a OO point of view (do I inherit the default value? can I `super` the default value?)
* no need to expose the expressions used as default values in the metamodel
* no need to expose the expressions used as default values in the doc
Most of these things make the language less KISS (obviously), and less POLA because whatever spec we can choose for these points, 1) not 100% of user will find them obvious; and 2) for some cases the wanted behavior will not be the specified one.
Note that in Python, while they allow to define a specific default value on parameters, they recommend to use `null` as a default to avoid most of these aforementioned problems.
Note2: this PR was unlocked by #1269 and #1268
Pull-Request: #1280
Reviewed-by: Etienne M. Gagnon <egagnon@j-meg.com>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Ait younes Mehdi Adel <overpex@gmail.com>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
var param = msignature.mparameters[i]
var j = map.map.get_or_null(i)
if j == null then
+ # default value
+ res.add(null_instance)
continue
end
if param.is_vararg and map.vararg_decl > 0 then
var param = msignature.mparameters[i]
var j = map.map.get_or_null(i)
if j == null then
+ # default value
+ res.add(null_instance)
continue
end
if param.is_vararg and map.vararg_decl > 0 then
# Example: for "(a: Int, b: Bool..., c: Char)" #-> vararg_rank=1
var vararg_rank: Int is noinit
- # The number or parameters
+ # The number of parameters
fun arity: Int do return mparameters.length
+ # The number of non-default parameters
+ #
+ # The number of default parameters is then `arity-min_arity`.
+ #
+ # Note that there cannot be both varargs and default prameters, thus
+ # if `vararg_rank != -1` then `min_arity` == `arity`
+ fun min_arity: Int
+ do
+ if vararg_rank != -1 then return arity
+ var res = 0
+ for p in mparameters do
+ if not p.is_default then res += 1
+ end
+ return res
+ end
+
redef fun to_s
do
var b = new FlatBuffer
# Is the parameter a vararg?
var is_vararg: Bool
+ # Is the parameter a default one?
+ var is_default: Bool
+
redef fun to_s
do
if is_vararg then
do
if not self.mtype.need_anchor then return self
var newtype = self.mtype.resolve_for(mtype, anchor, mmodule, cleanup_virtual)
- var res = new MParameter(self.name, newtype, self.is_vararg)
+ var res = new MParameter(self.name, newtype, self.is_vararg, self.is_default)
return res
end
for param in sig.mparameters do
var ret_type = param.mtype
- var mparameter = new MParameter(param.name, ret_type, false)
+ var mparameter = new MParameter(param.name, ret_type, false, ret_type isa MNullableType)
mparameters.add(mparameter)
end
initializers.add(npropdef.mpropdef.mproperty)
var paramname = npropdef.mpropdef.mproperty.name.substring_from(1)
var ret_type = npropdef.mpropdef.static_mtype
if ret_type == null then return
- var mparameter = new MParameter(paramname, ret_type, false)
+ var mparameter = new MParameter(paramname, ret_type, false, ret_type isa MNullableType)
mparameters.add(mparameter)
var msetter = npropdef.mwritepropdef
if msetter == null then
if pd isa MMethodDef then
# Get the signature resolved for the current receiver
var sig = pd.msignature.resolve_for(mclassdef.mclass.mclass_type, mclassdef.bound_mtype, mclassdef.mmodule, false)
- mparameters.add_all sig.mparameters
+ # Because the last parameter of setters is never default, try to default them for the autoinit.
+ for param in sig.mparameters do
+ if not param.is_default and param.mtype isa MNullableType then
+ param = new MParameter(param.name, param.mtype, param.is_vararg, true)
+ end
+ mparameters.add(param)
+ end
else
# TODO attributes?
abort
var mparameters = new Array[MParameter]
for i in [0..param_names.length[ do
- var mparameter = new MParameter(param_names[i], param_types[i], i == vararg_rank)
+ var is_default = false
+ if vararg_rank == -1 and param_types[i] isa MNullableType then
+ if i < param_names.length-1 or accept_special_last_parameter then
+ is_default = true
+ end
+ end
+ var mparameter = new MParameter(param_names[i], param_types[i], i == vararg_rank, is_default)
if nsig != null then nsig.n_params[i].mparameter = mparameter
mparameters.add(mparameter)
end
if mwritepropdef != null then
var name: String
name = n_id2.text
- var mparameter = new MParameter(name, mtype, false)
+ var mparameter = new MParameter(name, mtype, false, false)
var msignature = new MSignature([mparameter], null)
mwritepropdef.msignature = msignature
end
node.labels.add "MParameter"
node["name"] = mparameter.name
node["is_vararg"] = mparameter.is_vararg
+ node["is_default"] = mparameter.is_default
node.out_edges.add(new NeoEdge(node, "TYPE", to_node(mparameter.mtype)))
return node
end
var name = node["name"].to_s
var mtype = to_mtype(model, node.out_nodes("TYPE").first)
var is_vararg = node["is_vararg"].as(Bool)
- var mparameter = new MParameter(name, mtype, is_vararg)
+ var is_default = node["is_default"].as(Bool)
+ var mparameter = new MParameter(name, mtype, is_vararg, is_default)
mentities[node.id.as(Int)] = mparameter
return mparameter
end
return null
end
else if args.length != msignature.arity then
- modelbuilder.error(node, "Error: expected {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
- return null
+ if msignature.arity == msignature.min_arity then
+ modelbuilder.error(node, "Error: expected {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+ return null
+ end
+ if args.length > msignature.arity then
+ modelbuilder.error(node, "Error: expected at most {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+ return null
+ end
+ if args.length < msignature.min_arity then
+ modelbuilder.error(node, "Error: expected at least {msignature.min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+ return null
+ end
end
#debug("CALL {unsafe_type}.{msignature}")
# Associate each parameter to a position in the arguments
var map = new SignatureMap
+ var setted = args.length - msignature.min_arity
var vararg_decl = args.length - msignature.arity
var j = 0
for i in [0..msignature.arity[ do
var param = msignature.mparameters[i]
+ if param.is_default then
+ if setted > 0 then
+ setted -= 1
+ else
+ continue
+ end
+ end
var arg = args[j]
map.map[i] = j
j += 1
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import standard::kernel
+
+class A
+ fun foo(a, b: nullable Int, c, d: Int, e,f: nullable Int)
+ do
+ if a == null then '\n'.output else a.output
+ if b == null then '\n'.output else b.output
+ c.output
+ d.output
+ if e == null then '\n'.output else e.output
+ if f == null then '\n'.output else f.output
+ '-'.output
+ '\n'.output
+
+ end
+
+ fun bar(a,b,c: nullable Int)
+ do
+ if a == null then '\n'.output else a.output
+ if b == null then '\n'.output else b.output
+ if c == null then '\n'.output else c.output
+ '-'.output
+ '\n'.output
+ end
+
+ fun bar=(a,b,c: nullable Int)
+ do
+ if a == null then '\n'.output else a.output
+ if b == null then '\n'.output else b.output
+ if c == null then '\n'.output else c.output
+ '-'.output
+ '\n'.output
+ end
+
+ fun [](a,b,c: nullable Int): Int
+ do
+ if a == null then '\n'.output else a.output
+ if b == null then '\n'.output else b.output
+ if c == null then '\n'.output else c.output
+ '-'.output
+ '\n'.output
+ return 0
+ end
+
+ fun []=(a,b,c: nullable Int): Int
+ do
+ if a == null then '\n'.output else a.output
+ if b == null then '\n'.output else b.output
+ if c == null then '\n'.output else c.output
+ '-'.output
+ '\n'.output
+ return 0
+ end
+
+ fun +(a: nullable Int): Int
+ do
+ if a == null then '\n'.output else a.output
+ '-'.output
+ '\n'.output
+ return 0
+ end
+end
+
+var a = new A
+var x
+
+#alt1#a.foo
+#alt1#a.foo(2)
+a.foo(1,2)
+a.foo(1,2,3)
+a.foo(1,2,3,4)
+a.foo(1,2,3,4,5)
+a.foo(1,2,3,4,5,6)
+#alt1#a.foo(1,2,3,4,5,6,7)
+
+a.bar
+a.bar(1)
+a.bar(1,2)
+a.bar(1,2,3)
+#alt1#a.bar(1,2,3,4)
+
+a.bar= 10
+a.bar(1) = 20
+a.bar(1,2) = 30
+#alt1#a.bar(1,2,3) = 40
+
+#alt2# x = a[]
+x = a[1]
+x = a[1,2]
+x = a[1,2,3]
+#alt2#x = a[1,2,3,4]
+
+#alt2#a[] = 10
+a[1] = 20
+a[1,2] = 30
+#alt1#a[1,2,3] = 40
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class A
+ fun foo(a: nullable Int, bs: Int..., c: nullable Int)
+ do
+ if a == null then '\n'.output else a.output
+ for b in bs do
+ ' '.output
+ b.output
+ end
+ if c == null then '\n'.output else c.output
+ '-'.output
+ '\n'.output
+
+ end
+
+ fun bar(a: nullable Int, bs: Int...)
+ do
+ if a == null then '\n'.output else a.output
+ for b in bs do
+ ' '.output
+ b.output
+ end
+ '-'.output
+ '\n'.output
+ end
+
+ fun bar=(a:nullable Int, bs: Int..., c: nullable Int)
+ do
+ if a == null then '\n'.output else a.output
+ for b in bs do
+ ' '.output
+ b.output
+ end
+ if c == null then '\n'.output else c.output
+ '-'.output
+ '\n'.output
+ end
+
+ fun [](a: nullable Int, bs: Int...): Int
+ do
+ if a == null then '\n'.output else a.output
+ for b in bs do
+ ' '.output
+ b.output
+ end
+ '-'.output
+ '\n'.output
+ return 0
+ end
+
+ fun []=(a: nullable Int, bs: Int..., c: nullable Int): Int
+ do
+ if a == null then '\n'.output else a.output
+ for b in bs do
+ ' '.output
+ b.output
+ end
+ if c == null then '\n'.output else c.output
+ '-'.output
+ '\n'.output
+ return 0
+ end
+end
+
+var a = new A
+var x
+
+#alt1#a.foo
+#alt1#a.foo(2)
+#alt1#a.foo(1,2)
+a.foo(1,2,3)
+a.foo(1,2,3,4)
+
+#alt1#a.bar
+#alt1#a.bar(1)
+a.bar(1,2)
+a.bar(1,2,3)
+
+#alt1#a.bar = 10
+#alt1#a.bar(1) = 20
+a.bar(1,2) = 30
+a.bar(1,2,3) = 40
+
+#alt1#x = a[1]
+x = a[1,2]
+x = a[1,2,3]
+
+#alt1#a[1] = 20
+a[1,2] = 30
+a[1,2,3] = 40
+a[1,2,3,4] = 50
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import standard::kernel
+
+class A
+ var mandatory: Int
+ var optional: nullable Int
+
+ fun foo
+ do
+ '-'.output
+ '\n'.output
+ mandatory.output
+ if optional == null then
+ ' '.output
+ '\n'.output
+ else
+ optional.output
+ end
+ end
+end
+
+class B
+ super A
+ var optional_b: nullable Int
+ var mandatory_b: Int
+ redef fun foo
+ do
+ super
+ if optional_b == null then
+ ' '.output
+ '\n'.output
+ else
+ optional_b.output
+ end
+ mandatory_b.output
+ end
+end
+
+class C
+ super B
+ autoinit optional_b, mandatory_b, mandatory
+end
+
+var a
+
+#alt1#a = new A
+#alt1#a.foo
+
+a = new A(1)
+a.foo
+
+a = new A(1,2)
+a.foo
+
+#alt1#a = new A(1,2,3)
+#alt1#a.foo
+
+#alt1#a = new B(1)
+#alt1#a.foo
+
+a = new B(1,4)
+a.foo
+
+a = new B(1,2,4)
+a.foo
+
+a = new B(1,2,3,4)
+a.foo
+
+#alt1#a = new B(1,2,3,4,5)
+#alt1#a.foo
+
+#alt1#a = new C(1)
+#alt1#a.foo
+
+a = new C(4,1)
+a.foo
+
+a = new C(3,4,1)
+a.foo
+
+#alt1#a = new C(1,2,3,4)
+#alt1#a.foo
--- /dev/null
+
+
+1
+2
+
+
+-
+1
+
+2
+3
+
+
+-
+1
+2
+3
+4
+
+
+-
+1
+2
+3
+4
+5
+
+-
+1
+2
+3
+4
+5
+6
+-
+
+
+
+-
+1
+
+
+-
+1
+2
+
+-
+1
+2
+3
+-
+
+
+10
+-
+1
+
+20
+-
+1
+2
+30
+-
+1
+
+
+-
+1
+2
+
+-
+1
+2
+3
+-
+1
+
+20
+-
+1
+2
+30
+-
--- /dev/null
+1
+ 2
+3
+-
+1
+ 2
+ 3
+4
+-
+1
+ 2
+-
+1
+ 2
+ 3
+-
+1
+ 2
+30
+-
+1
+ 2
+ 3
+40
+-
+1
+ 2
+-
+1
+ 2
+ 3
+-
+1
+ 2
+30
+-
+1
+ 2
+ 3
+40
+-
+1
+ 2
+ 3
+ 4
+50
+-
--- /dev/null
+alt/base_arg_default2_alt1.nit:81,3--5: Error: expected at least 3 argument(s) for `foo(a: nullable Int, bs: Int..., c: nullable Int)`; got 0. See introduction at `base_arg_default2_alt1::A::foo`.
+alt/base_arg_default2_alt1.nit:82,3--5: Error: expected at least 3 argument(s) for `foo(a: nullable Int, bs: Int..., c: nullable Int)`; got 1. See introduction at `base_arg_default2_alt1::A::foo`.
+alt/base_arg_default2_alt1.nit:83,3--5: Error: expected at least 3 argument(s) for `foo(a: nullable Int, bs: Int..., c: nullable Int)`; got 2. See introduction at `base_arg_default2_alt1::A::foo`.
+alt/base_arg_default2_alt1.nit:87,3--5: Error: expected at least 2 argument(s) for `bar(a: nullable Int, bs: Int...)`; got 0. See introduction at `base_arg_default2_alt1::A::bar`.
+alt/base_arg_default2_alt1.nit:88,3--5: Error: expected at least 2 argument(s) for `bar(a: nullable Int, bs: Int...)`; got 1. See introduction at `base_arg_default2_alt1::A::bar`.
+alt/base_arg_default2_alt1.nit:92,3--5: Error: expected at least 3 argument(s) for `bar=(a: nullable Int, bs: Int..., c: nullable Int)`; got 1. See introduction at `base_arg_default2_alt1::A::bar=`.
+alt/base_arg_default2_alt1.nit:93,3--5: Error: expected at least 3 argument(s) for `bar=(a: nullable Int, bs: Int..., c: nullable Int)`; got 2. See introduction at `base_arg_default2_alt1::A::bar=`.
+alt/base_arg_default2_alt1.nit:97,5--8: Error: expected at least 2 argument(s) for `[](a: nullable Int, bs: Int...): Int`; got 1. See introduction at `base_arg_default2_alt1::A::[]`.
+alt/base_arg_default2_alt1.nit:101,1--9: Error: expected at least 3 argument(s) for `[]=(a: nullable Int, bs: Int..., c: nullable Int): Int`; got 2. See introduction at `base_arg_default2_alt1::A::[]=`.
--- /dev/null
+alt/base_arg_default_alt1.nit:81,3--5: Error: expected at least 2 argument(s) for `foo(a: nullable Int, b: nullable Int, c: Int, d: Int, e: nullable Int, f: nullable Int)`; got 0. See introduction at `base_arg_default_alt1::A::foo`.
+alt/base_arg_default_alt1.nit:82,3--5: Error: expected at least 2 argument(s) for `foo(a: nullable Int, b: nullable Int, c: Int, d: Int, e: nullable Int, f: nullable Int)`; got 1. See introduction at `base_arg_default_alt1::A::foo`.
+alt/base_arg_default_alt1.nit:88,3--5: Error: expected at most 6 argument(s) for `foo(a: nullable Int, b: nullable Int, c: Int, d: Int, e: nullable Int, f: nullable Int)`; got 7. See introduction at `base_arg_default_alt1::A::foo`.
+alt/base_arg_default_alt1.nit:94,3--5: Error: expected at most 3 argument(s) for `bar(a: nullable Int, b: nullable Int, c: nullable Int)`; got 4. See introduction at `base_arg_default_alt1::A::bar`.
+alt/base_arg_default_alt1.nit:99,3--5: Error: expected at most 3 argument(s) for `bar=(a: nullable Int, b: nullable Int, c: nullable Int)`; got 4. See introduction at `base_arg_default_alt1::A::bar=`.
+alt/base_arg_default_alt1.nit:110,1--13: Error: expected at most 3 argument(s) for `[]=(a: nullable Int, b: nullable Int, c: nullable Int): Int`; got 4. See introduction at `base_arg_default_alt1::A::[]=`.
--- /dev/null
+alt/base_arg_default_alt2.nit:101,7: Syntax Error: unexpected ']'.
--- /dev/null
+-
+1
+
+-
+1
+2
+-
+1
+
+
+4
+-
+1
+2
+
+4
+-
+1
+2
+3
+4
+-
+1
+
+
+4
+-
+1
+
+3
+4
--- /dev/null
+alt/base_arg_default_autoinit_alt1.nit:59,5--7: Error: expected at least 1 argument(s) for `init(mandatory: Int, optional: nullable Int)`; got 0. See introduction at `standard::Object::init`.
+alt/base_arg_default_autoinit_alt1.nit:68,5--7: Error: expected at most 2 argument(s) for `init(mandatory: Int, optional: nullable Int)`; got 3. See introduction at `standard::Object::init`.
+alt/base_arg_default_autoinit_alt1.nit:71,5--7: Error: expected at least 2 argument(s) for `init(mandatory: Int, optional: nullable Int, optional_b: nullable Int, mandatory_b: Int)`; got 1. See introduction at `standard::Object::init`.
+alt/base_arg_default_autoinit_alt1.nit:83,5--7: Error: expected at most 4 argument(s) for `init(mandatory: Int, optional: nullable Int, optional_b: nullable Int, mandatory_b: Int)`; got 5. See introduction at `standard::Object::init`.
+alt/base_arg_default_autoinit_alt1.nit:86,5--7: Error: expected at least 2 argument(s) for `init(optional_b: nullable Int, mandatory_b: Int, mandatory: Int)`; got 1. See introduction at `standard::Object::init`.
+alt/base_arg_default_autoinit_alt1.nit:95,5--7: Error: expected at most 3 argument(s) for `init(optional_b: nullable Int, mandatory_b: Int, mandatory: Int)`; got 4. See introduction at `standard::Object::init`.