5 ms·
I don't feel like any of these are particularly compelling examples, particularly if you are familiar with more languages beyond Java and Python which are used
by kscaldef 17y ago
I don't feel like any of these are particularly compelling examples, particularly if you are familiar with more languages beyond Java and Python which are used for comparison in the article.
In both the file handling and mutex example, blocks seem to be serving as a substitute for proper lexically scoped variables. In Perl, for example, we would use a lexically-scoped file handle to ensure the file is closed when the variable goes out of scope. The same technique is the standard way of implementing mutexes in C++.
As for respond_to, I've never understood how it wasn't just a baroque rewriting of a case-statement, but perhaps someone can enlighten me as to why blocks are superior in this situation.
- wycats 17y agoThe problem using a case statement is: what are you casing on? In order to properly do content negotiation, you first need to know all of the possible provided formats, so you can negotiate against the "Accept"ed ones. In order to do it with a case statement, you'd have to repeat the list of provided Mimes: once so the content_type method knew how to negotiate, and then once for each switch in the case statement. Using a block here allows us to make a native'ish case statement that eliminates the duplication.
- kscaldef 17y agoThanks you, that does make sense. Can I suggest adding that explanation to your article so it's clear that the example actually does do more than simply switching on a singular requested format? (I'm actually curious in practice how often the "Accept" header is really used to specify the format, and how often the format is simply coming from the extension on the requested path. I've only ever encountered the latter case, which I why I was thinking of the format as a single-valued variable you can switch on.)
- dmnd 17y agoIt's true that lexically scoped variables can replace closures when all you're using closures for is to wrap a section of code in starting and ending sections. But blocks are more general purpose than this. Have a look at the clean API that blocks have enabled in Ruby's Enumerable module, for example. Once you're using blocks/closures, why not use them to acquire/release resources as well?
- wedesoft 17y agoBlocks are objects themselves and they preserve their access to the local variable scope (i.e. they are "lexical closures"). This allows you to write control structures yourself. E.g. if-statement: def my_if( cond ) if cond yield end end x = 3 my_if x < 5 do puts "#{x} is lower than 5" end # prints '3 is lower than 5' Unfortunately the syntax for a control structure accepting more than one block is less elegant. But you don't need to compromise on the semantics: def my_if_else( cond, a, b ) if cond a.call else b.call end end x = 2 my_if_else( x < 0, proc do puts "#{x} is lower than zero" end, proc do puts "#{x} is greater or equal zero" end ) # prints '2 is greater or equal zero'
- kscaldef 17y agoSure and I understand all that. My comment was just that the examples in the article are perhaps not the best ones to use because they are simply replicating what other languages do with lexically scoped variables, and users of those languages view the lack of such as a deficiency of Ruby.
- swannodette 17y agoHow does a lexically scoped variable close the file? I couldn't find any documentation about this while Google searching. Is this a real language feature where you can specify what happens to a variable when it goes out of scope? In other words, is it a baked-in convenience or a real extensible abstraction?
- kscaldef 17y agoYes, you can absolutely specify a destructor which will be called when a lexical / local variable goes out of scope in Perl or C++ (just to name the most well-known languages with this feature).
- wedesoft 17y ago#include <fstream> using namespace std; int main() { { fstream f( "test.txt", ios::out ); f << "Hello" << endl; } // File gets closed // ... return 0; }
- grandalf 17y agoIn ruby you can create a file pointer as a lexically scoped object: def write(something) f = File.open("/path/to/somewhere", "w+") f << something end The variable f is local to the write method. Without explicitly closing the file, it may not be flushed to disk until the ojbect is garbage collected (I think).
- kscaldef 17y agoThat delay until the next garbage collection is a big caveat. While you might get away with this for file handles in some situations (honestly, I'm not sure that File#close really guarantees a write to disk anyway), it makes this technique essentially useless for mutexes. (Edit: I should also point out that that's a function-scoped variable, not a lexically-scoped variable. Ruby doesn't have lexically-scoped variables.)
- aaronblohowiak 17y agoFile.open('path', 'w') do |f| f << something end will do what you want
- kscaldef 17y agoIt might be good to read the whole discussion, particularly noting what I personally have said, before making conclusions about what I want.
- aaronblohowiak 17y agoI should have simply explained that my example will close the file handle and flush the changes out at the end of the block passed to the anonymous file handle, instead of making a statement about your wants.
- bad_user 17y agoFor the "resource acquisition is initialization" technique, you are limiting yourself to a reference-counting garbage collector, or to manual or stack allocation/deallocation (like in C++), otherwise you can't have a deterministic behavior. For example this technique wouldn't be possible on a virtual machine like the JVM or .NET. Yes, you could say something like "when a variable goes out of scope, you need to call the dispose method on the object". But what happens in a case like this? ... { open my $fileh, '<', $path; $another_object->add_file_handle($fileh); } The intention here is to preserve the file-handle in $another_object. So what happens? Is $fileh closed when going out of scope? Or is another file handle opened when the reference is copied? Really, it's not really clear when the handle is closed. Not like in C++ anyway. And Perl is a really bad example, what would really be needed is something like the "use statement" we have in F# ... { use fileh = File.OpenText(path); ... } But this is a special form. And that's the trouble ... Ruby's blocks solve a lot of problems that are solved in other languages with special forms (not that F# doesn't have closures or anonymous blocks).
- sreque 17y agoLexical scoping has almost nothing to do with Perl's ability to close file handles as you describe. As others point out, it's RAII with reference-counted garbage collection. This feature is not-thread safe, which is one reason why perl threads require making an entire copy of the whole program state and treating shared variables specially. The former makes perl threads a mostly useless abomination and the latter causes other problems. For instance, a friend of mine at work who didn't know the full details of perl threads couldn't figure out why his threaded program was running abysmally slow and using tons of memory. When he found out, he ended up just switching to a forking process model. As for the specialness of shared variables, another friend of mine wrestled with the fact that the perl serialization libraries for whatever reason couldn't handle serializing shared variables and he as far as I know never found a way to solve the problem. Bottom line, Perl's morass of bad language design leads to lots of problems that more elegant solutions like ruby's blocks can avoid. Also, reference counting is considered computationally less efficient than global garbage collectors. See the recent paper Myths and realities: the performance impact of garbage collection, which shows that a reference-counting garbage collector performs significantly worse than a mark-and-sweep algorithm.