Perl is worse!

Martijn Faassen m.faassen at vet.uu.nl
Fri Jul 28 16:11:42 EDT 2000


Steve Lamb <grey at despair.rpglink.com> wrote:
> On 27 Jul 2000 20:24:38 GMT, Martijn Faassen <m.faassen at vet.uu.nl> wrote:
>>In the end if you want to add your data together, you need to know what
>>the result will be. If you want to substract your data, you also need
>>to know. 

>     That all depends.  In python, yes, you need to know.  Why?  Because 1 +
> "foo" doesn't work.  Sure, in perl you get 1, but you don't get blown out of
> the water because of it.

You mean your interpreter does not stop, it continues, even though it got
data it couldn't really make sense of. I mean, there's no reason 
'foo' would be 0, '1foo' is 1, and 'foo1' is 0 again. Right?

I don't see how you dont 'get blown out of the water'; it seems worse --
you get input that is not a number, and Perl just happily continues
with your calculations. So you have to make sure your input is a number
*anyway* if you want to calculate things, so why not complain in the
first place when you run into something that doesn't seem to make sense.

Anyway, I'm sure we won't be able to budge each other from our
position on this. :)

>  OTOH, in Perl, if you meant to do a string addition
> there is a different.syntax.for.that.  Because of this you know that "foo"
> doesn't equal anything, IE, 0.

0 *is* a legitimate number, you know. I know there's a different syntax for
concatenating strings in Perl.

>>What if your data is "foo" and you add them together? What if
>>the user gave some input and you don't *know* what the user entered?

>     Then that would be a user error, wouldn't it?  You can check for those
> things.  If you're relying upon user input you don't need type checking to do
> it.

Yes, but in Python at least the program doesn't try to do things when you
forgot to check the user input properly. And that increases the chances
you'll find the bug in the first place.
  

> python:
> try:
>   b = b + 1
> except TypeError:
>   print "Numbers, stupid, not letters."

> perl:
> unless (int($a)){
>   print("Numbers, stupid, not letters.\n")
> }
> $a++

This code doesn't seem to be equivalent, though. In Perl if 'a' is a 
string-that-when-striped-of-whitespace-does-not-start-with-a-number you'd
get 1 (I think?), and in Python 'b' remains unchanged.

And if I forgot the 'unless' in Perl (though it doesn't matter in this 
code fragment :), I'd get 1, and in Python it'd get a bug. So I know I
need to change my code to do a check.

>     The difference comes when you do something like this:

> (/^(\d{1,2})[Dd](\d{1,3})$/ || /^(\d{1,2})[Dd](\d{1,3})([\+\-])(\d{1,2})$/) 

I don't know what you mean here. I never do anything like that, I'm
happy to say. :)

>     Perl or Python, doesn't matter, you should be able to read it.

I'm not able to read it.

> Simple regular expression to take die codes from RPGs (3D6+3, for example)
> and break it down into the component parts regardless of their being a
> modifier to the actual dice rolled.  What does this sample show?  Same
> thing that the previous one did.

I'd probably solve that in a more verbose way myself. 
 
[snip regex example]

So you're saying, since I know those particular groups are numbers anyway
by necessity, it makes no sense to require explicit conversion (with
int() in Python) to numbers of those things. I just want to use them
as numbers right away. Did I understand that correctly?

The problems arise if you made either a mistake in your regular expression
so what's being returned happens not to be a number (but you expected it
to be), or when the regex is fine but you confused which variable contained
what result.

As your program proceeds and for instance throws the dice, it'll try to use
those variables as numbers, but since they're other strings, you'll eventually
get strange answers (such as 0). And then presumably you'd have to search
what part of your program is wrong. In Python you'd know pretty quickly 
that you got strings instead of numbers, and so you know what to fix.

>>I think in your case you usually need to remember what type your data
>>is *anyway*, and what's worse you need to know what will happen to your
>>data for various cases:

>     Assumption: that data checking cannot be performed by the programmer.  

That wasn't the assumption. I said the exact opposite, you need to 
remember what type your data is *anyway*. In order to do that you need
to check the data if it is input.

>     False assumption, I've given two examples where the data will be what I
> want because I have made the appropriate checks.

That's the point; you need to do that anyway. It doesn't take you that much
effort to do the right kind of conversion (to an integer) afterward; as you're
checking anyway. And it catches the bugs more quickly, I'd think?

