User: Password:
|
|
Subscribe / Log in / New account

Affects Perl too

Affects Perl too

Posted Mar 12, 2014 19:03 UTC (Wed) by dskoll (subscriber, #1630)
Parent article: A false midnight

I've seen many instances of:

# Ignore unless $var is defined and non-empty
if ($var) {
   # ... do something
}
which can fail if $var is the string "0". I religiously use constructs like if (defined($var)) or if ($var ne '') depending on what I mean.


(Log in to post comments)

Affects Perl too

Posted Mar 12, 2014 20:57 UTC (Wed) by Tara_Li (subscriber, #26706) [Link]

My sense is - unless it's a Boolean variable of some kind, or the Boolean result of comparisons & logical operations, it should be a full on syntax error to say "if variable" or even "if !novariable". 0 is not FALSE, -1 is not FALSE, the empty string or empty list is not FALSE.

Neat trick may be neat, but it doesn't really make things *better* or *clearer*.

Affects Perl too

Posted Mar 12, 2014 22:34 UTC (Wed) by gerdesj (subscriber, #5446) [Link]

You and me both. I am not a particularly skilled programmer but I have to write scripts from time to time in quite a few languages.

To avoid corner cases like that or effects that are well known to "experts" but not me I always spell out tests as simply and unambiguously as possible with the emphasis on unambiguously. I can generate bugs at quite a rate myself without trying to be clever.

Now, times are especially weird as a data type due to the myriad rules and the modulo effect and to even contemplate trying to squeeze in a notion of one time being true whilst another false looks bloody stupid to me.

It looks to me like the clever buggers who test the existence of a var with if $var are just plain lazy. If it's a common construct then I think it's probably a really stupid one. I think that you are effectively wanting to test a variable's existence by comparing its value with true/false which are two different things.

Surely you should test for "is not null" (or whatever syntax is necessary in the language concerned) which implies existence or test the contents of the variable. Trying to do both at once clearly ends in tears eventually or ends up with strange discussions about date/times that might be true or false

Cheers
Jon

Affects Perl too

Posted Mar 12, 2014 23:06 UTC (Wed) by dchichkov (guest, #90878) [Link]

I would kindly suggest writing readable code and emphasizing readability.

Affects Perl too

Posted Mar 12, 2014 22:58 UTC (Wed) by bracher (subscriber, #4039) [Link]

well, object as true/false affects perl, but perl's datetime library doesn't treat UTC midnight as false. DateTime object with time set to UTC midnight returns true as most sane observers expect:

$ perl -e 'use DateTime; my $d = DateTime->now(time_zone => "UTC")->set(hour => 0, minute => 0, second => 0); print $d ? "true" : "false","\n";'
true

which is the whole point of the hoopla in python-land, that doing otherwise is non-sensical at best...

Affects Perl too

Posted Mar 12, 2014 23:26 UTC (Wed) by dskoll (subscriber, #1630) [Link]

DateTime object with time set to UTC midnight returns true as most sane observers expect:

That's 00:00:00.

The preceding sentence, of course, means "That's true."

I think you get my point. The whole notion of treating non-Boolean variables as "truthy" or "falsy" opens one up for a whole class of bugs.

Affects Perl too

Posted Mar 13, 2014 20:25 UTC (Thu) by jimparis (subscriber, #38647) [Link]

They both have quirks.
Perl considers the time "00:00:00" true, and the string "0" false.
Python considers the time "00:00:00" false, and the string "0" true.

Affects Perl too

Posted Mar 14, 2014 17:37 UTC (Fri) by jtc (guest, #6246) [Link]

"perl -e 'use DateTime; my $d = DateTime->now(time_zone => "UTC")->set(hour => 0, minute => 0, second => 0); print $d ? "true" : "false","\n";'"

The object you're testing, $d, is a DateTime object, while in the given python example, the object is being tested is a time object, so your example is not equivalent. (You might think $d->time would produce a Time object and thus be equivalent, but it just produces a string that evaluates to true.) DateTime in perl does not (apparently) use or expose a Time object, so there perhaps is no equivalent comparison. (False dates - as opposed to times - are another issue - but we might have a hard time defining what is a 0 date.)

However, if you evaulate a zero hour, minute, or second in perl you do get false, as this example shows:

#!/bin/perl
use Modern::Perl;
use DateTime;
use Data::Dumper;
sub execute {
my ($o, $msg) = @_;
say "$msg: ", Dumper($o);
say 'o: ', ($o? 'true': 'false');
say '=' x 66;
}

my $d = DateTime->new(time_zone => 'UTC', year => 0, month => 1, day => 1,
hour => 0, minute => 0, second => 0);
execute($d, 'year 0 ...'); # true
execute($d->time, 'time 0 ...'); # true
execute($d->second, 'second 0 ...'); # false
execute($d->minute, 'minute 0 ...'); # false
execute($d->hour, 'hour 0 ...'); # false
execute($d->hour + $d->minute + $d->second, 'h+m+s 0 ...'); # false

Affects Perl too

Posted Mar 14, 2014 18:13 UTC (Fri) by mbunkus (subscriber, #87248) [Link]

Simply because the three getter methods return integers and not objects. Of course an integer of 0 is false in Boolean contexts in Perl. The whole point of the article is that certain objects evaluate to false in Python while "if obj: …" is a common way of checking whether or not obj currently holds an object instance.

Therefore your comparison is a bit besides the point.

Affects Perl too

Posted Mar 14, 2014 22:44 UTC (Fri) by jtc (guest, #6246) [Link]

"Therefore your comparison is a bit besides the point."

The point of my post was simply to point out that bracher's example compared a DateTime in perl with a Time in python - two different classes with different semantics - and thus not a valid comparison. I wasn't addressing the article - at least directly.

To make this point only my first sentence was needed ("The object you're testing ... not equivalent"). The rest was a - perhaps unnecessary - elaboration of the types involved with respect to DateTimes and Times in python vs perl (and yes, hour, minute, and second in perl evaluate to integers) and how, when it comes to Boolean evaluation, this leads to complexity and possible confusion. I'll grant that this is off-topic from the article.

On the other hand, I think it might be relevant (to the article) to point out that such complexity and confusion that result from regarding some instances of zero-ness as 'false' and other instances as 'not false' can cause problems that lead to defects and that it's wise not to use this idiom, at least for large projects. (Obviously, this puts me on the side of those arguing for midnight not evaluating to false in python, although I go even further to say "don't allow such an idiom in the code") The best way to avoid the idiom is to not allow it in the language (as is the case in statically-typed languages like Java [though not the case in C or C++]), but that's probably not practical for perl or python (or ruby, where 0 is true), since much existing code, obviously, uses these idioms. In that case all you can do, I suppose, is follow a coding standard that outlaws such comparisons (and perhaps use a lint-like tool to find violations).

(I elaborated on this point a bit more in another post:
http://lwn.net/Articles/590778/ )

Affects Perl too

Posted Mar 15, 2014 8:30 UTC (Sat) by mbunkus (subscriber, #87248) [Link]

Almost each and every language has its own rules which values it considers false in Boolean contexts. They're varied too much to approach remembering them with logic. You just have to learn them by heart.

C: the value 0; C++: 0 / nullptr (C++11 and newer) or depending on the class if operator bool is overloaded; Perl: undef / () / 0 / "0" (but not "0E0" or other strings which evaluate to 0 if taken in a numeric context) or depending on the class if bool conversion is overloaded; Lisp: depending on the Lisp dialect in question, somethings nil and the empty list (), sometimes only nil, sometimes nil and the Boolean type false as in Clojure); Ruby: nil and the Boolean false…

Therefore I consider saying anything along the lines of »0 is always/never supposed to be false« to be the wrong approach.

I fully agree that Boolean conversion for instances of a time-based classes (no matter if it's just the time component or a date as well or even both of them) is harmful. Remembering special rules like this leads to mistakes, it always does. There are useful cases for this kind of overloading, but they're rare: e.g. a TriBool class representing True/False/Unknown.

Affects Perl too

Posted Mar 15, 2014 10:35 UTC (Sat) by peter-b (subscriber, #66996) [Link]

I found myself thinking about this last night -- I quite like languages where there is one and only one "false" value. Like Scheme:

> Of all the standard Scheme values, only #f counts as false in conditional expressions. Except for #f, all standard Scheme values, including #t, pairs, the empty list, symbols, numbers, strings, vectors, and procedures, count as true.

The reason I like this is that it removes all uncertainty about what, exactly, a conditional expression is testing on.

Affects Perl too

Posted Mar 15, 2014 15:03 UTC (Sat) by khim (subscriber, #9252) [Link]

My position is different: I'm Ok with multiple “false” values in statically-typed languages (if any single type can have exactly one “false” value) but to me it looks like crazy thing to do with dynamically typed languages!

I mean: if I see something like if (i/*int*/) … or if (p/*pointer*/) … in C I know that there can be exactly one value which will be interepreted as false in this particular place. That is: language has many possible false values, but each particular point in a program can employ just one of them! I dislike tricks like std::ios::operator bool for that reason (yes, it's clever and sometimes it looks cool, but it erodes concept of “false” value).

But with dynamically typed languages this justification flies right out of the window: you can pass around “real” “false” everywhere, why would you need surrogate ones? They will just complicate everything for no good reason!

Perl, python, php - they all adopted C concept of “zero of any type is “false”” without thinking when they had no need for that kludge! This is a pity, really, but I'm afraid we are stuck with this decision: such change when applied to popular types will just break way too many programs.

Affects Perl too

Posted Mar 16, 2014 5:43 UTC (Sun) by jtc (guest, #6246) [Link]

"Therefore I consider saying anything along the lines of »0 is always/never supposed to be false« to be the wrong approach."

I agree.

"I fully agree that Boolean conversion for instances of a time-based classes ... is harmful. ..."

We appear to be in 100% agreement. :-)

Affects Perl too

Posted Mar 13, 2014 10:05 UTC (Thu) by intgr (subscriber, #39733) [Link]

> which can fail if $var is the string "0".

Note that in Python, the string "0" is considered True (but integer 0 is False). It's consistent because in most contexts there is no implicit casting from str to int.

Affects Perl too

Posted Mar 14, 2014 19:21 UTC (Fri) by mathstuf (subscriber, #69389) [Link]

> most contexts there is no implicit casting from str to int.

What contexts do?

Anyways, while we're in Python's deeper, darker corners, some things I really dislike about Python:

bool('False') -> True (ast.parse_literal is needed) whereas int('1') -> 1
str.split() is approximately filter(len, split(' ')) (there is no way to pass non-None arguments to str.split and get the no-arg behavior)

Affects Perl too

Posted Mar 17, 2014 12:13 UTC (Mon) by intgr (subscriber, #39733) [Link]

> What contexts do?

Actually I can't think of any from the top of my head. I just said "most" because I suspect there are a few edge cases in the standard library; Python's typing isn't always as strict as people make it out to be. Implicit casts to str are quite common, for example.

> bool('False') -> True (ast.parse_literal is needed) whereas int('1') -> 1

Why do you think this should return False? It doesn't work this way for most other built-in types either, consider list('[1,2]'). The fact that it works for numeric types is a coincidence, because that's a useful way to represent numbers. It never was a goal for sometype(str(value_of_sometype)) return the original value again.

I think using bool(x) to get the truth value of x makes lots of sense, just like list(iterable) does.

> there is no way to pass non-None arguments to str.split and get the no-arg behavior

Agreed, that is inconsistent.

Affects Perl too

Posted Mar 17, 2014 13:13 UTC (Mon) by mathstuf (subscriber, #69389) [Link]

The only place I can think of for implicit str casts is Python2's print statement. The %s way warns at least IIRC.

Casts for compound types makes sense since the internal type to parse out is very ambiguous. I would have thought bool would follow int since they're both POD-ish. It bit me in a config parser (it was our pre-existing format, so the stdlib module wasn't going to work) where I passed a function to use to cast the value out from the parse. You could use int, but anything else needed a wrapper function.


Copyright © 2017, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds