Would you guys be interested in a PR that allows you to curry a function using options objects?
Here is an example: https://gist.github.com/JAForbes/edee9adda777c4ec646e
In the gist, there is a function request that takes three arguments. Two of which are optional (protocol and port) and 1 is required (url).
This request function will continue to accept invocations with options objects, until it receives an options object that contains the required field (url).
You can have as many required fields as you would like. But I kept it to one.
This function side steps the currying argument order issue. With the added benefit that options objects self document the intention of the arguments.
//example function
function request( protocol, port, url ){
return protocol + "://" + url + (port == 80 ? "" : ":"+port)
}
//creating keyword argument curried function
var kRequest = kwargs(request, {
port: 80,
protocol: "http",
url: undefined
})
//tests
;([
kRequest
({})
({})
({url: "google.com"}) == "http://google.com"
,
kRequest
({ port: 3000 })
({url: "localhost"}) == "http://localhost:3000",
kRequest
({ port: 3000 })
({ protocol: "https", port: 3001 })
({ url: "github.com"}) == "https://github.com:3001"
]).every( function(val){ return val} ) && "All tests passed"
I don't really like the name kwargs by the way, it was just the first thing that came to my head. I was referencing python kwargs.
We're better off drawing inspiration from Haskell than from Python in this case, since curried functions are everywhere in Haskell. I suggest something along these lines:
const requestWithOpts = R.curry((protocol, hostname, port) =>
protocol + '://' + hostname + (port === 80 ? '' : ':' + port)
);
const requestHttp = requestWithOpts('http');
const requestHttps = requestWithOpts('https');
const request = requestHttp(R.__, 80);
We're better off drawing inspiration from Haskell than from Python
I found this comment to be quite condescending. Not sure if that was intended?
For functions that take more than 4 or 5 arguments, I tend to prefer named-arguments using ES6 destructing, like this:
function f({ a, b, c, d, e, f })
{
return a + b * c - d + e / f;
}
f({ a: 1, ... })
And because there's no way to "curry" such a thing, I find myself using default-value for destructing ({ a = 3 } etc) to cover the most common use case of "currying" such functions. For more complex cases I'm writing wrapper functions merging the input options object with some defaults.
Writing all of this, I realize that useWith(fn, merge({ a: 1 ...}) is an easy way to achieve the desired effect.
@davidchambers: What were you doing at that time in the morning?! :smile: Obviously our typical sort of currying is not a replacement for the proposal, which would add a different style of function configuration.
@JAForbes: Although I'd be interested to hear more, my first response is mostly "_why_"? I can't see how this would help _in source code_. I guess I can imagine it potentially useful in some interactive environment. But in source code, you eventually need to get a value out. For your example, you need a URL. You really can't do anything with the result until you've configured if far enough to have a URL. And if that's the case, why not pass all that information in one pass? If it doesn't start as a single object, you should be able to use merge to create one.
But there are two other real issues. One is that this seems to be a stateful function, which is something we absolutely avoid in Ramda. I suppose if each call to kRequest creates either a final result or a recursive call to kRequest, then we can get around this concern. But that leads to the other concern. What exactly is the signature of this function? We put a great deal of stock in the signatures. We don't have them all right, and even our exact syntax is not entirely nailed down, but we still find them helpful. This one looks almost impossible:
(a, b, ..., -> z) -> {k: v} -> (z | ((a, b, ..., -> z) -> {k: v} -> (z | ((a, b, ..., -> z) -> {k: v} -> (z | ...)))))
Uggh! We'd need something like BNF for our signatures in order to simplifiy that.
So, I'm at best sceptical. Feel free to try to convince me, though! :smile:
@CrossEye I don't know about the author, but for the use case is usually for setting defaults and get a function that is name is more expressive. e.g:
var getDailyChart = getChart({ TIME_RESOLUTION: "DAYS" })
@asaf-romano: Ahh, yes, that makes sense. I wasn't making much sense from the original example.
My other objections are still there, but now I do start to see _why_.
We're better off drawing inspiration from Haskell than from Python
I found this comment to be quite condescending.
I'm sorry about that.
Python is a well-designed imperative, object-oriented programming language. Much inspiration can be drawn from it (I've quoted _The Zen of Python_ on many occasions).
Currying is not common in Python but is ubiquitous in Haskell. Haskell does not support named parameters, since all functions are unary. But there's a real benefit to named parameters: avoiding the Boolean trap. mkRegexWithOpts falls right into this trap:
mkRegexWithOpts :: String -> Bool -> Bool -> Regex
Setting aside the Boolean trap for a moment, I believe Haskell's approach to "optional" arguments is a good one: define a function of arity N, then partially apply it to define a function of arity M with one or more default values fixed. For example:
// split :: String -> String -> [String]
const split = curry((delim, s) => s.split(delim));
// words :: String -> [String]
const words = split(' ');
Several Ramda functions are defined in this way.
Coming back to the Boolean trap, I think @asaf-romano is on the right track with R.merge.
// requestDefaultOpts :: RequestOptions
const requestDefaultOpts = {
protocol: 'http',
hostname: 'localhost',
port: 80,
};
// request :: RequestOptions -> String
const request = (opts) =>
opts.protocol + '://' + opts.hostname + (opts.port === 80 ? '' : ':' + opts.port);
// Usage examples:
request(requestDefaultOpts); // => 'http://localhost'
request(R.merge(requestDefaultOpts, {port: 3000})); // => 'http://localhost:3000'
This isn't very convenient, though, so we may like to do the merging inside request (at the cost of making the function's type less clear):
// request :: Object -> String
const request = (_opts) => {
const opts = R.merge(requestDefaultOpts, _opts);
return opts.protocol + '://' + opts.hostname + (opts.port === 80 ? '' : ':' + opts.port);
};
// Usage examples:
request({}); // => 'http://localhost'
request({port: 3000}); // => 'http://localhost:3000'
This is a reasonable compromise. I don't see an room for currying, though. It seems we need to choose currying or named parameters. Mixing the two approaches seems confusing to me.
@davidchambers
I'm sorry about that.
Thank you.
@asaf-romano
the use case is usually for setting defaults and get a function that is name is more expressive.
That was exactly the point. (sorry I was obtuse about that)
I was imagining you'd use this it in some kind of functional router within a single page application that talks to multiple servers.
var get = {
dropbox: request({ protocol: "https" }),
users: production ?
request( protocol: "https", url: "production.api.com" ) :
request({ url: "localhost", port: 3000 }),
amazonS3: request({ url: ... }) //etc
}
//usage
var users = get.users()
//etc
@CrossEye
Although I'd be interested to hear more, my first response is mostly "why"? I can't see how this would help in source code.
I think there are better examples than the one I chose. The real world "source code" situation that made me want to have this, was writing a C# wrapper to a command line tool. I needed to make the wrapper function configurable and self documenting. But the user of the function wouldn't necessarily want to type all that configuration at call time.
I used named args because the code base was so massive and dense, that I felt named arguments would reduce the opacity of the overall project. The CLI tool itself, has a lot of flags - that are all useful in different situations - and after 5 or 6 arguments, positional arguments would just be confusing in the usage code.
I was so impressed with named args that I wanted to use them everywhere. But I couldn't do that without currying...
@CrossEye
What exactly is the signature of this function?
I never really grokked the signatures you guys use. But the way I see it is, the underlying curried function does not accept an option argument, it is positional, so this is just like the normal R.curry but with one less assumption: that the arguments received are coming in positional order.
Without that assumption, we need a mechanism to link the received arguments to the original order. And so I am passing in an object literal. But that is just a mechanism, or implementation detail. It could be some kind of Receiver monad...
It is as stateful as R.curry is.
I don't know how you represent generics in the signatures. So I'll foolishly attempt to define two
notations, that or may not be better represented some other way: <*...> and <*>
Here is an attempt:
( ( <*...> -> <*> ), { k:v } -> * ) -> ( { <*...>: v } -> <*> )
Where <*> is the return value of the received function, which can be any type. But the result of the satisfied curry must be the same any type.
And where: <*...> is the specific list of variadic args and { <*...> : v} is an object that needs the specific keys of the specific variadic args used in the function definition.
Takes a function that accepts any number of arguments of any type and returns any type. And a defaults object that accepts variadic
k:vpairs. And returns a function that accepts an object with the same variadic keys as the original function definition and returns the same "any" type as the original function.
Apologies if that signature is inaccurate, I've never written one before.
@CrossEye
This seems to be a stateful function
If you mean stateful because it has an array of received arguments - this was just my first pass. I think this could be written in a stateless manner. But all the state is transient as the inner work function clones its state on every invocation.
If you mean stateful as in the object that the function receives has a specific relationship that needs to be satisfied before it invokes the visitor - then I think that is just a perspective.
Technically Promises are stateful, and Function::bind is stateful.
Partial application is stateful by definition, the only thing that matters is that the function that is bound is pure. And that is still the case here.
@DavidChambers
Avoiding the Boolean trap
I had this in mind while writing it actually. The great thing about this implementation is it allows you to take functions that accept booleans into named arguments, so you can retroactively _untrap_ yourself without editing the original function.
function render( controller, view, drawImmediately ){
if(drawImmediately){
view(controller)
} else {
// store the render instruction somewhere, and do it at a more efficient time
// like when the user is no longer scrolling.
// ...
}
}
kRender = kwargs( render, {} )
// a lot clearer
kRender({ controller: ctrl, view: view, drawImmediately: true })
//legacy function still available
render( controller, view, true )
And this is ignoring the currying capabilities.
renderImmediate = kRender({ drawImmediately: true})
renderLater = kRender({ drawImmediately: false})
In this situation, merge doesn't suffice. You could use placeholder arguments, but they don't have the benefits of self documentation. The underlying function does not accept an options object. And most function definitions do not use options objects, so merge wouldn't work in those situations.
A lot of the motivation for ramda's existence was the difficulty in composing lodash/underscore functions, because their arguments were in the wrong order. Now trine is suggesting we use the this object for the data argument: which is a cool idea. But this is another tactic all together.
I think Ramda is a lot more than those original motivations. Other libraries would rather give more capabilities to an existing argument that branches based on its signature, where as Ramda would prefer each function does it's own job in the best way it can, but in only one way, with a clear signature. Where any customization can be achieved via composition.
That said: this is just another, light weight solution to the positional argument composition problem. So why not include it?
Below is an example of currying existing lodash functions without having to use lodash-fp. It just works.
var kMap = kwargs(_.map)
var half = kDivide({ denominator: 2 })
var halfAll = kMap({ iteratee: half })
halfAll( {
collection: [2,4,6,8]
}) //=> [1,2,3,4]
_For the record I really don't like this whole k thing. I'd rather call the function curryNamed or something along those lines...._
Is the idea that the following could all be equivalent?
f({a: 1, b: 2})f({a: 1})({b: 2})f({b: 2})({a: 1})f()()()({a: 1})()()()({b: 2})Sorry if it's taking me a while to grasp your proposal. :)
I believe the question of how to model functions with default args (i.e. allowing a user to override defaults) has come up on Gitter a couple of times now, so it will be interesting to see how this pans out.
I'm not such a fan of extracting the args from the func.toString(), as it won't play nicely with native or bound functions, or inline argument comments such as Flow's. While more verbose, I think I'd prefer the required keys to be passed as an explicit argument, e.g.
var curryObj = function curryObj(keys, fn) {
function curriedObj(remainingKeys, partialObj) {
if (R.isEmpty(remainingKeys)) {
return fn(partialObj);
} else {
return function (obj) {
return curriedObj(
R.difference(remainingKeys, R.keys(obj)),
R.merge(partialObj, obj)
)
}
}
}
return curriedObj(keys, {});
}
var thing = curryObj(['t','h','i','n','g'], R.identity);
thing({ t: 0, h: 1, bob: 2 })({ h: 3, i: 4 })({ n: 5, g: 6 });
// { t: 0, h: 3, bob: 2, i: 4, n: 5, g: 6 }
The only thing that would concern me about using this myself is that it would be potentially difficult to keep track of how much of the object has been applied already. If you were to pass a partially applied instance around, the next caller doesn't know for sure whether they'll receive another partially applied function or the end result when they call it. This could potentially be mitigated if it didn't have to be curried, but instead just declares a set of required object keys and a default object to merge with, returning a function that can only be called once.
@JAForbes: I'm definitely warming up to this idea. Thank you for your detailed and considered comment above.
I think my notion of it being stateful was based on a misunderstanding. Somehow I assumed that in, say this example:
kRequest ({ port: 3000 }) ({ protocol: "https", port: 3001 }) ({ url: "github.com"}) == "https://github.com:3001"
that kRequest({ port: 3000 }) and kRequest({ port: 3000 })({ protocol: "https", port: 3001 }) pointed to the same result. Obviously there is no reason they should.
So, while I am warming up to this, I still have concerns about how to describe this function. Regardless of the syntax of the signature, how do you describe the inputs and the outputs? Do you need to have a recursive definition?
I also share @scott-christopher's concerns about converting a function into named parameters. I don't think that's a well-solved problem, especially in the es3 - es6 environments that Ramda currently supports. His solution clearly gets only partway to your goal. Do you think it's reasonably close?
@scott-christopher:
While more verbose, I think I'd prefer the required keys to be passed as an explicit argument [ ... ]
A possible extension to this would be to a defaults object instead of the keys. Anything with value undefined would have to be supplied; the others could be left with their defaults.
The only thing that would concern me about using this myself is that it would be potentially difficult to keep track of how much of the object has been applied already. If you were to pass a partially applied instance around, the next caller doesn't know for sure whether they'll receive another partially applied function or the end result when they call it.
That same sort of argument could be made against Ramda's style of currying. Since we properly report arity, I suppose there is a way to know. But I've _never_ seen Ramda-based code checking the arity of a curried-and-partially-applied function and then passing a different number of arguments based on that result. You simply know what you're getting. If it's supposed to be a binary predicate generated from a ternary function, you assume someone has already curried in the first argument. I think a similar thing could apply here. The initial examples that showed a sequential function applications threw me off, and I think do a disservice to the basic idea.
This would strike me as most useful with only a small number of required parameters alongside others that, though defaulted, might often be overridden. Following the example from @asaf-romano, we might get something like this:
var getChart = rekwest({ // sorry, couldn't resist
type: 'line',
time_resolution: "weeks",
period: '1yr',
data: undefined
}
var getDailyChart = getChart({time_resolution: 'days'});
var chart = getDailyChart({data: [ ... ]}) //=> actual chart
var getDailyPieChart = getDailyChart({type: 'pie});
// or
var getDailyPieChart = getChart({time_resolution: 'days', type: 'pie'});
var chart = getDailyPieChart({data: [...});
In these cases, the only required paameter is data, which I would expect the user of the function to understand.
So this idea is certainly achievable and interesting. But it doesn't go as far as the original request, and I'm curious to see what @JAForbes thinks. Is this a reasonable approximation?
@davidchambers
Is the idea that the following would be equivalent?
Yep, spot on
Sorry if it's taking me a while to grasp your proposal. :)
All good :)
@CrossEye
I'm definitely warming up to this idea. Thank you for your detailed and considered comment above.
Thank you for creating the environment where ideas like this can be discussed in the first place!
@scott-christopher
I'm not such a fan of extracting the args from the func.toString(), as it won't play nicely with native or bound functions
Yes, I agree with you. The argument extraction thing is cool, but unfortunately it doesn't fit in well with Ramda's MO of Rich Hickey style simplicity
@CrossEye
A possible extension to this would be to a defaults object instead of the keys. Anything with value undefined would have to be supplied; the others could be left with their defaults.
I don't believe this approach would work, as the function needs to know the order of the argument names. The argument name extraction handled this issue, but as @scott-christopher points out, this won't work for native and bound functions.
And we can't rely on the keys to be ordered in Javascript ( even though we really can, 99% of the time ).
Unless anyone can think of a way around the ordering problem, I think the initial invocation will need an array of arg names. I'd like that array to be optional, and for the arg names to be inferred via the toString merely for convenience - but I know that is not really Ramda's style. And I can live with the added verbosity, as this function is all about that self documentation and clarity.
Alternatively we could abandon this problem, if the target function did receive an options object for the final invocation. This will probably become more and more common given ES6's destructuring syntax, and it has no overlap with the current currying implementation, which treats the options object as a single parameter.
Below is an example of a git function in a node environment. It accepts an options argument, that we will curry using curryNamed
var git = R.curryNamed(function(options){
var command = ["git"].concat(
options.subcommand,
options.arguments
).join(" ")
return exec(command, options.callback);
}, {
//default values are predefined
subcommand: "help",
arguments: [],
//required indicated by undefined
callback: undefined
})
Next we will create some shorthand functions for some common operations.
//git log
var log = git({
subcommand: "log",
})
var prettyLog = log({
arguments: [
"pretty=oneline"
]
})
var diff = git({
subcommand: "diff"
})
Next we can execute our preconfigured functions by providing the callback.
//executes `git log` because it's only required argument was provided.
log({
callback: console.log
})
prettyLog({
callback: console.log
})
diff({
callback: console.log
})
I have mixed feelings about this approach. I personally like function definitions to specify all their
arguments. I think it avoids a lot of confusion in a codebase. And that is why I prefer the original, you get the benefits of predefined arguments in your source code _and_ the benefits of self documentation in the user land code.
But I don't know how to resolve @scott-christopher's criticism, so maybe this is a valid compromise.
Even after that lengthy explanation of the options object approach, I still think I'd prefer @scott-christopher 's proposal: to have an array of argument names.
var git = R.curryNamed(function(subcommand, arguments, callback){
var command = ["git"].concat(
subcommand
arguments
).join(" ")
return exec(command, callback);
}, ["subcommand","arguments", "visitor"], {
//default values are predefined
subcommand: "help",
arguments: [],
})
Note that the original function definition had the final argument named as callback while the array
has labelled it as visitor. This would be a nice side effect of the array approach: you don't have to call the visitor iteratee just because underscore does (as an example).
And while it looks quite verbose, it would be easier to digest without the original function definition inline:
var curriedGit = R.curryNamed( git, ["subcommand","arguments", "visitor"], {
subcommand: "help",
arguments: [],
})
I've also moved the default arguments to be the final argument, as you may not actually want to provide default arguments, but you will always need to provide the argument order.
I just thought too, a nice side effect of this approach, is the outsourcing of default argument handling.
You often see in usage code of options objects, a lot of var value = option.value || "default value"
And after 4 or 5 lines of that, it really distracts from the actual intent of the function.
But if you used R.curryNamed, you can let the function safely assume that all arguments will be provided. ( I hope that makes sense! )
sorry if this doesn't make sense, been reading along but not _super_ closely.. what about pairs, so you have ordering but don't have to repeat yourself:
var curriedGit = R.curryNamed(git, [
["subcommand", "help"],
["arguments", []],
["visitor"]
]);
if it is useful, could make snd a object
var curriedGit = R.curryNamed(git, [
["subcommand", { default: "help", somethingElse: "foo"}],
["arguments", { default: []} ],
["visitor"]
]);
@kedashoe Pairs make a lot of sense. They solve the ordered problem, and they remove the redundancy of stating arguments existence twice ( defaults hash, and argument order).
But to finally invoke the code in your example do we continue to send pairs (for consistency)?
Or, seeing as we already know the argument order do we accept hashes?
Example:
//Do we invoke using pairs for consistency?
curriedGit([
["visitor", console.log]
])
//Or can we revert to a hash now that the argument order has been established?
curriedGit({
visitor: console.log
})
I like the idea, but I am worried about the inconsistency of pairs/hash.
I am also feeling pretty settled with the solution @scott-christopher came up with: (an array of ordered arguments), But I think the defaults arguments hash should just be a new invocation.
This echoes R.curry in that the first two arguments are just initialization, followed by new invocations that are just binding arguments.
R.curry( func, arity )(args...)(args...)(args...)
R.namedCurry( func, argNames)( { args } )({ args })({ args })
The argNames array serves the same purpose as arity; it allows us to know when to invoke the source function. There is no need to have a defaults argument, as with R.curry there is no need to have default arguments.
I am proposing the final signature is just
( ( <...*> -> <*> ), [ String ] ) -> ({ <*...>: * }) -> <*>
Below is an example of creating some named curried functions as an example of that signature.
// There is no default argument here. Just stating argument order, and assigning names to that order.
var namedRender = R.curryNamed( render, [ "controller", "view", "drawImmediately" ])
// Creating a version of `namedRender` where drawImmediately is bound to true
var renderImmediately = namedRender({ drawImmediately: true })
// Creating a version of `namedRender` where drawImmediately is bound to false
var renderLater = namedRender({drawImmediately: false})
// Creating a function built for mapping over views, and rendering them using the app's cotnroller.
var renderViewLater = R.pipe(
R.createMapEntry("view"),
renderLater({
controller: app.controller
})
)
And here are the invocations:
//This view will render at a time of the apps choosing
renderLater({ view: view1 }
//This view will render immediately
renderImmediately({ view: view2})
// Here we don't need to reference the view by name
// as the definition included R.createMapEntry("view")
R.map(
// these views will all be rendered at a later time
renderListLater, [ view1, view2, view3 ])
)
On reflection, I actually prefer that you have to state the argument order. It solves many problems: (minified code, bound and native functions). While also documenting the intention of the invocation of namedCurry.
And my original concept of having an initial defaults argument, was redundant, when you consider following invocations have the same intent.
And when you compare R.curry with R.namedCurry, their signatures become more aligned.
One argument is the source function. The other informs curry when to invoke. Following invocations bind arguments. There is a lot of symmetry there.
What do you guys think?
I've written a new version that matches the proposal from the above post.
It will work in a repl without any dependencies.
function namedCurry(source_fn, arg_order){
function receiver( received_values, n_received, defaults ){
received_values = (received_values || []).slice()
n_received = n_received || 0
Object.keys(defaults).forEach(function( input_arg ){
var value = defaults[ input_arg ]
var required_index = arg_order.indexOf( input_arg )
var is_known_argument = required_index > -1
var existing_value = received_values[required_index]
if( is_known_argument ){
if( typeof existing_value == "undefined" ){
n_received++
}
received_values[required_index] = value;
}
})
if( n_received >= arg_order.length ){
return source_fn.apply(null, received_values )
} else {
return receiver.bind( null, received_values, n_received )
}
}
return receiver.bind(null, null, 0)
}
And here is some usage code of a fictional web app.
// The original render function with positional arguments.
function render(controller, view, renderImmediately ){
console.log("Rendering",view,"as part of a",controller,"controller", renderImmediately ? "(now)" : "(later)" )
}
// A curried-options object style version of the original render function.
// Notice the argument names are different to the source code.
var namedRender = namedCurry(render, ["ctrl", "view", "immediate"])
//Creating some preconfigured functions for different areas of the site.
//This function is waiting on the `immediate` parameter before being invoked.
var modal_signup = namedRender({
ctrl: "modal",
view: "signup form"
})
//This function is waiting for the `view` parameter before being invoked.
var sidebar_later = namedRender({
ctrl: "sidebar",
immediate: false
})
//Invoking the signup modal _immediately_
modal_signup({
immediate: true
})
//Invoking the signup modal _later_
modal_signup({
immediate: false
})
//Invoking the sidebar with some navigation icons _later_
sidebar_later({
view: "naviagation-icons"
})
//Invoking the sidebar_later as above, but we are overwriting the immediate property at call time.
sidebar_later({
immediate: true, //you can overwrite existing properties
view: "naviagation-icons"
})
I'm still not convinced that there is any need for positional arguments at all. If the examples above are the usual cases, then I would think this would work just as well:
var render = R.curry(function render(controller, view, renderImmediately ){
console.log("Rendering",view,"as part of a",controller,"controller", renderImmediately ? "(now)" : "(later)" )
})
var modal_signup = render("modal", "signup form");
var sidebar = namedRender("sidebar");
var sidebar_later = sidebar(R.__, false);
//Invoking the signup modal _immediately_
modal_signup(true);
//Invoking the signup modal _later_
modal_signup(false);
//Invoking the sidebar with some navigation icons _later_
sidebar_later(naviagation-icons");
//Invoking the sidebar_later as above, but we with the immediate property set to false.
sidebar_later("naviagation-icons", false);
If this only works because the signature of the function is so short, then I'd suggest that such functions should be constructed against an object rather than ordered parameters anyway. And if you're doing that, then you can configure the function as I suggested, with a default-or-undefined parameter and the original function.
Even if this changes, I'm intrigued, but not convinced that this is common enough to want to include in Ramda. But my mind is certainly still open.
@CrossEye
should be constructed against an object rather than ordered parameters anyway. And if you're doing that, then you can configure the function as I suggested, with a default-or-undefined parameter and the original function.
I had to reflect on this. But I think this function is about more than options objects. I think the fact that namedCurry takes an object is a distraction from its real value. That is only a side affect of needing the arguments to be named.
A lot of the value of this function is steeped in my personal programming philosophy, and that may not apply to the rest of you, or the rest of the users of this library for that matter. And in that case I am perfectly happy for this function not to be a part of ramda. But I do need to elaborate on my personal philosophy in order to explain why I think this function is not only valuable, but superior to R.curry for a lot of situations ( I know how blasphemous that sounds ).
The real value is taking a pre-existing function that is opaque, or unintuitive to read, and writing equivalent code that is self documenting without having to "refactor" the original function.
I'd go as far to state that refactoring a function to use an options object is a mistake in most cases.
I would argue that that the latter is more intuitive than the former, and this point alone justifies its value.
sidebar(R.__, false);
sidebar({ immediate: false })
The justification for options object vs positional is usually that the number of arguments introduces a chance of error, and a lack of convenience/clarity when invoking that function.
But this is at the cost of self documenting code. Options objects are opaque. And with namedCurry that is no longer a valid justification, as the invocation of the function is named, while the definition is positional. It's win win. Both sides self document intent.
When I see the former signature (below), I get worried. I need to read every single line of code just to understand what the inputs are! When I see the latter, I can often infer a lot about the functions behaviour without going any further.
I'd prefer to rely on function signatures as documentation than comments. Comments are not checked by an interpreter/compiler/linter - code is.
function render( options ){
}
function render(controller, view, renderImmediately ){
}
If namedCurry only applied to functions that receives options objects; I would see it as a helper function for an anti pattern; therefore I would never use it I try to stay away for options objects for most situations. But when this function applies to positional arguments, I would use it instead of R.curry for all but the most trivial functions because I am a strong believer in documentation via the algorithm.
Reading code is hard. I think a lot of people would argue functional programming's main benefit is reduction of complexity due to immutable state. This reduction of complexity is only valuable because it allows us to write code that is easier to reason about. And code that is easy to reason about, is easy to edit, is easy to repair.
Given that logic, placeholder arguments are an anti pattern. They make a function call harder to reason about. And their existence is only justified by the lack of any solid alternative.
Below is an unnamed function written with placeholder arguments and with named arguments. The former is next to impossible to infer its intent. The latter is obvious, even without seeing the definition or the variable name of the resulting function we can infer what the intent of the function will be.
unnamed(R.__, R.__, "!" )
unnamed({ name: undefined, greeting: undefined, exclamation: "!" })
In Python or C# (and other languages with named arguments) you can call a function in two ways. In the original order stated in the function definition, or in any position you like as long as you indicate which name to bind your value to. (Perhaps this function could take the same approach)
I touched on this earlier, but this function escapes the whole "Hey Underscore! You're doing it wrong!" debate, by avoiding order altogether. Ramda isn't about argument positions, not really. it is about the API
We sacrifice a great deal of implementation elegance for even a slightly cleaner API.
What is the cleaner API?
I'm not the person to answer this question. And I understand if it seems to have not enough added functionality to justify its existence in this library.
In some cases I'd still use R.curry, and in some cases I'd still use options objects. But in the majority of code I've been writing lately I'd prefer clarity, intent and self documentation to terse syntax.
I'm on my phone and can't respond as this deserves. I probably won't be able to give it serious attention for nearly 24 hours, which bothers me because I'm intrigued.
Thank you once again for a thoughtful -- and thought-provoking comment. I agree with a lot of what you say.
The biggest sticking point for me is that the chief goal of Ramda is to enable a workflow built around function composition. It seems to me that a function built with this will not compose well, at least not in the traditional way we use in Ramda. (How could you pass a partially-configured output of namedCurry to map for instance? )
I have two different ideas for implementations that might clean this up, and I will plan on trying them tomorrow if no one beats me to it. Both follow up on your remark that perhaps we could duplicate the ability to call the function both ways.
I'm looking forward to having time to try this.
@JAForbes:
I have been thinking about this a lot. I agree that there's very little to love in placeholders. But I think there's a great deal to like about the standard way Ramda curries the usual cases, where it's generally clear which parameters are most likely to and which one are least likely to change. Then things like this make a great deal of sense:
var squareAll = map(square);
And that's much cleaner than something like
var squareAll = map({fn: square})
or whatever would be used in its place.
For complex function where these choices are not so obvious, however, something like your suggestion makes a great deal of sense.
But I really don't like the API. The functions, once created, need to be called only with these options objects, and that means they can't behave like most of the functions that we use in our day-to-day experience. From Ramda's point of view, they can't be passed around as callbacks to our usual functions such as map. And this makes the specific API fairly disheartening.
I played around with a very rough cut of an alternative in this gist. The API it presents includes two separate functions, namedCurry and configure, used like this:
// render :: String -> String -> Bool -> LogMessage
var render = function(controller, view, renderImmediately) {
return 'v:' + view + ', c: ' + controller + ', ' + (renderImmediately ? 'now' : 'later');
};
// namedRender :: String -> String -> Bool -> LogMessage
var namedRender = namedCurry(render, ['ctrl', 'view', 'immediate']);
namedRender('sidebar', 'navigation-icons', false);
//=> "v:navigation-icons, c: sidebar, later"
// modalSignup :: Bool -> LogMessage
var modalSignup = configure(namedRender, {
ctrl: 'modal',
view: 'signup form'
});
modalSignup(true); //=> "v:signup form, c: modal, now"
// login :: String -> Bool -> LogMessage
var login = configure(namedRender, {
view: 'login form'
});
login('normal', false); //=> "v:login form, c: normal, later"
// modalLogin :: Bool -> LogMessage
var modalLogin = login('modal');
modalLogin(true); //=> "v:login form, c: modal, now"
An alternative formulation that would be easy to create would be to make the configure function act as a method of the functions rather than a stand-alone function:
// modalSignup :: Bool -> LogMessage
var modalSignup = namedRender.configure({
ctrl: 'modal',
view: 'signup form'
});
The basic idea is to create functions that _can_ be configured through a name-based mechanism but that are still positional
As I said, this is a very rough implementation. There are a number of open questions, and there is likely plenty of room for improvement in the code. The key for the metadata should probably be a Symbol where available, and definitely should not be "secret-sauce" :smile:. But something like this API seems more overall useful to me.
Here are the current versions of these functions:
var namedCurry = function(fn, argNames) {
// TODO: what if fn.length != argNames.length?
var f = R.curryN(fn.length, function() {
return fn.apply(this, arguments);
});
f['secret-sauce'] = {
argNames: argNames
}
return f;
};
var configure = (function() {
var makeArgs = function(inputArgs, argNames, config) {
var idx = 0;
var outputArgs = [];
R.forEach(function(argName) {
if (argName in config) {
outputArgs.push(config[argName]);
} else {
outputArgs.push(inputArgs[idx]);
idx += 1;
// TODO: proper bounds checking.
}
}, argNames);
return outputArgs;
};
return function(fn, props) {
var sauce = fn['secret-sauce'];
if (!sauce) {
throw TypeError('Configure called on function without secret sauce');
}
var config = R.merge(sauce.config ? sauce.config : {}, props);
var argNames = sauce.argNames;
var unassigned = R.difference(argNames, R.keys(config)).length;
// TODO: what if unassigned is 0? Does this return value or thunk?
var f = R.curryN(unassigned, function() {
return fn.apply(this, makeArgs(arguments, argNames, config));
});
f['secret-sauce'] = {
names: argNames,
config: config
};
return f;
};
}());
Thanks @CrossEye I agree with your assessment of the API. I'm excited about this new approach. I'll try them out today (or tomorrow if I can't find a spare moment today).
Thank you for taking the time to try out an approach.
Several other points about this I forgot to make.
into, and I bought into it with lens, so I suppose I might have to just get used to it. On the other hand, my alternative suggestion, where one might be able to call it as a method, via namedCurry(fn, args).configure(opts) seems likely to offend most people even more than the two-function API bothers me.TODOs left in that code ask serious API questions to which I don't have good answers, especially about what happens when it's fully configured.@CrossEye
and I bought into it with lens
I've only just started playing with lenses, and I'm having a lot of fun.
I've been working a new approach after observing @CrossEye's implementation. I'll hopefully have more time tomorrow, to post it here, and elaborate on the reasoning behind it.
Here is some usage code, the implementation is at the end.
//positional function
function divide(a,b){
return a / b
}
divide(10,2) //=> 5
//named curried version
var nDivide = curryNamed(divide, ["numerator", "denominator"])
//still works positionally
nDivide(10,2) //=> 5
//but you can provide a name if you want
nDivide({ denominator: 2 })(10) //=> 5
//to be particularly explicit when configuring...
var half = nDivide({ denominator: 2} )
//this new function will work in the nornal contexts
//just like R.curry
R.map(half)([1,2,3,4]) //=> [0.5,1,1.5,2]
//but with one exception
//If the argument is an object, as in `R.type(val) == "Object"`
//then you need to reference it by name
var data = { a:1, b:2, c:3 }
var usesObject = curryNamed(function(models, options){
},["models", "options"])
usesObject([1,2,3])({ options: data }) //good
usesObject([1,2,3])( data) //bad
// but _technically_ you can still pass in an object literal
// it just can't be the first argument.
usesObject([1,2,3], data) //works
usesObject({ models: [1,2,3] })( data) //doesn't execute
Reasoning:
This function is a little warm and fuzzy. It dispatches on type, and I remember that is something @CrossEye is not a fan of. But because object literals are JS's way of emulating named arguments, I think _just this once_ it should get a pass.
I'm not too attached by the innards of the function. But I do like this API. I think it is fairly intuitive, it is not overly magical. It serves a purpose, which is self documentation. But it doesn't betray ramda's functional composition roots.
I prefer this API to the two function approach, and not just for the convenience. If there were to be a configure function, I'd hope it would be useful in other contexts. And I don't think it would be.
Implementation:
var from = R.flip(R.prop)
function thunk(func, arg_names, args_received){
return function(){
var args = [func, arg_names, R.clone(args_received)].concat(
[].slice.call(arguments)
)
return curryNamed.apply(null, args)
}
}
function curryNamed(func, arg_names, args_received, named){
if(typeof args_received == "undefined") return thunk(func, arg_names, {})
if( R.type(named) == "Object" ){
var valid_args = Object.keys(named).forEach(function(key){
//Check if the named argument is a known argument.
var index = arg_names.indexOf(key)
if( index > -1 ){
//store the val against the key in our hash
args_received[key] = named[key]
}
})
} else{
//find the next positional slot to be filled
[].slice.call(arguments).slice(3).forEach(function(val){
var index;
arg_names.some(function(key, i){
index = i
return typeof args_received[key] === "undefined"
})
//use the position to find the key
var key = arg_names[index]
//store the val against the key in our hash
if(key) args_received[key] = val
})
}
// if we the same, or more values than were declared in `arg_names` call the func
if( R.keys(args_received).length >= arg_names.length){
return func.apply(null, arg_names.map(
from(args_received)
))
} else {
// otherwise, wait for more values
return thunk(func, arg_names, args_received)
}
}
Ping.
I'm not sure how this interesting idea fell off the radar, but it's certainly still worth consideration. I'm glad you brought it up on Twitter, @JAForbes.
Anyone else remember this? Some very intriguing concepts here!
@CrossEye Thank you for saying so. I was just reading over this issue the other day thinking about it.
I'm pretty happy with data last, and I tend to use arrow functions instead of placeholders in the rare occasion I do need to change the arg order. I'm not sure how useful this function would be in today's climate - where es6 is pretty standard, and we'll even have object destructuring in ES7.
Its an intriguing idea, and if anyone could think of a practical usage scenario, I'd love to hear it. But I'm not advocating for this anymore. I stand by my original arguments ( :wink: ), but the JS landscape, and Ramda have changed a lot in the past year.
Ok, then I'll close this without prejudice.
It's still an interesting idea, and if we want to bring it back we can always refer to this discussion.
Stumbled upon this issue looking for ways to curry named arguments. I think it's interesting that Python is mentioned in this thread. Python allows currying of both named and positional arguments with it's partial(fn, myarg="bound value").
My usecase for it is to bind values to a react component:
const createComponent = ({
arg1, arg2, arg3
}) => {
// do things here ...
return <MyComponent arg1={arg1} arg2={arg2} />;
};
It feels natural to me be able to bind some of these arguments.
const withDefaults = curry(createComponent, {arg3: "blue"});
I'm surprised there hasn't been any discussion about this since 2016, is there no demand for this? :)
@antonagestam: The consensus seemed to be that although there were some very interesting APIs under discussion here, none really fit well with Ramda. I don't know if any of the Ramda extension libraries picked it up, but it would be easy enough to include one of these techniques in your own utilities.
it would be easy enough to include one of these techniques in your own utilities
Can't that be said about most features of Ramda?
Reading through this thread I don't get the impression that there was a clear consensus against this, rather the opposite I sensed some excitement here.
I am just trying to find out a little deeper reasoning as to why this was abandoned, if it's considered a bad idea to implement that kind of function into Ramda, I'd like to understand that reasoning. (Because maybe then it's a bad idea for me to have that function in my own utilities).
Cheers
@antonagestam:
it would be easy enough to include one of these techniques in your own utilities
Can't that be said about most features of Ramda?
Yes, of course. But we cannot include every function that might be useful to someone, sometime. We always need to find the balance between how widely used a function would likely be, and how easy it is to write yourself. We use the Cookbook and other manage extension libraries for things that don't make the cut but are themselves useful.
You're right that there was some real interest in this, but none of us seemed to see how it would fit well with Ramda. If you want to make a new proposal, I can't promise that it would be accepted, but I can promise a fair hearing. I do think it should be a new issue, or even better a PR, though. Feel free to link it to this discussion, and/or to borrow or steal any parts of the implementations discussed.
I don't think there was any sense that it was a bad idea. I grew to like it, and others had much positive to say. It was just a matter of whether it made sense for Ramda.
Most helpful comment
@antonagestam:
Yes, of course. But we cannot include every function that might be useful to someone, sometime. We always need to find the balance between how widely used a function would likely be, and how easy it is to write yourself. We use the Cookbook and other manage extension libraries for things that don't make the cut but are themselves useful.
You're right that there was some real interest in this, but none of us seemed to see how it would fit well with Ramda. If you want to make a new proposal, I can't promise that it would be accepted, but I can promise a fair hearing. I do think it should be a new issue, or even better a PR, though. Feel free to link it to this discussion, and/or to borrow or steal any parts of the implementations discussed.
I don't think there was any sense that it was a bad idea. I grew to like it, and others had much positive to say. It was just a matter of whether it made sense for Ramda.