New Ruby conditional semantics thoughts

B

Brian Mitchell

The basic background was begun by someone asking a question about
boolean expressions and _why_ Ruby treats expression results nil and
false as a *false* path in a boolean conditional. Some just explained
that nil is like empty so it is nice to treat is like false.
Someone then pointed out how one could use the methods false? and
true? on the object to test its value. This immediately reminded me of
smalltalk conditional design and possible ways one could implement
something similar in Ruby. For example, here are smalltalk
conditionals implemented in Ruby:

module Kernel
def _if(exp, &block)
else_block = callcc { |cont|
@condition = cont
return exp.true? &block
}
@condition = nil
exp.false? &else_block
end

def _unless(exp, &block)
else_block = cont { |cont|
@condition = cont
return exp.false? &block
}
@condition = nil
exp.true? &else_block
end

def _else(&block)
@condition.call(block)
end
end

class Object
def false?
nil
end

def true?
yield
end
end

class FalseClass
def false?
yield
end

def true?
nil
end
end

class NilClass
def false?
yield
end

def true?
nil
end
end

### Test It!###
a = nil
b = 2
c = false

_if(a != b) {
puts "a!=b"
}
_else {
puts "a==b"
}

_unless(b) {
puts "b is false"
}
_else {
puts "b is true"
}

Now this is just for if and unless. case and other conditions
could be made also but this is enough to give a basic idea (and save
me time). I will explain why this is a good thing. As I explained how
this whole thread started, there was a question as to why nil and
false were treated as "boolean false". Here we use duck typing (which
is right along side most ruby design) rather than just say that these
values to this. This also allows change in behavior and more false
types to be added to ruby. This will help the with implementation of
DSLs using ruby. The implementation of conditionals as methods brings
up another pro. conditions can be hooked, changed, and treated like
any other ruby internal. This has been usually noted to be a good
thing but a good use is hard to come up with... just as the argument
goes with callcc ;) . The other good note is the use of blocks. While
blocks now must be called (slower? I haven't bench marked yet), the
block can be passed around now or even come from a different binding.
This is only one way to do conditionals though. If you look at the
above method it is actually a three part method. One is to use methods
not values to evaluate a boolean, the second is to use a block that is
yielded upon, and the third is the implementation of conditionals as
methods of Kernel. Ruby currently uses none. Any could be adopted. I
would say that the adoption of conditionals implicitly calling false?
or true? respectively would be a good thing. the adoption of blocks is
a large jump and would probably require some syntax sugar to use
regularly. It also break compatibility with current code. Which might
not be as great of a thing. As for if as a function... this may or may
not be a positive change. I will explain bellow. I do have an idea on
how to keep current code compatible and add both the duck typing for
booleans and the block calling. The one thing is that conditionals as
functions must then be dropped. We would check all object via the
boolean methods false? and true? . The conditional would check for
truth (now mostly internal -- although true and false would still
exist). Truth would be easy to evaluate as false? would return true or
false and true? would do like wise respective to the object. If the
conditional is to execute then it checks for the use of a block. if so
it will yield the block. if no block was given it continues to
function as it does.
Now you may ask, why check for a block anymore? you could just use
my_proc.call. This is a valid point. The only reason that blocks are
there is for the illusion that conditions are not controlled by value
but rather by behavior of each object. The only reason I can think of
to add blocks to conditionals is for syntax sugar.
There is one more final method I have to cover. This method allows
for all three parts of smalltalk conditionals to be included and still
keep *some* backwards compatibility. In the description above... if
the conditional was passed a block it would then use the method
version. if it was not then it would use the plain old version.
Another thought would be convert the non-block version to a block and
then call if. The problem with this is scope semantics for variables
created within the conditional. As many of you know block variables
not present outside of the block (in context) are block local... this
is not currently so for variables created within a conditional. This
as been posted as a change for the Ruby 2.0 spec but I have not
researched this extensively.

Anyway... I feel like I am talking too much so I will end it here.
This is just food for thought and I have yet to decide if I have the
intention to write up an RCR for this. I also should note that I am
rather new to Ruby so i may have errors in the above statements ;) .
So take it lightly.

