Groovy's implementation of curry
does not actually curry at any point, even behind the scenes. It is essentially identical to partial application.
The curry
, rcurry
and ncurry
methods return a CurriedClosure
object that holds the bound arguments. It also has a method getUncurriedArguments
(misnamed—you curry functions, not arguments) which returns the composition of the arguments passed to it with the bound arguments.
When a closure gets called, it ultimately calls the invokeMethod
method of MetaClassImpl
, which explicitly checks to see if the calling object is an instance of CurriedClosure
. If so, it uses the aforementioned getUncurriedArguments
to compose the full array of arguments to apply:
if (objectClass == CurriedClosure.class) {
// ...
final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
// [Ed: Yes, you read that right, curried = uncurried. :) ]
// ...
return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}
Based on the confusing and somewhat inconsistent nomenclature above, I suspect that whoever wrote this has a good conceptual understanding, but was perhaps a little rushed and—like many smart people—conflated currying with partial application. This is understandable (see Paul King's answer), if a little unfortunate; it will be difficult to correct this without breaking backwards compatibility.
One solution I've suggested is to overload the curry
method such that when no arguments are passed it does real currying, and deprecate calling the method with arguments in favour of a new partial
function. This might seem a little strange, but it would maximise backwards compatibility—since there's no reason to use partial application with zero arguments—while avoiding the (IMHO) uglier situation of having a new, differently-named function for proper currying while the function actually named curry
does something different and confusingly similar.
It goes without saying that the result of calling curry
is completely different from actual currying. If it really curried the function, you would be able to write:
def add = { x, y -> x + y }
def addCurried = add.curry() // should work like { x -> { y -> x + y } }
def add1 = addCurried(1) // should work like { y -> 1 + y }
assert add1(1) == 2
…and it would work, because addCurried
should work like { x -> { y -> x + y } }
. Instead it throws a runtime exception and you die a little inside.
As much as people sometimes complain about regular expressions, this is a perfect scenario for using them. I'm not familiar with the syntax for Java myself (but here's a reference), but I can tell you that you're going to want the pattern:
(\d+ )?([MTWRF] )+(\d+) (\d+) (\w+) (\d+)
You can test it out here.
Step by step, this means:
(\d+ )?
- If they exist, capture any number of digits followed by a space.
([MTWRF] )+
- Capture a letter from the set MTWRF
followed by a space, as many times as it happens (but at least once).
- If you want to enforce the 1-5 rule, replace the
+
with {1,5}
, so it becomes ([MTWRF] ){1,5}
.
- If you want to include Saturday and Sunday, add
S
and U
inside the []
block (order doesn't matter), so it would become ([UMTWRFS] )+
.
(\d+)
- Capture a series of digits. If you want to enforce four digits, replace the +
with {4}
so it becomes (\d{4})
, or simply put in four \d
characters to make (\d\d\d\d)
.
(\d+)
- Capture another series of digits. See above for modifications.
(\w+)
- Capture a series of letters
(\d+)
- Capture a last series of digits. See above for modifications.
You then can access each capture - if the first one doesn't match, it should be empty. If the second one matches multiple letters, you can just get those by splitting on spaces. All other ones don't even have stray spaces for you to worry about.
Best Answer
One reason is that conceptually,
firstName
,middleName
, andlastName
are logically one argument, not three. They're all parts of a bigger whole, and one can imagine that they're almost always passed together. In functional languages they'd likely be passed as a tuple or record; Java lacks those, so they'd probably be aggregated into a Name class. Note that if you needed to compose functions that take and return full names, you wouldn't be able to without aggregating them - you can only return one value, after all.Maybe it just doesn't come up very often that a value is optional and also not part of a bigger whole. If the function requires a certain value to do its work, then that function shouldn't be accepting an
Optional
. If you need to chain operations that might not return a value, aborting withNothing
if any function along the way fails, you can chain calls toflatMap
, and if you want to fail with an exception you can useget()
at any step of the way or at the end of the chain.If a value is truly optional and a function does two different things based on its presence or absence, that's a code smell unless the function is a wrapper of two smaller functions that do one thing each, and that particular combination of decisions is very common.
I don't think it's so much that it's wrong as it is a relatively uncommon use case.