13

As I understand it, implicit conversions can cause errors.

But that doesn't make sense -- shouldn't normal conversions also cause errors, then?

Why not have

len(100)

work by the language interpreting (or compiling) it as

len(str(100))

especially since that's the only way (I know of) for it to work. The language knows what the error is, why not fix it?

For this example I used Python, though I feel that for something this small it's basically universal.

  • 2
    perl -e 'print length(100);' prints 3. – user40980 May 26 '15 at 1:27
  • 2
    And thus the nature of the language and its type system. That is part of the design of python. – user40980 May 26 '15 at 2:39
  • 2
    It doesn't fix it, because it doesn't know your itnesion. maybe you wanted to do something completly different. like suing a loop but never did anything like programming before. so if it fixes it by it self the user doesn't know either he was wrong nor that the implemantation won't do what he expects. – Zaibis May 26 '15 at 9:58
  • 5
    @PieCrust How is Python supposed to convert? It is not true that all possible conversion would return the same result. – Bakuriu May 26 '15 at 11:55
  • 5
    @PieCrust: strings and arrays are iterables. why would str be the implicit way to convert an int into an iterable? how about range? or bin (hex, oct) or chr (or unichr)? All those return iterables, even if str seems the most obvious for you in this situation. – njzk2 May 26 '15 at 14:31
36

For what it's worth, len(str(100)), len(chr(100)) and len(hex(100)) are all different. str is not the only way to make it work, since there's more than one different conversion in Python from an integer to a string. One of them of course is the most common, but it doesn't necessarily go without saying that's the one you meant. An implicit conversion literally means, "it goes without saying".

One of the practical problems with implicit conversions is that it isn't always obvious which conversion will be applied where there are several possibilities, and this results in readers making errors interpreting the code because they fail to figure out the correct implicit conversion. Everyone always says that what they intended is the "obvious interpretation". It's obvious to them because it's what they meant. It might not be obvious to someone else.

This is why (most of the time) Python prefers explicit to implicit, it prefers not to risk it. The main case where Python does do type coercion is in arithmetic. It allows 1 + 1.0 because the alternative would be too annoying to live with, but it doesn't allow 1 + "1" because it thinks you should have to specify whether you mean int("1"), float("1"), ord("1"), str(1) + "1", or something else. It also doesn't allow (1,2,3) + [4,5,6], even though it could define rules to choose a result type, just like it defines rules to choose the result type of 1 + 1.0.

Other languages disagree and do have lots of implicit conversions. The more they include, the less obvious they become. Try memorising the rules from the C standard for "integer promotions" and "usual arithmetic conversions" before breakfast!

  • +1 For demonstrating how the starting assumption, that len can only work with one type, is fundamentally flawed. – KChaloux May 26 '15 at 12:43
  • 1
    @KChaloux: actually, in my example str, chr and hex do all return the same type! I was trying to think of a different type whose constructor can take just an int, but I haven't come up with anything yet. Ideal would be some container X where len(X(100)) == 100 ;-) numpy.zeros seems a bit obscure. – Steve Jessop May 26 '15 at 12:48
  • 2
    For examples of the possible problems see this: docs.google.com/document/d/… and destroyallsoftware.com/talks/wat – Jens Schauder May 27 '15 at 5:19
  • Implicit conversion (I think this is called coercion) can cause nasty stuff like having a == b, b == c, but a == c not being true. – bgusach May 28 '15 at 7:09
  • Excellent answer. Everything I wanted to say, only worded better! My only minor quibble is that C integer promotions are quite simple compared to memorizing JavaScript coercion rules, or wrapping your head around a C++ code base with careless use of implicit constructors. – GrandOpener Jun 7 '15 at 1:01
27

As I understand it, implicit conversions can cause errors.

You're missing a word: implicit conversions can cause runtime errors.

For a simple case like you show, it's pretty clear what you meant. But languages can't work on cases. They need to work with rules. For many other situations, it's not clear if the programmer made an error (using the wrong type) or if the programmer meant to do what the code assumes they meant to do.

If the code assumes wrong, you get a runtime error. Since they're tedious to track down, many languages err towards the side of telling you that you messed up and letting you tell the computer what you really meant (fix the bug or explicitly do the conversion). Other languages make the guess, since their style lends itself to quick and easy code that is easier to debug.