Cheers,
Brian Mitchell
 
T

trans. (T. Onoma)

Nice work. Your explanation is a bit terse, but I'll take it :) I forwarded
this on to suby-ruby. Hope that's okay by you.

Thanks,
T.

On Saturday 30 October 2004 05:58 pm, Brian Mitchell wrote:
| The basic background was begun by someone asking a question about
| boolean expressions and _why_ Ruby treats expression results nil and
| false as a *false* path in a boolean conditional. Some just explained
| that nil is like empty so it is nice to treat is like false.
| Someone then pointed out how one could use the methods false? and
| true? on the object to test its value. This immediately reminded me of
| smalltalk conditional design and possible ways one could implement
| something similar in Ruby. For example, here are smalltalk
| conditionals implemented in Ruby:
|
| module Kernel
| def _if(exp, &block)
| else_block = callcc { |cont|
| @condition = cont
|
| [snip]
 
R

Robert Klemme

I have some questions and remarks which you will find below.

Brian Mitchell said:
The basic background was begun by someone asking a question about
boolean expressions and _why_ Ruby treats expression results nil and
false as a *false* path in a boolean conditional. Some just explained
that nil is like empty so it is nice to treat is like false.
Someone then pointed out how one could use the methods false? and
true? on the object to test its value. This immediately reminded me of
smalltalk conditional design and possible ways one could implement
something similar in Ruby. For example, here are smalltalk
conditionals implemented in Ruby:

module Kernel
def _if(exp, &block)
else_block = callcc { |cont|
@condition = cont
return exp.true? &block
}
@condition = nil
exp.false? &else_block
end

Using instance variable @condition is likely to break in multithreaded
applications. Also, I don't understand why you use a continuation here.
Why is that?

Now this is just for if and unless. case and other conditions
could be made also but this is enough to give a basic idea (and save
me time). I will explain why this is a good thing. As I explained how
this whole thread started, there was a question as to why nil and
false were treated as "boolean false". Here we use duck typing (which
is right along side most ruby design) rather than just say that these
values to this.

I'd say you use polymorphism instead of duck typing because you rely on
different behavior of certain methods. At least that's the important
mechanism in this context IMHO.
This also allows change in behavior and more false
types to be added to ruby. This will help the with implementation of
DSLs using ruby.

Hmm... I'm not sure whether I understand this: where exactly does it help
to have more "false" values when implementing DSL's?
The implementation of conditionals as methods brings
up another pro. conditions can be hooked, changed, and treated like
any other ruby internal. This has been usually noted to be a good
thing but a good use is hard to come up with... just as the argument
goes with callcc ;) .

I'm not sure whether I like the idea to be able to mess with such a
fundamental thing as boolean values. Currently I can't see what we would
gain. OTOH, Ruby allows us to modify a lot internal behavior.
The other good note is the use of blocks. While
blocks now must be called (slower? I haven't bench marked yet), the
block can be passed around now or even come from a different binding.

Note that this might interfere with changed block semantics in R2 and could
break compatibility more than you'd like. Currently "if then" does not
introduce a new scope, this is legal and prints "bar":

if true
foo = "bar"
end
puts foo

the adoption of blocks is
a large jump and would probably require some syntax sugar to use
regularly. It also break compatibility with current code. Which might
not be as great of a thing.

Could even be a showstopper...

<snip/>

Currently I'm not convinced that we do indeed gain as much as to justify the
cost of change. Personally I'm completely happy with booleans as they are
in Ruby today. The syntactic differences aren't really that big, are they?
And I'm really not certain whether we'll gain something by having the option
to change interpretation of boolean truth values. And *if* you need that,
you can do it today:

class Object
def to_b() self end
end

class SpecialBoolHandling
def to_b ... end
end

if x.to_b
...
else
..
end

I believe I've read a comment of Matz about this (implicitely calling to_b
or somethig similar) where he mentioned performance as the reason why not to
do it, bI'm not 100% sure.

Nevertheless interesting thoughts.

Kind regards

robert
 
B

Brian Mitchell

I have some questions and remarks which you will find below.




Using instance variable @condition is likely to break in multithreaded
applications. Also, I don't understand why you use a continuation here.
Why is that?

I am simple avoiding using method chaining. Note that I put this
together in a few minutes also ;) . And yes I do not plan continued
use of instance variables in this case. I will probably look at a
clean ruby only way and possibly a Ruby patch (for benchmarking).
I'd say you use polymorphism instead of duck typing because you rely on
different behavior of certain methods. At least that's the important
mechanism in this context IMHO.

I say duck typing out of slight mistake (you are right there). I made
this word choice because that is how boolean *type* can be deduced.
Not Ruby *type*. Its just a matter of how you look at things.
Hmm... I'm not sure whether I understand this: where exactly does it help
to have more "false" values when implementing DSL's?

If I were to implement a simulation program it is sometimes more
beneficial to say something domain specific. And as far as behavior
goes it falls right in. "false" itself means nothing but that. In DSLs
you purposely tailor everything possible to fit your application's
domain. Its not a huge deal but I think Ruby has become flexible
enough to do things like this (HTML builders are a good example -- see
the old entries on onestepback.org for how a loose syntax and flexible
keywords make document building very easy).
I'm not sure whether I like the idea to be able to mess with such a
fundamental thing as boolean values. Currently I can't see what we would
gain. OTOH, Ruby allows us to modify a lot internal behavior.

Its for peace of mind. Actually, its for control of more things and
making more things fundamental rather than just-another-feature. If
you examined Ruby as a set of axioms which the language was built off
of before and after this you would notice that the base mechanics of
the language have become simpler while retaining most (or more?)
capability. It may also have further ramifications in future things we
may want to change but needed something like this to get to that
change to work well. (If that made any sense)? I see that as we work
on this project pros and cons will become more apparent. We are on
step one or many. You are right though. If it were __just__ to change
behavior of booleans then it may not be worthy of an RCR at all.
Note that this might interfere with changed block semantics in R2 and could
break compatibility more than you'd like. Currently "if then" does not
introduce a new scope, this is legal and prints "bar":

if true
foo = "bar"
end
puts foo



Could even be a showstopper...

Yes, but part of the idea is, IF we can present this as a positive
change even in the face of incompatibilities then it would be worthy
of an RCR. Ruby 2.0 will change a lot. The question is, "Is it worth
the break in functionality or do we even need the functionality?"

I am currently tyring to think of ways to get it to work w/o drawbacks
of 2.0 block semantics.
<snip/>

Currently I'm not convinced that we do indeed gain as much as to justify the
cost of change. Personally I'm completely happy with booleans as they are
in Ruby today. The syntactic differences aren't really that big, are they?
And I'm really not certain whether we'll gain something by having the option
to change interpretation of boolean truth values. And *if* you need that,
you can do it today:

class Object
def to_b() self end
end

class SpecialBoolHandling
def to_b ... end
end

if x.to_b
...
else
..
end

I believe I've read a comment of Matz about this (implicitely calling to_b
or somethig similar) where he mentioned performance as the reason why not to
do it, bI'm not 100% sure.

Nevertheless interesting thoughts.

I appreciate the feed back. Thanks and I will work on this either way.
It is a good educational experience.

Brian Mitchell.
 
F

Florian Gross

Robert said:
Hmm... I'm not sure whether I understand this: where exactly does it
help to have more "false" values when implementing DSL's?

