Named Parameters Support

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
MG
Reply | Threaded
Open this post in threaded view
|

Named Parameters Support

MG
Hi,

having recently explained why Groovy is my language of choice got me
thinking about the few areas of Groovy where I personally wish for / see
the potential for improvement during my daily development tasks.

I will start off with named paramters support, a topic that has been
moving more into my consciousness as the framework I develop at work
grew larger over time: I feel that the current named parameter support I
am aware of
(http://groovy-lang.org/objectorientation.html#_named_argument_constructor 
/ http://groovy-lang.org/objectorientation.html#_named_arguments ) is a
limited feature that fit relatively well with a purely dynamic language,
but is not nearly as well rounded / powerful as in other languages.

Do you have any plans to support named parameters such as they exist
e.g. in PL/SQL (to be honest, I never expected I would ever quote PL/SQL
as a language that is superior to Groovy in any aspect before I thought
about this ;-) ), where named paramters are just a convenient (and
safer) way to call a callable, without loosing type safety, which works
in a purely static manner, and without requiring a method to explicitely
deal with a Map as its argument.
In short, a way to call any Groovy method/ctor with a syntax like:

class Processor {
     Processor(String name, boolean writeableQ = false, boolean
extendableQ = true,  Number id = null, Worker defaultWorker = null,
Closure logCls = {  println it }) { ... }
     work(Worker worker = null, boolean dryRunQ = false, int
maxNrRetries = 10, boolean overwriteQ = false) { ... }
}

final processor= new Processor(name:"Task 1", id:1234) { log.debug "T1:
$it" }  // Closure as last argument can still be given outside of brackets
processor.work(maxNrRetries:99)


The grooviest thing of all would be, if there would be a way to keep the
flexibility of the map argument, and combine it with the named
arguments, to be able to e.g. define a different default set of default
values for a method/ctor. E.g.

static ParametersMap getDebugWorkParams() {
     return [ worker:dummyWorker, dryRunQ:true, maxNrRetries:0,
overwriteQ:true ]
}

processor.work(*(debugWorkParams + [worker:logOnlyWorker,
maxNrRetries:3])) // dryRunQ=true and overwriteQ=true comes from
debugWorkParams; worker and maxNrRetries are give explicitly in call


In my case such a feature would have two benefits:
1a) Make it easy to quickly add a final field to a root base class,
without having to do some a major ctor refactoring on all the child
classes (for which I typically don't have the time, meaning that I am
forced to add the field as non-final "for now", so I can modify it after
object creation where needed).
Note that this can, even if IntelliJ would not sometimes not get the
refactoring wrong (which can lead to hard to track bugs), not be covered
by better refactoring support: Not having to give all the values for the
parameters before the newly added parameter (but instead having them
take their default value) is not something a refactoring engine can supply.
1b) Similar to 1a), for adding parameters to method calls
2) Make the calling of ctors/methods safer in certain cases
a) When a ctor/method takes a lot of parameters of the same type, e.g.
boolean: createPackageSql(String name, boolean trimQ, boolean
indentLinesQ, boolean keepEmptyLinesIndentationQ, boolean
seperateFunctionsQ, boolean uppercaseKeywordsQ, boolean uppercaseNamesQ)
{ ... } *
b) When a framework function is called by a framework user, e.g. from a
script, or generally in an environment where there is little or no test
coverage, and the paramter order can easily be confused: e.g. method
which retrieves a DB item through 2 IDs, and getting an empty result set
is not an error - which ID has to be given first ? Easy, when calling
with named paramters is supported pe.retrieveTreeNodeRow(orgUnit:123,
orgItem:456) .

mg

*Here a parameter object might be the better choice. But typically the
number of paramters was small at the beginning, and grew over time, so
initially no need for a paramater object existed. Again the refactoring
time needed to introduce a parameter object, the fact that your class
namespace gets cluttered with helper structures, external framework
users will have to adapt their code (or you must maintain a backward
compatibility facade), etc make this less practical than it might
initially seem...



Reply | Threaded
Open this post in threaded view
|

Re: Named Parameters Support

Charles Monteiro-2
How about the same support as languages like Ruby and Smalltalk provide ?

On Sat, Jul 22, 2017 at 5:17 PM MG <[hidden email]> wrote:
Hi,

having recently explained why Groovy is my language of choice got me
thinking about the few areas of Groovy where I personally wish for / see
the potential for improvement during my daily development tasks.

