Summary
What to do if you are stuck with existing libraries and API's.
Advertisement
There's a fundamental difference between your own code and libraries
of other people: You can change or extend your own code, but if you
want to use some other libraries you have to take them as they are.
A number of constructs have sprung up in programming languages to
alleviate this problem. Smalltalk has meta classes, Ruby has
modules. These are very powerful, but also dangerous, in that you
modify the behavior of a class for an entire application, some parts
of which you might not know. C# 3.0 has method extensions, which are
more local, but also more restrictive in that you can only add
methods, not fields or interfaces to a class.
Scala has implicit parameters and conversions. They can make existing
libraries much more pleasant to deal with. A particularly nice example
is a wrapper for simplifying JDBC, written by Ross Judson
(I misattributed this to Jamie Webb in the original version of the blog;
my apologies).
The rest of this blog explains what implicits are (you can skip it if
you know it already, or if you don't care). Say you have a value
x of type Array[int] and you want to assign this value to some
variable of type String:
var v: String = x
Array[int] does not conform to String, so normally this would
give a type error. However, you can define a conversion function from
arrays of arbitrary element type T to String, like this:
(Yes, arrays have a usable toString method in Scala!). The only
non-standard aspect of this function is its implicit modifier,
which labels it as an implicit conversion. If such an implicit
conversion is visible at the point of an otherwise illegal assignment,
the conversion is automatically inserted. So the above assignment
would be expanded to:
var v: String = array2string(x)
Implicit conversions can also be applied in other situations. For
instance they can be applied when selecting a member of some value
which is undefined in its type. An example is an append operator
on arrays, to be used as in the following:
val x = Array(1, 2, 3)
val y = Array(4, 5, 6)
val z = x append y
Scala parses the last expression as a method call:
x.append(y). The problem is that Scala's arrays are directly
mapped to Java's arrays, and there is no append method in either
type! However, implicit conversions can help. We need to define a new
class, say RichArray, which defines an append method (and
which might also define other methods we want to add to the Array
type):
class RichArray[T](value: Array[T]) {
def append(other: Array[T]): Array[T] = {
val result = new Array[T](value.length + other.length)
Array.copy(value, 0, result, 0, value.length)
Array.copy(other, 0, result, value.length, other.length)
result
}
}
Now, all that remains is to define an implicit conversion from plain
arrays to rich arrays:
implicit def enrichArray[T](xs: Array[T]) = new RichArray[T]
With this conversion, we have effectively enriched Java's array class
with an append method.
> I wonder the same thing. If there are two implicit > conversion paths, does the Scala compiler complain, or > pick one based on scoping or other criteria?
That depends. In a sense, implicits are treated as overloaded functions. If there are several applicable implicits, the Scala compiler will apply overloading resulution to pick a most specific one. If there is no unique best conversion, an ambiguity error results.
Example:
implicit def string2int(x: String): int = x.length implicit def object2int(x: Object): int = x.hashCode
val x: int = "abc"
In that case, the string2int conversion will be picked, because it is more specific than the object2int conversion. On the other hand, if we added another conversion
implicit def string2intAlt(x: String): int = x.hashCode
So if I do an inopportune "promiscuous import", I may end up importing a more specific set of conversions, and thus the behaviour of my program will change?
> So if I do an inopportune "promiscuous import", I may end > up importing a more specific set of conversions, and thus > the behaviour of my program will change?
Yes. Implicits are very powerful but you should use them with care.
and repeating the mistake in another language doesn't make it better.
In general, I find that automatic type conversion leads to programs that are fragile with performance that is difficult to predict and occasionally very surprising behavior.
If you have something and you want it to become something else, its not a burden to call the conversion. IOW
String x = someObject.toString();
makes it clear that type conversion is occurring to the reader. But
String x = someNonStringObject;
would lead the casual reader to assume that someNonStringObject is of a type compatibile with String.
So you've just eliminated a valuable aspect of communication between coder and reader and made the program just a bit more cryptic.
This is the kind of nonsense that lead me to abandon C++.
> If you have something and you want it to become something > else, its not a burden to call the conversion. IOW > > String x = someObject.toString();
You might have overlooked that 'implicit' is a declarative that affects the whole component. So it's about not writing boilerplate in each place when an existing component shall be coupled to another with slightly different conventions. You might even replace the component by an other one by just replacing the 'implicit' conversion without touching the code otherwise. If your UTs fail afterwards you still can handcraft the conversions and trash implicit.
Todd wrote: <But String x = someNonStringObject; would lead the casual reader to assume that someNonStringObject is of a type compatibile with String. So you've just eliminated a valuable aspect of communication between coder and reader and made the program just a bit more cryptic. This is the kind of nonsense that lead me to abandon C++.>
I wonder if it is fair to criticise C++ because it allows you to write non-intuitive code? Is there really a better alternative? ;-)
I agree with everything you say, but suspect that writing good code that is easy to understand, maintain and extend may only be achieved by professional discipline.
If anyone knows of a language that can help us in this respect, I would *love* to hear about it...?
Steve wrote: > Todd wrote: > I wonder if it is fair to criticise C++ because it allows > you to write non-intuitive code? Is there really a better > alternative? ;-)
> > I agree with everything you say, but suspect that writing > good code that is easy to understand, maintain and extend > may only be achieved by professional discipline. > > If anyone knows of a language that can help us in this > respect, I would *love* to hear about it...?
Ruby (in my opinion) does well in this regard (even though it comes with even more aparatus that CAN be used for foot-shooting than C++), as does Eiffel.
Part of this is that the language is designed without significant areas where it's too easy and tempting to write really weird code and have it look like it is normal.
An example of this in C++ is the references syntax: In other languages, what end up as references and what end up as being passed by value is implictly defined. This means that when you look at a call and know what the various parameters are, you also know whether they are passed by reference or value - ie, whether they may or may not be changed after the call. In C++, the & operator override this - and I've relatively often seen people write void f(int & varToChange); instead of void f(int *varToChange); because they see the calling convention f(&lexicalVariable); as ugly, and they want to modify the variable in the caller's scope.
I've also seen really weird stuff come out from the conversion rules in C++, and a lot of abuse of overloading. These are problems that aren't available in many other languages: There simply is no way to get inconsistent behaviour. This means that it's much simpler to be sure of what happens at the point of call in these languages, and that decrease the amount of problems in programs produced in them.
> I wonder if it is fair to criticise C++ because it allows > you to write non-intuitive code? Is there really a better > alternative? ;-)
Don't get me wrong here, I completely see where you are coming from but someone could make the sme argument about assembly or machine code. My personal opinion is that C++ allows things that are not only problematic from a maintenance perspective, it also allow things that are dangerous and, frankly, unnecessary. As I use more languages and learn more about Scala, C++ seems more and more like a dinosaur, COBOLishly archaic, perhaps.
But again I do agree that a language should help us solve problems and not second-guess us at every turn, however, it easy to demonstrate that the argument that anything goes and if there's an issue, it's the developers fault doesn't hold a lot of water. There's a middle ground and I don't think there is one answer.
>You might have overlooked that 'implicit' is a declarative that affects the whole component.
No, I caught that. But implicit behavior leads to assumptions and assumptions lead to bugs.
FORTRAN had an implicit typing convention that determined the type of a variable based on what letter it started with. In the 77 version, a declaration IMPLICIT NONE was added to turn this feature off and require explicit typing because the old behavior was a source of bugs.
Many bugs in C++ stem from automatic unexpected type conversions.
In general, "magic" features often produce unexpected results, which cause bugs, and so should be avoided in language design.
Flat View: This topic has 32 replies
on 3 pages
[
123
|
»
]