It's quite useful for proxies. For example a WeakRef to false will not
behave exactly like false. This is a bigger problem when you're trying
to do some more exotic proxies. (This is needed for Junctions like they
exist in Perl 6.)
 
C

Christoph

Robert Klemme schrieb:
....
class Object
def to_b() self end
end

class SpecialBoolHandling
def to_b ... end
end

if x.to_b
...
else
..
end

I believe I've read a comment of Matz about this (implicitely calling
to_b or somethig similar) where he mentioned performance as the reason
why not to do it, bI'm not 100% sure.

I once proposed a "truth" flag scheme akin to Ruby's
"tainted" and "frozen" flags schemes which can implemented
with an (depending on taste) acceptable performance penalty.
I believe that the implementation ([ruby-core:279] and
[ruby-core:294]) is still valid.

I'd also choose this truth flags scheme over boolean
conversion methods any day, irrespective of performance
issues.


/Christoph
 
B

Brian Mitchell

Robert Klemme schrieb:
....

class Object
def to_b() self end
end

class SpecialBoolHandling
def to_b ... end
end

if x.to_b
...
else
..
end

I believe I've read a comment of Matz about this (implicitely calling
to_b or somethig similar) where he mentioned performance as the reason
why not to do it, bI'm not 100% sure.

I once proposed a "truth" flag scheme akin to Ruby's
"tainted" and "frozen" flags schemes which can implemented
with an (depending on taste) acceptable performance penalty.
I believe that the implementation ([ruby-core:279] and
[ruby-core:294]) is still valid.

I'd also choose this truth flags scheme over boolean
conversion methods any day, irrespective of performance
issues.

Just remember that we are NOT converting to boolean. In fact
conversion to boolean is totally unneeded by this model. to_b was
simple but that would require the creation of a VALUE for return. This
does not. In fast it just calls a method that will yield relative to
its "boolean value". I am currently looking at the 1.9 cvs code trying
to decipher how they go about things now and what changes would need
to be made at C level. Since this is a possible change for 2.0 not
current revisions I will need to at least implement it in cvs head.
The reason this is probably going to be done in C is because a large
part of the job will be the parser's and other stuff that may be found
through out ruby code. It is not something easy to do well in pure
Ruby and maintain speed and safety of the code. In order to make this
a possible RCR we must look at a real implementation because this is
such a fundamental component of Ruby. The draw back of course is that
a patch will be required which is a more inconvenient than a pure ruby
implementation or a ruby extension.

A reason not to use a truth flag is that boolean value may involve
call-time evaluation of truthfulness. This removes the perceived lazy
evaluation. This would normally create a large performance hit but, we
can implement faster code in Object by default and then allow
overrides that may be slower (if in ruby). A way to use it: internal
representation of a non-overridden object might use a truth flag as
that fast implementation.

The unfortunate thing about most everything I just said is that I am
still learning the internals of Ruby (beyond ruby.h ;) ). This means I
may not understand the C implementation correctly. Either way, it is
something I am looking at right now. If any of you guys have pointers
they are welcome. Also, I am on #ruby-lang as binary42 a large portion
of the day and evening (Mountain Time) if you would like a more
interactive conversation about this.

Thanks for your input! I will look at the posts in the morning.

:)
Brian Mitchell.
 
A

Austin Ziegler

I'm really not trying to be obtuse here, but I still don't get why
this is useful -- especially so useful that it would need to become
part of the Ruby core language. For DSLs, this sort of behaviour can
become part of the standard 'require' for that language (e.g., require
'truthDSL').

I honestly do not see the value of doing.

foo.ifTrue { bar; baz }

as opposed to doing:

if foo
bar
baz
end

IME, the only time that the "if foo" construct tends to give people
problems is when they come from C-like languages where 0 is false
(e.g., foo = 0; if foo ...).

-austin
 
R

Robert Klemme