I will start off with named paramters support, a topic that has been
moving more into my consciousness as the framework I develop at work
grew larger over time: I feel that the current named parameter support I
am aware of
(http://groovy-lang.org/objectorientation.html#_named_argument_constructor
/ http://groovy-lang.org/objectorientation.html#_named_arguments ) is a
limited feature that fit relatively well with a purely dynamic language,
but is not nearly as well rounded / powerful as in other languages.

Do you have any plans to support named parameters such as they exist
e.g. in PL/SQL (to be honest, I never expected I would ever quote PL/SQL
as a language that is superior to Groovy in any aspect before I thought
about this ;-) ), where named paramters are just a convenient (and
safer) way to call a callable, without loosing type safety, which works
in a purely static manner, and without requiring a method to explicitely
deal with a Map as its argument.
In short, a way to call any Groovy method/ctor with a syntax like:

class Processor {
     Processor(String name, boolean writeableQ = false, boolean
extendableQ = true,  Number id = null, Worker defaultWorker = null,
Closure logCls = {  println it }) { ... }
     work(Worker worker = null, boolean dryRunQ = false, int
maxNrRetries = 10, boolean overwriteQ = false) { ... }
}

final processor= new Processor(name:"Task 1", id:1234) { log.debug "T1:
$it" }  // Closure as last argument can still be given outside of brackets
processor.work(maxNrRetries:99)


The grooviest thing of all would be, if there would be a way to keep the
flexibility of the map argument, and combine it with the named
arguments, to be able to e.g. define a different default set of default
values for a method/ctor. E.g.

static ParametersMap getDebugWorkParams() {
     return [ worker:dummyWorker, dryRunQ:true, maxNrRetries:0,
overwriteQ:true ]
}

processor.work(*(debugWorkParams + [worker:logOnlyWorker,
maxNrRetries:3])) // dryRunQ=true and overwriteQ=true comes from
debugWorkParams; worker and maxNrRetries are give explicitly in call


In my case such a feature would have two benefits:
1a) Make it easy to quickly add a final field to a root base class,
without having to do some a major ctor refactoring on all the child
classes (for which I typically don't have the time, meaning that I am
forced to add the field as non-final "for now", so I can modify it after
object creation where needed).
Note that this can, even if IntelliJ would not sometimes not get the
refactoring wrong (which can lead to hard to track bugs), not be covered
by better refactoring support: Not having to give all the values for the
parameters before the newly added parameter (but instead having them
take their default value) is not something a refactoring engine can supply.
1b) Similar to 1a), for adding parameters to method calls
2) Make the calling of ctors/methods safer in certain cases
a) When a ctor/method takes a lot of parameters of the same type, e.g.
boolean: createPackageSql(String name, boolean trimQ, boolean
indentLinesQ, boolean keepEmptyLinesIndentationQ, boolean
seperateFunctionsQ, boolean uppercaseKeywordsQ, boolean uppercaseNamesQ)
{ ... } *
b) When a framework function is called by a framework user, e.g. from a
script, or generally in an environment where there is little or no test
coverage, and the paramter order can easily be confused: e.g. method
which retrieves a DB item through 2 IDs, and getting an empty result set
is not an error - which ID has to be given first ? Easy, when calling
with named paramters is supported pe.retrieveTreeNodeRow(orgUnit:123,
orgItem:456) .

mg

*Here a parameter object might be the better choice. But typically the
number of paramters was small at the beginning, and grew over time, so
initially no need for a paramater object existed. Again the refactoring
time needed to introduce a parameter object, the fact that your class
namespace gets cluttered with helper structures, external framework
users will have to adapt their code (or you must maintain a backward
compatibility facade), etc make this less practical than it might
initially seem...



--
Charles A. Monteiro
www.monteirosfusion.com
sent from the road
MG
Reply | Threaded
Open this post in threaded view
|

Re: Named Parameters Support

MG
I have used a lot of languages over the years, but I don't do Puppet in our team, so have not used Ruby myself, and have never programmed in Smalltalk. Could you elaborate on what named parameter features from Ruby/Smalltalk you had in mind ?

On 23.07.2017 01:00, Charles Monteiro wrote:
How about the same support as languages like Ruby and Smalltalk provide ?

On Sat, Jul 22, 2017 at 5:17 PM MG <[hidden email]> wrote:
Hi,

having recently explained why Groovy is my language of choice got me
thinking about the few areas of Groovy where I personally wish for / see
the potential for improvement during my daily development tasks.