>  One comes right from the
> program that I am porting to Python to learn Python.  In fact, that program
> relies on user input every step of the way yet I've not had any problems
> performing the operations I want to perform on the data.

>>This may become quite unobvious in various cases, and may happen by
>>accident, right?

>     Doesn't mean the programming language should barf on it.

But if the language barfs and tells me where it barfed and why, I can
more easily fix the bug, right?

>  I am of the
> opinion that a /lot/ more operations are performed where a lack of type
> checking is a virtue than a bane.  I really dislike having to explicitely
> declare data as a certain type just for an operation,
> especially after having programmed in a language where such is not
> required and especially if I have
> control of the dataflow throughout the entire course of its life.  Not only
> does this cause problems for simple operations like extracting numbers with
> regex to be manipulated with mathematics, but also internal data structures
> where the author is defining things on the fly and needs to perform both
> string and numerical operations on, say, the keys of a hash/directory.

>>Though I understand the case for genericity, I don't really see it here.

>     I see it the other way.  Though I understand the case for typing, I rarely
> see a case where it is actually useful and, in fact, many cases where it is a
> bane.  I'd much rather have to deal with the few cases when they arise than
> the many cases when they arise.  For example, mathematics which require
> roll-over at a certain number (unsigned int, for example).  This is more so in
> a language which does have certain cases when types can change (int versus
> float, for example) and the initial type is implicitly stated but requires an
> explicit declaration to change that implicit type.  

>     Either have all types be immutable regardless of context unless explicitly
> stated or have all types mutable as best as possible based on context with the
> option of explicit typing.  The half-and-half is just as confusing as a lot of
> what a lot of Python pundants dislike about Perl.  Just like lists and
> directories (does that include tuples?  I forget) being referenced but the
> "single" data types (integer, character, I believe string, not sure) being
> copied.

This is a common misconception. *everything* in Python is always, always,
references. It is just that certain data types (integers, string, tuples) are 
immutable; you cannot change them. So the semantics are the same as 
copy semantics.

>  That is no less magical and confusing than,

It may be confusing, but it's definitely not magical. The rule is one of the
simplest I've seen, actually. :)
 
> say, using an implicit $_
> in a foreach loop or constructing a data loop from @lines into %lines using
> $line to do it.  $lines{$line} = $line[$line] is always fun to bedazzle
> newbies, isn't it?  No different than implicit typing, IMHO, except here I do
> have cues as to what is a scalar, a hash and an array.

>>But what if that makes no sense? What if I entered 'foo' in the config
>>file where I needed to enter a number? Won't you get a weird bug now?

>     OK, go ahead and do it in Python, what do you get?  An exception.

>     Do it in Perl, what do you get?  The /possibility/ of odd behavior.

Isn't that worse than the program giving up? Especially when I'm trying
to debug things. Obviously the right thing in both languages is to *check*,
and if you're checking anyway I don't see what the disaster is of doing
int() somewhere in your checking function. Especially because it points it
out to you if you forget.

[snip]
>     And that is the whole point.  In both cases you have to do error checking.
> The difference is that with the implicit types enforced it gets in the way.
> I'd much rather the language assume I am smart enough do to my own checking
> and assured that the data is valid than to assume I'm too stupid to do it and
> crap the program out when something doesn't match what it /thinks/ is invalid
> even though I have taken pains to ensure it is.

But you haven't; you haven't told the system it's a number yet. :) That's
the _only_ extra step you need to take. The system is too stupid to figure
it out very well on its own anyway, so why not tell the system explicitly?

Anyway, this whole debate is silly as philosophically Python and Perl are
very close in depending on the intelligence of the programmer. Python assumes
a bit less intelligence, I'm glad to say. I'm not intelligent enough to
do regular expressions a lot, for instance. :) But compared to language
that does static type checking the difference is so minimal.

>>This script won't do anything.

>     Thank you for so stating the obvious, especially when I provided the
> interactive mode right after it.

Interactive mode (I think it explains in the tutorial) gives you the output
of expressions as a result always. 

>>I imagine the reason Python doesn't complain here is that it can treat
>>expressions as statements, and that sometimes this makes sense, such
>>as when there's a function call:

>     The problem is, outside the context of interactive mode what good does it
> do?

I just gave you the example of the function call, right? That's what good
it can do:

foo()

is an expression, just like 1 + 1 is. foo() definitely makes sense, right?

