Numbered block parameters in Ruby
zverok.space
zverok.space
In my taste as a PL enthusiast, the biggest problem with this kind of anonymous parameters is a conflict with nested blocks. Suppose that our fictitious language is expression-oriented, `{ |x| ... }` denotes an explicit closure, `{ ... }` denotes an implicit closure and `$1`..`$n` are anonymous parameters in them. Consider the following examples:
{ $1 + $2 } // Yeah, that would be `{ |x, y| x + y }`
{} // Is this a zero argument function?
{ $100 } // Does this have 100 arguments???
{ |x| $1 + $2 } // Maybe this should be invalid...
{ { |x| $1 + $2 } } // But maybe this should be okay??
{ { |x| $1 + x }($2) } // And same to this???
{ { |x| $1 }($2) } // Uh...
{ { $1 }($2) } // Should be same to above, but...
{ { $2 }($1) } // Not this (and will fail)!
The problem is much worse when you don't even have an explicit boundary like `{ ... }`. A limited form of automatic function, say `_ + _` without no parentheses, might work, but anything beyond that quickly devolves into a mess.1. The function has to use all of its arguments at least once. You can’t do `&(&2)`, because it skips the first argument.
2. Single-expression. You can’t do something like `&(x = &1; x + 1)`.
3. No nesting. You can’t define an additional anonymous function inside of a & expression, or even use a `&SomeModule.some_fn/arity` capture. For anonymous functions with nesting, you have to use the `fn arg1, arg2, ... -> body end` syntax.
ghci> map reverse ["hello", "goodbye"]
["olleh","eybdoog"]
is (mostly) equivalent to the more verbose form with an explicit lambda: ghci> map (\s -> reverse s) ["hello", "goodbye"]
["olleh","eybdoog"]
Operators are just normal functions, but tuples are not automatically uncurried. Here I'm using the multiply operator (*) to multiple a list of pairs: ghci> map (*) [(1,2), (3,4)]
ERROR
ghci> map (uncurry (*)) [(1,2), (3,4)]
[2,12]
Points-free syntax can be used to construct chains of operations without any explicit lambda variable. Here filter strings of more than two characters: ghci> filter ((>2) . length) ["a","bb","ccc","dddd"]
["ccc","dddd"]
In lambda syntax: ghci> filter (\x -> (length x) > 2) ["a","bb","ccc","dddd"]
["ccc","dddd"]
I find the Haskell syntax concise and clear, and I always miss it when using other languages (despite Haskell's other shortcomings).Edit: formatting
Surprisingly few attempts even seem to get over the bar of understanding what currying is, and why it isn't just partial application. Now, I am not too concerned with which is "better" or anything, nor do I much care for pedantry for the sake of pedantry, but in this case it's a matter of type signatures; curry(some_function) and partial_apply(some_function, param1, param2) are fundamentally different type signatures and for that reason, not because of abstract verbal purity or something, aren't the same thing.
I mean, right out of the gate you have the problem that in most conventional languages you're looking at your first example being map(reverse)(["hello", "goodbye"]).
Whether Ruby syntax could be bent into making it work without parens (or Perl perhaps) I don't know but with all the other things going on in those syntaxes I'm sure it would be quirky as can be, possibly quirky beyond practical usefulness. And in dynamic scripting languages you'd be looking at a lot of actual function calls, so performance is going to be trashed if you use this pervasively.
For the same reason, I disagree that performance in dynamic scripting languages would suffer unduly.
This syntax wouldn't work in Ruby, because bare `reverse` is already a method call, not a reference to a method by name. Allowing method calls without parentheses is crucial to Ruby's design, where all objects are fully opaque, and every `obj.attr` looking like a getter, is just a call to an instance method `attr`. (This is, as far as I understand, the opposite to similar languages like Python/JS, where the object is a dictionary of attributes, and `obj.method` reference the attribute of type "method", while `obj.method()` is an invokation.)
This design became a huge drawback in the age of functional(ish) programming, because the simplest way for refer to a method in Ruby is `method(:its_name)` (which is also inefficient, because it creates wrapping object of class Method on the fly, there is no pre-existing first-class object), and any attempt of passing/combining methods would be cumbersome due to it.
FWIW, you can do that in Ruby:
(JSON.method(:parse) >> method(:puts)).call("[1, 2, 3]")
...which is semantically cool, but looks ridiculous.Another important trait of Ruby's design is that most of the important methods belong to their first argument, so it is not `reverse(string)` but `string.reverse`, so even to obtain a reference to a method, you need to have an object it belongs to! So you can't do that:
method(:>).call(a, b)
...because all operators are called on their first operands, so you need this this: a.method(:>).call(b)
...which will require to refer to at least a first argument of the operator, so you can't produce "argument-less comparison operation to call later".But when you get down into the weeds of what it means, it turns out to be something you can't "just" drop in. You really need to build the syntax for it from day one.
You absolutely can, if you know the specific one (that is, type) you need.
E.g.,
gt = Integer.instance_method(:>).method(:bind_call)
# gt is a two arg callable functionally equivalent to ->(a,b) { a < b } for Integer a, Any b
Of course, doing this instead of just doing: gt = -> { _1 > _2 }
loses Ruby duck-typing and dynamism with regard to the first parameter; which may be acceptable in some cases, but not others. Can you do the general form without an explicit lambda? Probably there is a way, but its not easy. Surprisingly few attempts even seem to get over the bar of understanding what currying is, and why it isn't just partial application.
Huh, what is the difference between currying and partial application? I always thought the terms were interchangeable.Partial application (of one arg) takes a function that is of type:
(a, b, c) -> d
And makes it: a -> (b, c) -> d
Currying makes it: a -> b -> c -> d
Ruby’s Proc#curry does magic that mostly lets you pretend its either or something in between.The first example with map reverse is very readable and an improvement over lambda syntax. The chaining example (filter on length) is horrible for readability and I greatly prefer the lambda syntax.
If you're fine with the extra syntax, yes, there are many alternatives, but they don't address the point.
> We might also start look into the concept of the “tacit programming”, which, from some point of view, is also about “not repeating the arguments,” but this would be a much longer post—while it is obscenely long already.
Note though that my analysis was mostly about "how you would do that in an established language," not "how would I design a language from scratch." And Ruby is very different from Haskell in its syntax, semantics, and typical intuitions of the language writer and reader.
So designing a solution for clearer representation of "default parameters" is quite different in a language which is built around an `object.method(argument) { block }` as its atomic phrase, and one built around `function(arguments)`. (This can turn into a discussion of which phrase structure is more elegant in general... But I believe it was already done quite a few times in a history of programming languages evolution!)
irb(main):001:0> ["foo", "bar"].map(&:reverse)
=> ["oof", "rab"]
Not precisely the same as what you've outlined, of course, but perhaps useful nonetheless. module MissionControl
#...
[Proc, Method, Binding, UnboundMethod].each_with_object(self, &:include)
end
if one were so inclined.Ruby doesn’t have functions, but any one parameter block (to which any one parameter callable, and some other things, can be converted with an &) can be passed as the block parameter to map. And, yes, you can use function composition and similar operations to build up more complex callables from existing ones without an explicit new block or lambda.
Your examples mostly have pretty close parallels in ruby:
irb(main):044:0> ["hello", "goodbye"].map &:reverse
=> ["olleh", "eybdoog"]
is the more compact form of the form with the explicit lambda: irb(main):045:0> ["hello", "goodbye"].map {|s| s.reverse}
=> ["olleh", "eybdoog"]
composition works similarly, though there is no compact notation to convert an operator and its right operand to a callable, only the left: irb(main):048:0> ["a","bb","ccc","dddd"].filter &(2.method(:<) << :length.to_proc)
=> ["ccc", "dddd"]
But, you do have to cheat a little to do: irb(main):049:0> [[1,2],[3,4]].map &:*.to_proc.uncurry
=> [2, 12]
in place of: irb(main):049:0> [[1,2],[3,4]].map {|x, y| x * y}
=> [2, 12]
Because while ruby has Proc#curry, it lacks Proc#uncurry. But its simple to define: class Proc
def uncurry
->(args) { self.call *args }
end
end> ["a","bb","ccc","dddd"].filter &(2.method(:<) << :length.to_proc)
I can't imagine a stronger case for GP's thesis. The point-free ruby rewrite is so baroque and needlessly circuitous... the extra variable is a far lesser evil, and more concise:
["a","bb","ccc","dddd"].filter { |x| x.length > 2 } ["a","bb","ccc","dddd"].grep /.../
but then, I'm a recovering perl hacker irb> [[1,2],[3,4]].each_with_object(:*).map(&:reduce)
=> [2, 12]
Given the mechanics of this I wouldn't claim it meets a strict definition of point-free, but that's an iterated application of operator-like methods omitting any explicit named/positional argument references.It's unfortunate because readers on HN are already responding to the title instead of to the content...
In any case, I made the mistake of assuming everyone is currently familiar with the idiom just because it was widespread when I joined HN.
I suppose at one point it'll be a mistake to assume everyone knows what ROTFL or LOL or IMO mean, just because they used to be widespread internet culture at one point.
Also, TFA used singularly is different from just saying "the article", e.g. when you want to imply RTFA. Within your comment you could see the "F" part was quite out of nowhere and can be considered rude.
Not if you see actual usage: https://hn.algolia.com/?dateRange=all&page=0&prefix=false&qu...
Context matters, and people here usually interpret it as "The Fine Article", and it was indeed written in that sense. It's supposed to be a shortcut (not rude like RTFM, which does have a meaning of impatience because it's directed at people perceived as too lazy to search the docs).
Anyway, a shortcut that requires this much explanation is a failed shortcut, and I apologize for that. It's sometimes hard for me to realize that what used to be a really common idiom that needed no explanations can quickly become something unfamiliar to new readers. Just in case, I won't use it again.
See usage in HN (yes, the latest ones are mine): https://hn.algolia.com/?dateRange=all&page=0&prefix=false&qu...
Sorry, didn't mean to confuse.
I've seen the idiom so many times here on HN I assumed it was widespread. It means "The Fine Article" (replace Fine with Fucking if feeling impatient).
It comes from the extremely well known idiom RTFM, for Read The Fucking Manual, used in unixy/hacker circles when someone asks for something trivially answerable by simply reading the manual.
I forgot that with new generations of techies, the old idioms become obscure or are replaced by newer idioms!
But I am afraid it is hard to do something to prevent people communicating with article title on its own. Happens all the time with me, whatever I write about! (I tried boring over-explanatory titles, people still argue with _that_.)
> My homegrown theory is that the idea of array.each { ... } as the main syntax for cycles was the first “aha!” of the new language development for its author
> The style that flourishes with this new shortcut is representing computation as a chain of small blocks, declaratively describing transformations from inputs to outputs. The style that is native to Ruby, the first mainstream language to have enumerable.transformation { ... } as the main cycle construct, and, I believe, played a role in normalizing “map”, “reduce”, and “filter” as a way of thinking about how things are handled.
Matz got that from Smalltalk. That's why most of the enumeration methods in Ruby match the names of similar methods in Smalltalk ("collect", "reject", "select", etc.) and not the corresponding names in functional languages ("map", "reduce", "filter"). The "each" method is the odd one out because that's called "do" in Smalltalk and "do" is a reserved word in Ruby.
Smalltalk definitely isn't mainstream today, but it did have its day in the sun where it was one of the better-known languages.
I do believe though that a lot of modern languages got that from Ruby (or, via Ruby, from "JS that Rubyists wrote" and such), when it was a "hot new thing" for a brief moment in 2000s. Or maybe it is my skewed perspective.
Ruby has both, actually; "map" is a synonym for "collect", "reduce" is a synonym for "reject", and "filter" is a synonym for "select" (well, actually, they are both synonyms for "find_all"). And the non-Smalltalk names seem to have had more influence on newer methods, e.g., Enumerable has filter_map, but not select_collect, even as a synonym.
Yes, the dictionary is normalized in the last years towards what became industry/mainstream standard names for the concepts, but the statement that initial names were derived from Smalltalk is correct.
things.each { puts _thing }
pairs.map { _x * _y }
It's kinda insane, but in the beautiful way that Ruby is.I'm not sure I see the value of ".then"... the one-off variable names on the side make it easy for me to scan quickly and understand what is happening w/o having to procedurally view the right side of the assignment. If you want to ensure that these vars are truly one-off, wrap it in a block or method.
I don't think Ruby's syntax needs to evolve. I just wish the core team cared more about opening up the lang to the improvements which were being offered by e.g Chris Seaton (RIP) and his colleagues. I wish typing proposals were being hotly debated etc. I'm sad that after 20 years I don't really want to use Ruby or Rails going forward despite how incredible the ecosystem really is, mostly because the data science, typing, and FE integration story is weak now.
I never realized how _nice_ it can be to have types in a dynamic language, even if they’re just annotations.
To be fair though Ruby is a much much harder language to wrangle under types than python.
We also don't allow single letter variable names. I'm an old school rubyist and find this to be an annoying constraint that can't be auto-corrected so it adds a little friction to writing code but arguing for more terse code is always a losing proposition so I just deal with it.
To be strictly serious we are talking about block parameters in ruby which contain the values yielded to it which, in an enumeration, is likely to be a value object from a collection and only occasionally an index (where you've explicitly chained `#with_index` for e.g.). If you are nesting deep enough in ruby to call for `k` then, yes, it would be bad practice.
However, saying that the latter is "more readable" lacks foundation imo... both are conventions, both are simple to understand, and you can adapt to either quickly. I'd argue this is a case of "familiarity"/"readability" confusion.
A more practical argument is that "|u|" is more widely known by inexperienced ruby devs, and hence has an advantage on larger teams, especially those with turnover. With that said, I agree with author's position.
The only case where I've used a shorthand pattern like _1 is with regex matching where the matched groups are not named, so $1, $2, $3 refer to the first, second, third matched groups, etc. Having worked a lot with csvs (mentioned in an example), referencing the header (or creating one) has always been really valuable, using numbers is asking for trouble when the spec inevitably changes or when a dev wants to know if a script will work with some new file.
To take the exact example you gave, `users.map{ |u| do_something(u) }` is unambiguously better than `users.map{ |user| do_something(user) }`.
What we really want is `users.map{ do_something }`. It is only happenstance that ruby allows this form for methods only via `users.map(&:some_method)`, and we are stuck with forced variable names when calling other functions. Still, we can reduce the boilerplate as much as possible, and this is the basic argument of the article.
Also fine: `users.map{ |x| do_something(x) }` (the "x" as a conventional placeholder for the meaningless but syntactically necessary placeholder) and `users.map{ do_something(_1) }` (the _1 accomplishing the same thing). In either case, the "meaningful variable" is already there in the name "users" -- repeating it is mere stutter and detracts from, rather than enhancing, clarity.
The exception (as you noted) would be a longer multiline block where repeating `user` can aid short term memory, especially if the block contains other local variables.
Not sure what you are trying to say here, because there are so much odd about this that it is hard to penetrate.
First, & works on proc-able objects (those that are Procs or can be converted to them via #to_proc), not "methods", and what you show it being used with is a symbol, not a method. Symbol#to_proc on a symbol s produces a proc that is functionally something like:
->(*args, **kwargs) { args[0].send(s, args[1..-1, **kwargs] }
While that's complicated, the upshot is that it creates a proc that calls the method whose name is the symbol & was applied to on its first argument, passing its remaining arguments as the arguments to the method.> What we really want is `users.map{ do_something }`.
But if you want to pass a method on self, the only thing in Ruby that you can call like do_something(x), to map, you can do that without an expllcit block already, by using the Object#method method, which returns a Proc that is equivalent to calling the method:
users.map &method(:do_something)
And if you want to call a Proc object in a local variable called do_something to map (the closest thing to a "non-method function" that exists in Ruby), you can do that without an explicit block, even more concisely, because you don't have to convert it to a Proc first: users.map &do_somethingIn many languages (even in JS!) you can just map a function point-free, rather than having to introduce a variable just because syntax demands it, or worry about which syntax to use depending on if the "function" you're calling is a method on the object being mapped or something else. It's all noise.
> you can do that without an expllcit block already, by using the Object#method method, which returns a Proc that is equivalent to calling the method:
> users.map &method(:do_something)
Again, I am aware, and the ugliness of that solution proves the point I was trying to make -- it is even worse than the block syntax with a variable.
Yes, in many languages, "functions" are a thing that exists. In Ruby, there is no such thing as a function. The concept does not exist.
Which is why it is bizarre to talk about what you can and can't do with "functions" in Ruby, and to call kinds of different not-function things (including symbols, of all things) "functions" in that discussion. Its just applying language that makes no sense.
If variable names are well picked they provide guiding context hints that ease understanding. If you have only a terse pack of symbols, deciphering the code will be longer because you have to go trough more contextualization to understand where the code take place and what it actually operates over for which purpose.
This reminds me a lot of using(&:blabla). I'd prefer it: `billing_subscriptions.each { |bs| bs.status = ACTIVE}`
But don't mind me I'm just bikeshedding
With only one variable, and sometimes with very simple expressions with more than one, you probably get a readability benefit by avoiding stuttering:
{ _1 + _2 }
reads a little better than: { |x, y| x + y }
But with any even slightly complex expression in the block, that goes from a benefit to a cost.> serialize :salary, format: -> csv_currency(_1)
> I didn’t analyze this deeply, but “intuitively” this seems enough syntax for the interpreter to understand “the next code is one-statement lambda’s body.”
I believe that Swift has an @autoclosure attribute which allows this?
;; Single argument
(fn [x] (* 2 x))
#(* 2 %)
;; Multiple arguments
(fn [x y] (+ x y))
#(+ %1 %2)
;; Remaining arguments
(fn [x & args] (reduce + x args))
#(reduce + % %&)
Example copied from this nice page - https://silviapan.com/blog/anonymous-functions-clojure/
{[a;b]a+b}
But if you leave it off, the names "x", "y", and "z" implicitly refer to the first three arguments, and their presence controls the valence (arity) of the lambda. {x+y} / a lambda with two arguments
{y} / also a lambda with two arguments, discarding the first
Using the implicit names for short definitions is highly idiomatic. By removing an unnecessary degree of freedom, it increases the likelihood that two K programmers will arrive at identical formulations of a short piece of code.[1] https://en.wiktionary.org/wiki/thither [2] https://en.wiktionary.org/wiki/thilk [3] https://en.wiktionary.org/wiki/said#Determiner
Having to write [](int x, int y) { return x*y } even for the simplest lambda is a pain.
Rust's syntax looks ideal: |x, y| x*y. Haskell's is also sweet.
For my own purposes I've defined macros so that I can write L1(x+5) or L2(x*y) (the x and y are hardcoded in). Not elegant but it keeps me sane.
More seriously, even something like "(x,y) => x*y" would be nice.
edit: I forgot that I managed to make the $(x, y)(x*y) syntax work a few years ago when trying to emulate constexpr lambdas:
I also hate the name "lambda" because it feels intentionally confusing and special. It's just a function that captures some context.
Here’s a few simple examples cribbed from their documentation:
// Set all elements to 1
for_each(a.begin(), a.end(), _1 = 1);
// Print elements separated by spaces
for_each(b.begin(), b.end(), std::cout << _1 << ' ');
// Sort in reverse order
sort(c.begin(), c.end(), *_1 > *_2); ENV.fetch('CONFIG_PATH')
.then &File.method(:read)
.then &YAML.method(:parse)
.dig('metadata', 'created_at')
.then &Time.method(:parse)
Really, if we want more compct syntax sugar for this, letting .:<identifier> be shorthand for .method(:<identifier>) would probably be better than _1 for this circumstance and more broadly useful for making functional-style programming more concise in Ruby.That being said, there are a few problems on this road, that make it less probable to be followed. First, as Ruby doesn't have first-class Method objects (e.g. object corresponding to `File.method(:read)` doesn't exist until you call `method(:name)` explicitly), this style being popularized will either bring a lot of subtle performance problems, or will require redesign of Ruby's internals.
Second, if we'll make an example _a bit_ more complicated, say:
ENV.fetch('CONFIG_PATH')
.then { File.read(_1, encoding: 'KOI8-U')
.then { JSON.parse(_1, symbolize_names: true) }
.dig('metadata', 'created_at')
.then { Time.new(_1, in: CURRENT_TIMEZONE) }
...it would be impossible to rewrite with `method`, until currying would be signficantly improved (currently it can only be applied to first arguments, not tail ones, and even then looks horrible). So it is not impossible, and it is a turn of the language I'd like to see in the future, but it is not as straightforward as I once imagined.1: https://zverok.space/blog/2022-01-13-it-evolves.html#taking-...
Perl does that with few thing, like regexp captures. It's kinda sorta neat for writing shell oneliners but that's about it. But then concise and intelligent design wasn't ever Ruby's forte.
Including $1 etc. for regexp captures, and a litany of other "$" variables.
If anything, those parts of Ruby's design tend to be among the aspects most often maligned, and we have to excuse it with "Perl did it, blame Perl".
Ruby is in many way the unholy offspring of Perl and Smalltalk. And I mean that mostly in a good way, but it also gave us warts.
And How do you say the opposite of sugar syntax in English? Because my lovely rustlang has a lot of sigil chars as old Perl, it makes me sad.
Syntax spice ? It burns when you eat/read and it burns when you write/poo
> Because my lovely rustlang has a lot of sigil chars as old Perl, it makes me sad.
I unironically prefer Perl mess, at least you don't have macros and annotations generating who-knows-what at random parts of code.
I mean, not that you couldn't in Perl, there is plenty of horrid things you can do using some more obscure Perl's features (my favourite so far was modifying other package's package-scoped variables), but most code in Perl doesn't.
edit —- I just checked, and salt is ‘name dropped’ in the Wikipedia article for syntax sugar, and there were a few more links making me think it is relatively common.
The idea behind 'sugar' is 'tasty but inessential', so the strict opposite would be something essential but not so tasty.
But maybe 'syntactic thorns' correspond better to the sense you intend to express.
I think you're comparing two different things. Kotlin has "annotations", not decorators. It's because of it's java inheritance, where that is the way it's done. It's two different concepts. With a decorator, your code is replaced with a function call with the function itself as a parameter. With an annotation, you're just adding metadata and an annotation processor or static analysis tool etc can do whatever it wants with it. Like aspect oriented programming. You mark your code as having certain properties, and then other code can find it and react to those.
I don't know of much other useless syntactic sugar in Kotlin? I think most of it is quite nice, for instance how you can put the lambda outside parenthesis or create infix operator for more readable code. I also like the implicit `it` parameter in lambdas, better than what this article shows. Doesn't support multiple arguments, though.
listOf(1,2,3).let { (a, b, c) -> a * b + c }
Which I think it's a good compromiseI believe it is a habit back from my journalistic days, when there were "insets" besides the main text with "things that deserve mentioning here, but kinda out of the main flow... but more related to it than footnotes." I miss the richness of markup of paper magazines!
Does it harm the reading experience significantly?
Especially when reading code, there's yet another special syntax form to learn, and one which has no real utility to structure the code better or to better communicate it's intent and/or operation.