I will start off with named paramters support, a topic that has been
moving more into my consciousness as the framework I develop at work
grew larger over time: I feel that the current named parameter support I
am aware of
(http://groovy-lang.org/objectorientation.html#_named_argument_constructor
/ http://groovy-lang.org/objectorientation.html#_named_arguments ) is a
limited feature that fit relatively well with a purely dynamic language,
but is not nearly as well rounded / powerful as in other languages.

Do you have any plans to support named parameters such as they exist
e.g. in PL/SQL (to be honest, I never expected I would ever quote PL/SQL
as a language that is superior to Groovy in any aspect before I thought
about this ;-) ), where named paramters are just a convenient (and
safer) way to call a callable, without loosing type safety, which works
in a purely static manner, and without requiring a method to explicitely
deal with a Map as its argument.
In short, a way to call any Groovy method/ctor with a syntax like:

class Processor {
     Processor(String name, boolean writeableQ = false, boolean
extendableQ = true,  Number id = null, Worker defaultWorker = null,
Closure logCls = {  println it }) { ... }
     work(Worker worker = null, boolean dryRunQ = false, int
maxNrRetries = 10, boolean overwriteQ = false) { ... }
}

final processor= new Processor(name:"Task 1", id:1234) { log.debug "T1:
$it" }  // Closure as last argument can still be given outside of brackets
processor.work(maxNrRetries:99)


The grooviest thing of all would be, if there would be a way to keep the
flexibility of the map argument, and combine it with the named
arguments, to be able to e.g. define a different default set of default
values for a method/ctor. E.g.

static ParametersMap getDebugWorkParams() {
     return [ worker:dummyWorker, dryRunQ:true, maxNrRetries:0,
overwriteQ:true ]
}

processor.work(*(debugWorkParams + [worker:logOnlyWorker,
maxNrRetries:3])) // dryRunQ=true and overwriteQ=true comes from
debugWorkParams; worker and maxNrRetries are give explicitly in call


In my case such a feature would have two benefits:
1a) Make it easy to quickly add a final field to a root base class,
without having to do some a major ctor refactoring on all the child
classes (for which I typically don't have the time, meaning that I am
forced to add the field as non-final "for now", so I can modify it after
object creation where needed).
Note that this can, even if IntelliJ would not sometimes not get the
refactoring wrong (which can lead to hard to track bugs), not be covered
by better refactoring support: Not having to give all the values for the
parameters before the newly added parameter (but instead having them
take their default value) is not something a refactoring engine can supply.
1b) Similar to 1a), for adding parameters to method calls
2) Make the calling of ctors/methods safer in certain cases
a) When a ctor/method takes a lot of parameters of the same type, e.g.
boolean: createPackageSql(String name, boolean trimQ, boolean
indentLinesQ, boolean keepEmptyLinesIndentationQ, boolean
seperateFunctionsQ, boolean uppercaseKeywordsQ, boolean uppercaseNamesQ)
{ ... } *
b) When a framework function is called by a framework user, e.g. from a
script, or generally in an environment where there is little or no test
coverage, and the paramter order can easily be confused: e.g. method
which retrieves a DB item through 2 IDs, and getting an empty result set
is not an error - which ID has to be given first ? Easy, when calling
with named paramters is supported pe.retrieveTreeNodeRow(orgUnit:123,
orgItem:456) .

mg

*Here a parameter object might be the better choice. But typically the
number of paramters was small at the beginning, and grew over time, so
initially no need for a paramater object existed. Again the refactoring
time needed to introduce a parameter object, the fact that your class
namespace gets cluttered with helper structures, external framework
users will have to adapt their code (or you must maintain a backward
compatibility facade), etc make this less practical than it might
initially seem...



--
Charles A. Monteiro
www.monteirosfusion.com
sent from the road

Reply | Threaded
Open this post in threaded view
|

Re: Named Parameters Support

paulk_asert
I too would be interested in thoughts from anyone who has used Ruby/Smalltalk a bit more extensively.

But generally, there is certainly more that we could potentially do. We could do various kinds of mapping during compilation of Groovy source since we have the names at hand - there is the issue of breaking backwards compatibility to deal with if we make such mapping automatic - since today a Map method would always be called but that wouldn't necessarily be the case if both a Map and eligible non-Map variants were found in the future.

Also, for runtime mappings and pre-compiled classes, we can probably only do something for libraries compiled with parameter information turned on (for Java see JEP 118, for Groovy 2.5+ see GROOVY-7423). So if making automatic we'd need to make it obvious when such mappings couldn't be done. Perhaps we should consider turning parameter name inclusion on by default (like Golo but unlike Java)?

And we could certainly create AST transforms to do more today.