>  Nothing that I can think if.  Have you made calls for Python to check for
> such things just as it checks implicit types? If not, why not?  In both cases
> the assumption is that the programmer does dumb things and should be babysat.

Okay, I rarely run into this type of thing, so I haven't made calls for it.
I think it's fairly difficult to add this, as you never know whether the
user didn't mean to place an expression there that does a function
call, which does make sense. So you'd need to check whether your expression
is a function call and supress the complaint if that happened. Perhaps
that would be interesting if it doesn't break anything else.

Anyway, perhaps you're right and you don't run into this type of problem
very often as you take care to do checking and your checking code doesn't
have too many bugs in it. I myself like this particular safety net,
and also the absense of lots of ambiguity in the case of adding something
together when the something involved is not a number. But I'm sure I could
learn to live with it.

>>I'm not claiming we want to be strict about things at all times. If
>>I thought that I wouldn't be using Python. I was claiming that 'guessing'
>>what a line of code is supposed to mean can be bad. I mean, really:

>>"foo1" + 1 # 1
>>"1foo" + 1 # 2
>>"1" + 1 # 2
>>" 1" + 1 # 2
>>"\n1" + 1 # 2
>>"1*2" + 1 # 2

>>Are you sure this never gives you any odd output?

>     Positive.  Because you have made the base assumption that the only way to
> check data is for the language to do it instead of the programmer.

No, my entire point was that you need to know what your data is *anyway*.
You need to know that something is an integer anyway, so why not simply
tell the computer what's going on? I'm *not* saying the language should
do the checking. It can't; even though Perl tries.

> I know what data I am passing, I don't need type checking in an implicit
> system to provide annoyances all over the place for the off chance that
> I /might/ goof up.  Quite a Libertarian view, innit?  In a sentence.

>     LET ME MAKE MISTAKES!

And when you make them, let the system complain loudly! I just don't
see the annoyances in this case. I do see the annoyances of static type
checking. If you want the system to shut up about your mistakes,
you can always do this in Python:

try:
   a = a + 1
except:
   pass

But at least you're saying explicitly here that the system is shutting up
about your mistakes.

>>No, giving up in the face of ambiguity versus guessing. Ambiguity tends
>>to arise when I'm doing something wrong; if I did it right it wouldn't
>>be ambiguous code. Besides, I wouldn't want to read such code even
>>if it did the right thing; confusing.

>     What ambiguity?  Data is data.

"foo" is not 0 to most people. "1foo" is not 1 to most people. What's more,
"one" *is* 1 to most people and "2*2" means 4 to most people. Lots of
ambiguity here. Where does the computer stop in trying to make sense of it?
The computer is stupid after all.

> I don't find it confusing when you unlearn
> the concept of types.  I mean, how many people out there who program in other
> languages consider Python confusing because whitespace is used for block
> declaration instead of {} or BEGIN/END or whatever other block declaration is
> out there.  

>     If your mind can wrap itself around the concept of a lack of braces for
> block declaration then there should be no problem unlearning the concept of
> data "types".  

The problem is that there is no unambiguous way to do numerical addition of
strings. You use the concept of numbers versus non numbers in Perl too; you
*have* to check for this in the face of unpredictable input. Since you're
using this concept, why not explicitly tell the machine about it?

The answer is; you're not because of convenience. And that's fine; Python
does that a lot in other places (all over its OO system, for instance). 
It's just that I'm not convinced that treating numbers and strings as the
same thing *is* convenient.

>>In Python there are tons of ways to do it too. :)

>     So long as you tell it explicitly what you want even though its ambuigity
> on the implicit typing is what cause the problem in the first place?

No, you probably don't realize how limited this kind of implicit typing
in Python really is. It doesn't exist in it's OO system!

class Cow:
    def make_sound(self):
        print "moo!"

class Chicken:
    def make_sound(self):
        print "cluck!"

mycow = Cow(); mychicken = Chicken()

a = mycow # or: a = mychicken
a.make_sound()

This works. I didn't need to tell the system that 'a' is a cow or a chicken
or an animal that can make sounds. The Cow and Chicken classes have nothing
to do with each other; no inheritance, no explicit interface, nothing.
The only thing they have in common is a make_sound() method.

> I'm
> sorry, but I do not consider data confusing unless it is typed.  I see 1 and I
> am confused why I can't add it to 2.  I don't /care/ if it is a char, string,
> integer or floating.  It is 1 to me.

