On 12.12.2004, at 00:06, zuzu wrote:

>> string.puts(STDOUT) creates a coupling, the string must call a method 
>> of the
>> given object with itself as the argument. It must know that the 
>> object it's
>> passed responds to #puts.
>
> i think to me this seems natural for the pure-OO "everything is an
> object" nature of ruby (or  smalltalk).  strings know how to display
> themselves, they just need to know where.  again, i think "naked
> objects" is the latest way to talk about this, though i often think of
> self language which was built on smalltalk.

Regarding the "string displaying itself"-bit, my view is that a string 
can suggest how it should be displayed, but the final call is in the 
hands of the display object.
As dataflow: cat string | /dev/display


>> The difference to Ruby's method.method.method is that the shell pipes 
>> aren't
>> namespaced to the preceding object. Ie. With shell pipes it'd be 
>> quite doable
>> to except the following to work in every situation:
>>
>> gets | reverse | puts , because they are in the global namespace. 
>> What you pass
>> through the pipe doesn't change the available methods.
>>
>> In ruby
>>
>> gets.reverse.puts
>> creates a dependency chain.
>>
>> gets must return something that responds to reverse, and reverse must 
>> return
>> something that responds to puts. Which is bad.
>
> hmmm... is this any different than unix which works blindly in 
> bytestreams?

The shell pipes differ in that shell commands are all "instance methods 
of class Bytestream", but in Ruby the input object and output object 
may be of different classes. So there's a possibility of interface 
mismatch.

With shell commands, the methods all have the same input and output 
type, but they are liable to fail if the incoming stuff doesn't quite 
agree with what the command was built to handle.

cat my.wav | lame - - | madplay -
cat my.txt | lame - - | madplay -
# if my.txt happens to be a valid wav file, it works
# if not, lame blows up.

Compared to:
Wav.new('my.wav').lame.madplay   # works, may fail inside call to lame 
if input is bad
Txt.new('my.txt').lame.madplay   #=>  NoMethodError, even if my.txt is 
a valid wav file


The way shell commands work could be rubyized as:

require 'thread'

class ShellPipe
   # create method for each command in each path with
   # paths in front overriding later ones
   # quite obtuse.
   ENV['PATH'].split(/:/).reverse.each{|path_root|
     Dir[File.join(path_root, "*")].each{|bin_name|
       define_method(File.split(bin_name).last){|*args|
         full_cmd = [bin_name, *args].join(" ")
         self.class.new(
           self,
           IO.popen(full_cmd, 'r+')
         )
       }
     }
   }

   def initialize(input, filter, stopped=false)
     @input = input
     @filter = filter
     @stopped = stopped
     @mutex = Mutex.new
     @cond = ConditionVariable.new
     suck_on_input
   end

   def silently_fail
     yield
   rescue
   end

   def method_missing(m, *args, &block)
     @filter.send(m, *args, &block)
   rescue NoMethodError
     super
   end

   def suck_on_input
     silently_fail{ @input_sucker.kill }
     @input_sucker = Thread.new{
       begin
         @mutex.synchronize{
           until @input.eof?
             @cond.wait(@mutex) if @stopped
             @filter.write @input.read(4096)
           end
         }
       ensure
         @filter.close_write
       end
     }
   end

   def start
     return nil unless @stopped
     @mutex.synchronize{
       @stopped = false
       @cond.signal
     }
   end

   def stop
     @stopped = true
   end

   def read(*args)
     @filter.read(*args)
   end

   def close
     @input.close if @input and not @input.closed?
   ensure
     @filter.close
   end
end

my_wav = ShellPipe.new(nil, File.open("my.wav"))
player = my_wav.lame("-", "-").madplay("-")
player.read
player.close # cascades up the pipe



Got a little carried away there, sorry about that :)