Cheers, Paul.


On Sun, Jul 23, 2017 at 10:18 AM, MG <[hidden email]> wrote:
I have used a lot of languages over the years, but I don't do Puppet in our team, so have not used Ruby myself, and have never programmed in Smalltalk. Could you elaborate on what named parameter features from Ruby/Smalltalk you had in mind ?


On 23.07.2017 01:00, Charles Monteiro wrote:
How about the same support as languages like Ruby and Smalltalk provide ?

On Sat, Jul 22, 2017 at 5:17 PM MG <[hidden email]> wrote:
Hi,

having recently explained why Groovy is my language of choice got me
thinking about the few areas of Groovy where I personally wish for / see
the potential for improvement during my daily development tasks.

I will start off with named paramters support, a topic that has been
moving more into my consciousness as the framework I develop at work
grew larger over time: I feel that the current named parameter support I
am aware of
(http://groovy-lang.org/objectorientation.html#_named_argument_constructor
/ http://groovy-lang.org/objectorientation.html#_named_arguments ) is a
limited feature that fit relatively well with a purely dynamic language,
but is not nearly as well rounded / powerful as in other languages.

Do you have any plans to support named parameters such as they exist
e.g. in PL/SQL (to be honest, I never expected I would ever quote PL/SQL
as a language that is superior to Groovy in any aspect before I thought
about this ;-) ), where named paramters are just a convenient (and
safer) way to call a callable, without loosing type safety, which works
in a purely static manner, and without requiring a method to explicitely
deal with a Map as its argument.
In short, a way to call any Groovy method/ctor with a syntax like:

class Processor {
     Processor(String name, boolean writeableQ = false, boolean
extendableQ = true,  Number id = null, Worker defaultWorker = null,
Closure logCls = {  println it }) { ... }
     work(Worker worker = null, boolean dryRunQ = false, int
maxNrRetries = 10, boolean overwriteQ = false) { ... }
}

final processor= new Processor(name:"Task 1", id:1234) { log.debug "T1:
$it" }  // Closure as last argument can still be given outside of brackets
processor.work(maxNrRetries:99)


The grooviest thing of all would be, if there would be a way to keep the
flexibility of the map argument, and combine it with the named
arguments, to be able to e.g. define a different default set of default
values for a method/ctor. E.g.

static ParametersMap getDebugWorkParams() {
     return [ worker:dummyWorker, dryRunQ:true, maxNrRetries:0,
overwriteQ:true ]
}

processor.work(*(debugWorkParams + [worker:logOnlyWorker,
maxNrRetries:3])) // dryRunQ=true and overwriteQ=true comes from
debugWorkParams; worker and maxNrRetries are give explicitly in call


In my case such a feature would have two benefits:
1a) Make it easy to quickly add a final field to a root base class,
without having to do some a major ctor refactoring on all the child
classes (for which I typically don't have the time, meaning that I am
forced to add the field as non-final "for now", so I can modify it after
object creation where needed).
Note that this can, even if IntelliJ would not sometimes not get the
refactoring wrong (which can lead to hard to track bugs), not be covered
by better refactoring support: Not having to give all the values for the
parameters before the newly added parameter (but instead having them
take their default value) is not something a refactoring engine can supply.
1b) Similar to 1a), for adding parameters to method calls
2) Make the calling of ctors/methods safer in certain cases
a) When a ctor/method takes a lot of parameters of the same type, e.g.
boolean: createPackageSql(String name, boolean trimQ, boolean
indentLinesQ, boolean keepEmptyLinesIndentationQ, boolean
seperateFunctionsQ, boolean uppercaseKeywordsQ, boolean uppercaseNamesQ)
{ ... } *
b) When a framework function is called by a framework user, e.g. from a
script, or generally in an environment where there is little or no test
coverage, and the paramter order can easily be confused: e.g. method
which retrieves a DB item through 2 IDs, and getting an empty result set
is not an error - which ID has to be given first ? Easy, when calling
with named paramters is supported pe.retrieveTreeNodeRow(orgUnit:123,
orgItem:456) .

mg

*Here a parameter object might be the better choice. But typically the
number of paramters was small at the beginning, and grew over time, so
initially no need for a paramater object existed. Again the refactoring
time needed to introduce a parameter object, the fact that your class
namespace gets cluttered with helper structures, external framework
users will have to adapt their code (or you must maintain a backward
compatibility facade), etc make this less practical than it might
initially seem...



--
Charles A. Monteiro
www.monteirosfusion.com
sent from the road