Sure, but what about "one" and "foo"? I don't see "foo" and think I can add it.

>>Because I wasn't arguing for 'strict checking'; I was arguing not checking
>>until the *last moment*, and if that makes no sense, give up.
>>(unless you catch the exception, of course)

>     Which makes no sense without declarations up front since you are allowing
> ambiguity, to use your term, from the onset.

>     Python: You need to know what data type it spews out for each operation.

>     Perl: You need to know what the data contains.

>     Either way, you got ambiguity.  

Sure, but Python gives up in the face of ambiguity. It doesn't try to
continue and infect the rest of the system with more ambiguity.
(though again, this debate is very limited! Look at Python's OO system)

>     As for catching the exception I'd much rather check the data first, thank
> you.

You can do that in Python too. I tend to prefer that as well.

>>Not in OO code:

>     Funny, we have implicit typing in OO code that causes problems later in
> the code.  So yes in OO code.

What implicit typing? I don't understand what you mean at all.

>    
>>That works, as the check at the last moment could figure it out; there was
>>such a method, so it executes it. Now if 'doit()' was absent and the system
>>started guessing, we'd be in trouble, right?

>     This is not, however, data.  Methods are functions, if there is nothing to
> call, there is nothing to call.  1 is data, be it integer, float, char, string
> or any other name you want to give it.

1 is data with operations. Just like an object is data with operations.
In Python, 1 is an object. See the end of my post.

>>So I don't need to know about what happens when I do "1foo" + 1?
>>Or "foo1" + 1? Or "foo1" - 1? Presumably I'd need to check my user
>>input *anyway*, right?

>     Yup, that is exactly what you should be doing and if you were doing it
> properly then you'd not have to rely upon the language to do it for you.  

Yes, but if I'm not doing it properly at least the language will complain
so I get a broad hint I do need to do it properly. :)

>     As I said, you're trading knowing what a certaion operation does with
> certain data versus having to know what type of data a certaion operation
> spits out at you.  

Yes, but they're not equivalent. Imagine we have 5 binary operators and
5 types of data. In Perl you can use any type of data with those operators,
and Perl will figure it out. That means I have 15 possible combinations
that I need to know about (whether they make sense or not, such as
adding together non-integer data). Now let's imagine a simple Python case,
where you can use all 5 operators for each type, but you can't mix
types. That means I need to know what Python does with only 5 combinations;
the rest of the cases, Python will complain.
 
>>> Not that hard to remember.  Much easier, in fact, that having to
>>> remember to declare everything from the onset or declare it each time you
>>> need to use it in a particular context much less having to put in checks
>>> in case you're trying to declare something into something which it cannot
>>> be morphed into.

>>But in Python you don't need to declare at all. You just need to explicitly
>>say sometimes you want something changed into something else. If you
>>want an integer, change your string into an integer. Do it early on if
>>you don't want to do it everywhere and work with integers from then on.

>     Thank you for ignoring what I said.

My apologies, what did I ignore? I didn't get the impression you read
what I was saying all the time either. :)

>  First off, in Perl I don't have to
> declare at all as a rule and only need to worry about the exceptions.  In
> Python it is the reverse, I need to declare as a rule except for the
> exceptions.

Huh? Where in Python did you find your declarations? I'm confused.

>  Furthermore if I need to change my string into an integer I need
> first check to make sure it is a string /THEN/ I can convert it to an integer.
[snip]

Well, because it doesn't really make a lot of sense if you want to convert
something to an integer when Python can't figure it out. If you're so
sure of yourself, you can always do:

try:
   a = int(b)
except ValueError:
   a = 0

>     This causes problems when using a regex that has an or that can match
> different numbers of groups based on which or hits because any conversions on
> any group that didn't hit now needs to be encapsulated in either a
> try/except|finally block or an if.

All right; this just doesn't happen very often in my code. Generally because
I can expect a certain regularity in my input. If my input is less regular
than I expected, I get exceptions. That gives me a nice indicator that my
expectations were off.

>  You might like littering your code with
> try/except and ifs which are only needed because the type checking isn't
> allowing you to do things you should be able to do but personally I don't.  I
> find it foolish that I need to check a variable to make sure it exists so I
> can type it to integer when I know an integer is going into it in the first
> place!  In short a regex which returns a number turns into...