Florian Gross said:
It's quite useful for proxies. For example a WeakRef to false will not
behave exactly like false. This is a bigger problem when you're trying to
do some more exotic proxies. (This is needed for Junctions like they exist
in Perl 6.)

Good point! Thx!

robert
 
R

Robert Klemme

I am simple avoiding using method chaining. Note that I put this
together in a few minutes also ;) .

That's funny: I'd *never* cook something up in a few minutes with
continuations. :) Nice to see how everybody has different things they try
to avoid.
I say duck typing out of slight mistake (you are right there). I made
this word choice because that is how boolean *type* can be deduced.
Not Ruby *type*. Its just a matter of how you look at things.

As often.
Its for peace of mind.

Well, *your* peace of mind. Others might get restless and... :)
Actually, its for control of more things and
making more things fundamental rather than just-another-feature. If
you examined Ruby as a set of axioms which the language was built off
of before and after this you would notice that the base mechanics of
the language have become simpler while retaining most (or more?)
capability. It may also have further ramifications in future things we
may want to change but needed something like this to get to that
change to work well. (If that made any sense)? I see that as we work
on this project pros and cons will become more apparent.

Yeah, probably. I'm open to convincing arguments / proofs of concept.
Yes, but part of the idea is, IF we can present this as a positive
change even in the face of incompatibilities then it would be worthy
of an RCR.
Definitely.

Ruby 2.0 will change a lot. The question is, "Is it worth
the break in functionality or do we even need the functionality?"
Exactly.

I am currently tyring to think of ways to get it to work w/o drawbacks
of 2.0 block semantics.

I never remember the exact changes although I do remember that there *will*
be changes. The discussion was just a tad too lengthy for my small brain.
:)
I appreciate the feed back. Thanks and I will work on this either way.
It is a good educational experience.

I'm glad I could help and you didn't feel offended. Whatever the outcome,
you - and probably we all - will have gained new insights and learned
something.

This one just stroke me: if syntax could be further relaxed without
introducing ambiguities then maybe we'd be able to write something like this
without other means:

expr.ifDo { puts "true" } :elseDo { puts "nah" }

Relaxations we'd need for this

(i) make argument separators (",") optional

(ii) make "lambda" optional when curly braces contain code (as opposed to
"x=>y" hash declarations.

Equiv. current syntax:
expr.ifDo lambda { puts "true" }, :elseDo, lambda { puts "nah" }

Alternatively

(i') make method invocation dot (".") optional

(ii) (as above)

expr.ifDo { puts "true" } elseDo { puts "nah" }

Equivalent of current impl would be

expr.ifDo { puts "true" }.elseDo { puts "nah" }

I don't have enough insight at the moment to judge whether those relaxations
possible at all but I guess there'll be too many ambiguities...

Kind regards

robert
 
B

Brian Mitchell


The main problem is that internally the entire conditional (i.e. if
else or case when...) need to be treated as a single expression.
Remember this is targeted to be both a change to evaluation of Ruby
and its parser. The RCR you pointed to above could use some
clarification. I don't know about you but it made ruby look a little
too messy of confusing. I am open to suggestions about syntax and if
any of you have thoughts please post them. I was , for now, looking at
taking current conditionals and converting their parts to block that
would be called. This is a problem with 2.0 block local vars. Any
thoughts on that would also be great :) . Last but not least. If a
conditional is passed a block it will call the block and not be
expected to have anything else. There may be a better way for us to
represent these conditionally executed blocks. For local blocks we
could speed up execution by in lining them and paying attention to
block locality before and after. This gets rid of a few calls.

Then there is still the possibility that we do not use blocks at all.
I am against this as I don't mind a block introducing new scope but
there may be other thoughts here too. The new scope is found with just
about everything else in Ruby so it makes for consistency (i.e. def
creates a new scope -- and hopefully will return something usefully in
the future).

Brian Mitchell.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,737
Latest member
Georgeengab

Latest Threads

Top