One thing to note is that implicit conversions do make the compiler a little more complex. You need to be more careful about cycles (let's try this conversion from A to B; oops that didn't work, but there's a conversion from B to A! and then you need to worry about C and D sized cycles too), which is a small motivation to avoid implicit conversions.

  • Mostly was talking about functions that can only work with one type, making the conversion obvious. What would be one that would work with multiple types, and the type you put in makes a difference? print("295") = print(295), so that wouldn't make a difference, except for with variables. What you said makes sense, except for the 3d paragraph... Can you reiterate? – Quelklef May 26 '15 at 2:01
  • 30
    @PieCrust - 123 + "456", did you want "123456" or 579? Programming languages don't do context, so it is difficult for them to "figure it out", since they would need to know the context that the addition is being done in. Which paragraph is unclear? – Telastyn May 26 '15 at 2:07
  • 1
    Also, maybe interpretation as base-10 is not what you wanted. Yes, implicit conversions are a good thing, but only where they cannot possibly hide an error. – Deduplicator May 26 '15 at 9:23
  • 13
    @PieCrust: To be honest I would never expect len(100) to return 3. I would find it much more intuitive for it to calculate the number of bits (or bytes) in the representation of 100. – Mehrdad May 26 '15 at 9:34
  • 3
    I'm with @Mehrdad, from your example it's clear that you think it is 100% obvious that len(100) should give you the amount of characters of the decimal representation of the number 100. But that's actually a ton of assumptions you're making while unaware of them. You could make strong arguments why the number of bits or bytes or characters of the hexadecimal representation (64 -> 2 characters) should be returned by len(). – funkwurm May 26 '15 at 11:26
14

Implicit conversions are quite possible to do. The situation where you get in trouble is when you don't know which way something should work.

An example of this can be seen in Javascript where the + operator works in different ways at different times.

>>> 4 + 3
7
>>> "4" + 3
43
>>> 4 + "3"
43

If one of the arguments is a string, then the + operator is a string concatenation, otherwise it is addition.

If you are given an argument and don't know if it is a string or an integer, and want to do addition with it, it can be a bit of a mess.

Another way to deal with this is from the Basic heritage (that perl follows from - see Programming is Hard, Let's Go Scripting...)

In Basic, the len function only makes sense being invoked on a String (docs for visual basic: "Any valid String expression or variable name. If Expression is of type Object, the Len function returns the size as it will be written to the file by the FilePut function.").

Perl follows this concept of context. The confusion that exists in JavaScript with implicit conversion of types for the + operator being sometimes addition and sometimes concatenation doesn't happen in perl because + is always addition and . is always concatenation.

If something is used in a scalar context, its a scalar (e.g. using an list as a scalar, the list behaves as if it was a number corresponding to its length). If you use a string operator (eq for equality test, cmp for string comparison) the scalar is used as if it was a string. Likewise, if something was used in a math context (== for equality test and <=> for numerical comparison), the scalar is used as if it was a number.

The fundamental rule for all programming is "do the thing that surprises the person the least". This doesn't mean there aren't surprises in there, but the effort is to surprise the person the least.

Going to a close cousin of perl - php, there are situations where an operator can act on something in either string or numeric contexts and the behavior can be surprising to people. The ++ operator is one such example. On numbers, it behaves exactly as expected. When acting on a string, such as "aa", it increments the string ($foo = "aa"; $foo++; echo $foo; prints ab). It will also roll over so that az when incremented becomes ba. This isn't particularly surprising yet.

$foo = "3d8";
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";

(ideone)

This prints out:

3d8
3d9
3e0
4

Welcome to the dangers of implicit conversions and operators acting different on the same string. (Perl handles that code block a bit differently - it decides that "3d8" when the ++ operator is applied is a numeric value from the start and goes to 4 right away (ideone) - this behavior is well described in perlop: Auto-increment and Auto-decrement)

Now, why one language does something one way and another does it another way gets to the design thoughts of the designers. Perl's philosophy is There's more than one way to do it - and I can think of a number of ways of doing some of these operations. On the other hand, Python has a philosophy described in PEP 20 -- The Zen of Python which states (among other things): "There should be one-- and preferably only one --obvious way to do it."

These design differences have lead to different languages. There is one way to get the length of a number in Python. Implicit conversion goes against this philosophy.

Related reading: Why doesn't Ruby have implicit conversion of Fixnum into String?

  • IMHO, a good language/framework should often have multiple ways of doing things which handle common corner cases differently (better to handle a common corner case once in a language or framework than 1000 times in 1000 programs); the fact that two operators do the same thing most of the time should not be considered a bad thing, if when their behaviors differ there would be benefits to each variation. It should not be difficult to compare two numeric variables x and y in such a way as to yield an equivalence relation or ranking, but IIRC Python's comparison operators... – supercat May 26 '15 at 23:38
  • ...implement neither equivalence relations nor ranking, and Python doesn't provide any convenient operators which do. – supercat May 26 '15 at 23:39
11

ColdFusion did most of this. It defines a set of rules to handle your implicit conversions, has a single variable type, and there you go.

The result is total anarchy, where adding "4a" to 6 is 6.16667.

Why? Well, because the first of the two variables is a number, so the result will be numeric. "4a" is parsed as a date, and seen as "4 AM". 4 AM is 4:00/24:00, or 1/6th of a day (0.16667). Add to 6 and you get 6.16667.

Lists have default separator characters of a comma, so if you ever add an item to a list containing a comma, then you just added two items. Also, lists are secretly strings, so they can get parsed as dates if they only contain 1 item.

String comparison checks if both strings can be parsed to dates first. Because there's no date type, it does that. Same thing for numbers and string containing numbers, octal notation, and booleans ("true" and "yes", etc.)

Rather than failing fast and reporting an error, instead ColdFusion will handle datatype conversions for you. And not in a good way.

Of course, you could fix the implicit conversions by calling explicit functions like DateCompare... but then you've lost the "benefits" of implicit conversion.


For ColdFusion, this is a way to help empower developers. Especially when all those developers are used to is HTML (ColdFusion works with tags, it's called CFML, later they added scripting via <cfscript> as well, where you don't need to add tags for everything). And it works well when you just want to get something working. But when you need things to happen in a more precise way, you need a language that refuses to do implicit conversions for anything that looks like it could have have been a mistake.

  • 1
    wow, "total anarchy" indeed! – hoosierEE May 27 '15 at 1:52
7

You're saying that implicit conversions could be a good idea for operations that are unambiguous, like int a = 100; len(a), where you obviously mean to convert the int to a string before calling.

But you're forgetting that these calls may be syntactically unambiguous, but they may represent a typo made by the programmer who meant to pass a1, which is a string. This is a contrived example, but with IDEs providing autocomplete for variable names, these mistakes happen.

The type system aims to help us avoid errors, and for languages who choose stricter type checking, implicit conversions undermine that.

Check out Javascript's woes with all the implicit conversions performed by ==, so much so that many now recommend sticking with the no-implicit-conversion === operator.

6

Imagine for a moment the context of your statement. You say this is the "only way" it could work, but are you really sure it will work like that? What about this:

def approx_log_10(s):
    return len(s)
print approx_log_10(3.5)  # "3" is probably not what I'm expecting here...

As others have mentioned, in your very specific example, it seems simple for the compiler to intuit what you meant. But in a larger context of more complicated programs, it is often not at all obvious whether you meant to input a string, or typo'd one variable name for another, or called the wrong function, or any of dozens of other potential logic errors.

In the case where the interpreter/compiler guesses what you meant, sometimes it's magically works, and then other times you spend hours debugging something that mysteriously doesn't work for no apparent reason. It is much safer, in the average case, to be told that the statement doesn't make sense, and spend a few seconds correcting it to what you actually meant.

3

Implicit conversions can be a real pain to work with. In PowerShell:

$a = $(dir *.xml) # typeof a is a list, because there are two XML files in the folder.
$a = $(dir *.xml) # typeof a is a string, because there is one XML file in the folder.

Suddenly there is twice as much testing needed and twice as many bugs as there would be without implicit conversion.

2

Explicit casts are important for they're making your intent clear. First of all using explicit casts tells a story to someone who is reading your code. They reveal that you intentionally did what you did. Moreover the same applies to the compiler. The following is illegal in C# for example

double d = 3.1415926;
// code elided
int i = d;

The cast will make you loose precision, which could easily be a bug. Hence the compiler refuses to compile. By using an explicit cast you are telling the compiler:"Hey, I know what I'm doing." And he will go:"Okay!" And compile.

  • 1
    Actually, I dislike most explicit casts more than implicit ones; IMHO, the only thing (X)y should mean is "I think Y is convertible to X; do the conversion if possible, or throw an exception if not"; if a conversion succeeds, one should be able to predict what the resulting value will be *without having to know any special rules about converting from the type of y to type X. If asked to convert -1.5 and 1.5 to integers, some languages would yield (-2,1); some (-2,2), some (-1,1), and some (-1,2). While C's rules are hardly obscure... – supercat May 26 '15 at 16:55
  • 1
    ...that kind of conversion seems like it would be more appropriately performed via method than via cast (too bad .NET doesn't include efficient round-and-convert functions, and Java's are overloaded in somewhat silly fashion). – supercat May 26 '15 at 16:56
1

Languages that support implicit conversions/casts, or weak typing as it is sometimes referred to, will make assumptions for you that don't always match up with the behavior you intended or expected. The designers of the language may have had the same thought process as you did for a certain type of conversion or series of conversions, and in that case you'll never see a problem; however if they didn't, then your program will fail.

The failure however, may or may not be obvious. Since these implicit casts occur at runtime, your program will run along happily and you'll only see an issue when you look at the output or result of your program. A language that requires explicit casts (some refer to this as strong typing) would give you an error before you program even begins execution, which is a much more obvious and easier problem to fix that implicit casts gone wrong.

The other day a friend of mine asked his 2 year old what 20 + 20 was and he replied 2020. I told my friend, "He is going to become a Javascript programmer".

In Javascript:

20+20
40

20+"20"
"2020"

Thus you can see the problems implicit casts can create and why it's not something that can just be fixed. The fix I would argue, is to use explicit casts.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.