Forum: Ruby Smalltalk's favorite Boolean method

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Ernest M. (Guest)
on 2006-04-11 17:13
Hi,

For a long time I have been programming Smalltalk.
Recently, Ruby "got me" and I was wondering how the
famous ifTrue:ifFalse: could be added to the System.

1 = 2
  ifTrue:
   [Transcript show: "true!" ]
  ifFalse:
   [Transcript show: "false!"]

So at least the following works but I was hoping that it would have the
same elegance. "proc" was needed to create a real block instance.

------------------
def true.ifTrue_ifFalse(trueBlock , falseBlock)
	trueBlock.call
end

def false.ifTrue_ifFalse(trueBlock , falseBlock)
	falseBlock.call
end

(1==2).ifTrue_ifFalse(
   proc { p "true!"},
   proc { p "false!"}
)

(1!=2).ifTrue_ifFalse(
   proc { p "true!"},
   proc { p "false!"}
)
James G. (Guest)
on 2006-04-11 17:35
(Received via mailing list)
On Apr 11, 2006, at 8:13 AM, Ernest Micklei wrote:

>    [Transcript show: "false!"]
>
> So at least the following works but I was hoping that it would have
> the
> same elegance. "proc" was needed to create a real block instance.

Here's an alternate syntax that does away with the need to call proc():

 >> class Object
 >>   def true?
 >>     yield self if self
 >>     self
 >>   end
 >>   def false?
 >>     yield self unless self
 >>     self
 >>   end
 >> end
=> nil
 >> (1 == 2).true? { puts "True!" }.false? { puts "False!" }
False!
=> false
 >> (1 != 2).true? { puts "True!" }.false? { puts "False!" }
True!
=> true

Hope that gives you some fresh ideas.

James Edward G. II
Christian N. (Guest)
on 2006-04-11 19:26
(Received via mailing list)
James Edward G. II <removed_email_address@domain.invalid> writes:

> => nil
>>> (1 == 2).true? { puts "True!" }.false? { puts "False!" }
> False!
> => false
>>> (1 != 2).true? { puts "True!" }.false? { puts "False!" }
> True!
> => true
>
> Hope that gives you some fresh ideas.

*hears 1000 functional programmers whine about return values getting
lost*
James G. (Guest)
on 2006-04-11 20:57
(Received via mailing list)
On Apr 11, 2006, at 10:24 AM, Christian N. wrote:

>>>>   end
>
> *hears 1000 functional programmers whine about return values
> getting lost*

I didn't say I *liked* it.  I just said it got rid of the need to
call proc() twice.  A boring if statement is all I need.  ;)

James Edward G. II
Jim W. (Guest)
on 2006-04-11 23:25
Christian N. wrote:
> *hears 1000 functional programmers whine about return values getting
> lost*

Just so all those functional programmers don't lose their return values:

---------------------------------------------------------------
class Object
  def true?
    TrueValue.new(yield)
  end
  def false?
    TrueValue.new(nil)
  end
end

class FalseClass
  def true?
    FalseValue.new(nil)
  end
  def false?
    FalseValue.new(yield)
  end
end

class NilClass
  def true?
    FalseValue.new(nil)
  end
  def false?
    FalseValue.new(yield)
  end
end

class BooleanValue
  attr_reader :value
  def initialize(val)
    @value = val
  end
  def true?
    self
  end
  def false?
    self
  end
end

class TrueValue < BooleanValue
  def true?
    TrueValue.new(yield)
  end
end

class FalseValue < BooleanValue
  def false?
    FalseValue.new(yield)
  end
end