In that case you don't need to do any checking, obviously. You have a 
regularity in your data so you just do something like:

a = int(a)

[snip example]

Though I may be missing something about regular expressions here and
I'm goofing up?
 
[snip]
>>The funny thing is that in Python this happens, and I'd agree with you,
>>but it tends to happen more often for class instances

>     Except I don't consider instances data.  They are structure or commands,
> not data.

I consider both integers and strings and instances as *objects*. 
See the end of my post.

>>We aren't so far apart in that sense; we just disagree about how many
>>operations the basic datatypes should support. I personally never had
>>trouble with having to call int() excessively, for instance. If you
>>find you're doing that a lot in your code, something may be odd about the
>>design of your code.

>     I seriously doubt that.  All I'm doing is a basic regex match and
> assignment of the matched data.  I haven't gotten into making complex data
> structures yet in Python but I have a feeling the typing will get in the way
> since a lot of the times that I manipulate keys in a hash/directory I move in
> and out of numerical and string processing to get the job done.

Hm, why would you do that? Depends on the structure of your keys. You can
use numeric keys in your Python dictionary just fine.

>>Note also that in your case, the only thing you're complaining about is
>>that Python treats numbers and strings differently; Python doesn't care much
>>whether your number is an integer or a float or a long integer, for instance.
>>It doesn't distinguish between characters and strings either. The only
>>addition to your type pantheon would seem to be the string/number
>>dichotomy and the list/tuple dichotomy, nothing else. :)

>     Hmmm, but you forget, lists, tuples and hashes are not data, they are
> structure.

They're all objects. :)

>  Hashes and arrays are not data, they are structure.  Classes that
> hold only data are not data, they are structure.  Classes, in fact, to me, are
> just structures that hold either functions (renamed to methods) or data.

>     You have different types of data.  I have, conceptually, data, commands
> and structure.  What /form/ those take and what semantics assigned to them
> vary from language to language but the concepts to me are quite clear.

I have objects (and some functions). Not all objects support all operations.

[snip]
>     Lists, arrays, hashed, directories, classes, structs, etc, etc, etc are
> all organizational in nature.

In Python, they're objects just like there rest of them.

>  OO has the distinction of having the ability to
> apply those organizing constructs to commands and not just data.

>     Operations, functions, prodecures, methods.  All manipulate the data.  The
> do something.  In OO they can be organized.

But an integer can be seen as an object with methods as well. A string
too (Python 2.0 will actually allow you to use string methods). In fact,
I can make a new type of object in Python that behaves much like your
Perl scalars:

import string
import re

class Scalar:
    def __init__(self, data):
        self.data = magic(data)
        
    def __add__(self, other):
        return Scalar(self.data + other)

    def __radd__(self, other):
        return Scalar(self.data + other)
    
    def __sub__(self, other):
        return Scalar(self.data - other)

    def __rsub__(self, other):
        return Scalar(other - self.data)
    
    def __mul__(self, other):
        return Scalar(self.data * other)

    def __rmul__(self, other):
        return Scalar(other * self.data)
    
    def __div__(self, other):
        return Scalar(self.data / other)

    def __rdiv__(self, other):
        return Scalar(other / self.data)
    
    # (not all possible operations implemented)
    
    def __coerce__(self, other): 
        return self, magic(other)

    def __str__(self):
        return str(self.data)
    
def magic(d):
    """Magically convert d to an integer, whatever it may be.
    """
    if type(d) == type(1):
        return d
    d = string.strip(str(d))
    matches = re.match("(\d+)", d)
    if matches:
        d = int(matches.group(1))
    else:
        d = 0
    return d

# now let's use it:
print Scalar("1") + "1" # 2
print Scalar("2") - "1" # 1
print Scalar(3) * "4" # 12
print Scalar("12") + 2  # 14

i.e. any expression with a scalar object involved will try to treat any
operant as a number.

Of course this particular implementation has various disadvantages:

  * it's incomplete

  * it's likely buggy

  * it's slow (could be solved by a C implementation though)

  * no operators for string concatenation exist by default in Python;
    we'll either have to coopt an existing operator or provide functions
    to do concatenation.

I even used a regular expression. :)

There's-more-than-one-way-to-do-it--ly yours,

Martijn
-- 
History of the 20th Century: WW1, WW2, WW3?
No, WWW -- Could we be going in the right direction?



More information about the Python-list mailing list