require 'test/unit'
class TestTrueFalse < Test::Unit::TestCase
  def test_false_results
    assert_equal nil, false.true? { no }.value
    assert_equal yes, false.true? { no }.false? { yes }.value
    assert_equal yes, false.false? { yes }.value
    assert_equal yes, false.false? { yes }.true? { no }.value
  end

  def test_nil_results
    assert_equal nil, nil.true? { no }.value
    assert_equal yes, nil.true? { no }.false? { yes }.value
    assert_equal yes, nil.false? { yes }.value
    assert_equal yes, nil.false? { yes }.true? { no }.value
  end

  def test_true_results
    assert_equal yes, true.true? { yes }.value
    assert_equal yes, true.true? { yes }.false? { no }.value
    assert_equal nil, true.false? { no }.value
    assert_equal yes, true.false? { no }.true? { yes }.value
  end

  def test_object_results
    assert_equal yes, "hi".true? { yes }.value
    assert_equal yes, "hi".true? { yes }.false? { no }.value
    assert_equal nil, "hi".false? { no }.value
    assert_equal yes, "hi".false? { no }.true? { yes }.value
  end

  def test_true_true_chains
    check_chain(true, :true?)
    check_chain("hi", :true?)
    assert_equal nil, nil.true? { no }.true? { no }.value
    assert_equal nil, false.true? { no }.true? { no }.value
  end

  def test_false_false_chains
    assert_equal nil, "hi".false? { no }.false? { no }.value
    assert_equal nil, true.false? { no }.false? { no }.value
    check_chain(nil, :false?)
    check_chain(false, :false?)
  end

  def check_chain(value, method)
    i = 0
    assert_equal 3,
      value.
      send(method) { i += 1 }.
      send(method) { i += 2 }.value
    assert_equal 3, i
  end

  def yes
    :yes
  end

  def no
    fail "Should never execute this"
  end
end

--
-- Jim W.
Roy S. (Guest)
on 2006-04-12 09:17
(Received via mailing list)
What about:

case (1 == 2)
  when true:
     p 'true'
  when false:
     p 'false'
end

If you don't like the ternary operator the above syntax is pretty clear.

Roy
Jim F. (Guest)
on 2006-04-12 17:05
(Received via mailing list)
On Apr 11, 2006, at 2:25 PM, Jim W. wrote:

> Christian N. wrote:
>> *hears 1000 functional programmers whine about return values getting
>> lost*
>
> Just so all those functional programmers don't lose their return
> values:

[Massive amount of code snipped]

Me thinks Jim W. is ether really smart or just has too much free
time.
:)


Jim F.
Jim W. (Guest)
on 2006-04-12 19:07
Roy S. wrote:
> What about:
>
> case (1 == 2)
>   when true:
>      p 'true'
>   when false:
>      p 'false'
> end
>
> If you don't like the ternary operator the above syntax is pretty clear.

Ah Roy ... You are spoiling the fun by being practical (insert big wink
here).

Actually the point of the exercise is to demonstrate that if/then/else
can be completely implemented in terms of polymorphic method sends
without using an if statement (or ternary operator) in that
implementation.  It is kind of a theoretical point that conditional
operations can be implemented with polymorphism (just as polymorphism
can be implemented with condition primitives).

No one is advocating that this code be used in preference to Ruby's
natural if statement.  We are just showing that it can be done fairly
easily.  (If you want a real challenge, try implementing the same in
Java ... same rules, no if statements allowed in the implementation.  I
managed it once, but I had to resort to using a Hash table for lookups).

The same can be done with looping constructs.  Here's an implementation
of a "while" loop done entirely with polymorphic method calls.  (Since
while is a reserved word, I'm using 'repeat' instead)

Example Usage:

   i = 0
   repeat { i < 10 }.do { puts i; i += 1 }

Implementation:

   alias repeat proc

   class Proc
     def do(&block)
       call.false? { return nil }
       block.call
       self.do(&block)
     end
   end

Much simplier than the if thing, mainly because you generally don't
worry about return values from the while loop.  Notice the use of tail
recursion.  If Ruby were properly tail recursive, that implementation
would be adequate.  But since we face the possibility of statck
overflow, here's a (slower) version without the overflow problem:

   class Proc
     def do_cc(&block)
       goto = nil
       callcc { |cc| goto = cc }
       call.false? { return nil }
       block.call
       goto.call
     end
   end

All in fun and games.

--
-- Jim W.
This topic is locked and can not be replied to.