Friday, March 09, 2007

Type safety, An Oxymoron?

I think I've found a concise definition for type safety. I found it on the C2 wiki, which is a great source for programming related info. Anyway here it is:

Type Safe
Any declared variable will always reference an object of either that type or a subtype of that type.

A more general definition is that no operation will be applied to a variable of a wrong type. There are additionally two flavors of type safety: static and dynamic. If you say that a program is type safe, then you are commenting on static type safety. That is, the program will not have type errors when it runs. You can also say that a language or language implementation is type safe, which is a comment on dynamic type safety. Such a language or implementation will halt before attempting any invalid operation.


Taking the first sentence. This rules out any type of conversion so int->float is type unsafe, it rules out any type of dynamic cast too. So that basically rules out C, C++, Java and C# as type safe. Moving on to the main paragraph we see that aswell as static type safety there is also the concept of dynamic type safety. Using this as our bench mark, still rules out C and C++, but deems Java and C# to be dynamically type safe (if we choose to ignore the issues surrounding conversions of primitives of course). This laxer definition of type safety also includes languages like Smalltalk, Python and Ruby. So all modern OO languages are dynamically type safe.

If this is true, what is the dynamic versus static typing debate all about? Is type safety an oxymoron? Reading on further on the C2 wiki:

There are various degrees of type safety.
This is different from
TypeChecking.
See also
StronglyTypedWithoutLoopholes, which is another term for (at least) dynamic type safety.
CategoryLanguageTyping

So using the "degrees of type safety" argument, Java could be said to be more type safe then say Smalltalk. This kind of makes sense, since even though Java is not fully static type safe, it is partially so. So type safety is relative. So you can rate languages on their degree of type safety. Statically typed languages are more type safe then dynamically typed languages generally. If you click on the link CategoryLanguageTyping you will find out that what we usually refer to as static typing isn't actually called static typing at all, the proper name is Manifest Typing, Static Typing means something else and includes Type Inference. Given the common use of the term static typing, I have chosen up to now not to use the proper term which is in fact Manifest Typng.

So what does all this buy us? At best we are partially type safe if we choose to use a language like Java. Partially? Is that useful? Either I'm safe or I'm not right? For example, when releasing to production, I can't tell the QA Manager that I believe my program is partially safe. He wants to know whether my program is safe.

So how do I know that my program is Safe? Well simple, I test it!

I could go into strong versus weak typing and the consequences, but the links are there if you're interested. No program is Type Safe, and to claim so is a bit of an oxymoron. IMO typing is no substitute for well thought out tests, but type checks can help to detect and track down bugs (either at compile time or runtime). Where I believe manifest typing is useful is in improving the readability of code, improving the comprehension of large systems, and improving tooling support for code browsing and editing. Examples of this is the code completion and refactoring features in Eclipse. Smalltalk has these features too, but with manifest type annotations, tools have that much more information to work with.

The downside of manifest typing is that all type systems use 'structural types'. Structural types are based on the structure of your code. Depending on the code annotations available, manifest structural types can limit expressiveness. This is why languages like Scala have invented a more expressive set of type annotations, to overcome the type constraints imposed by languages like Java. Strongtalk's type annotations are even more expressive. This had to be the case because the Strongtalk type annotations had to be applied to the existing Smalltalk 'blue book' library, and this was originally written without any manifest type constraints whatsoever. The other downside of manifest types is that your code is more verbose.

So ideally what you want is :

* Manifest type annotations that can express intent and do not constrain you (or no type annotations at all or type inference)
* Strongly typed without loop holes at runtime
* Tests that tell you whether your code is safe.

Type safe doesn't exist, and partial type safety is a poor substitute for the above!

74 comments:

steve said...

"IMO typing is no substitute for well thought out tests, but type checks can help to detect and track down bugs (either at compile time or runtime)."

Well, exactly. I don't think any good programmer would claim that typing is a substitute for tests: they should be used together.

Paul said...

Hi Steve,

Well, exactly. I don't think any good programmer would claim that typing is a substitute for tests: they should be used together.

Agreed.

But what follows logically when you have tests is that full test coverage + strong typing without loop holes is sufficient to achieve program safety.

It is this argument which persuaded Bob Martin, the former Editor of The C++ Report that dynamic languages like Smalltalk are just as safe as static languages even without manifest type annotations or type inference.

Paul said...

Hi Steve,

Bob Martin was a self confessed static typing bigot for years. I remember being on a C++ OOD training course he gave in 1996. He asked who had tried Smalltalk and when I put my hand up he proceded to explain why the language was totaly unsafe for large production systems.

Here is a link to a blog post on Artima, there is a good dicussion here too:

Are Dynamic Languages Going to Replace Static Languages?
by Robert C. Martin

steve said...

Sorry, but I simply don't believe that test coverage is a substitute for type safety. I'll come back to that in minute.

But even if it were, that would not be sufficient justification for abandoning typing. There are other good reasons for using it (1) it gives the run-time or compiler hints that allow significant performance boosts and (2) It allows certain refactorings and code analysis that can't be done otherwise and (3) it allows checking of code to be done much, much faster (in most good IDEs, even at edit time) than can be done with tests.

So why don't I think tests can be a substitute for typing?

Firstly because we are all imperfect coders. I challenge anyone to guarantee that you have sufficient test coverage free of gaps.

Secondly, because if you do write sufficient tests to replace typing, you are going to have to write a lot more tests than if you know you have typing which can be compiler-checked.

Thirdly, tests can't cover things that can happen dynamically at run time. You can include all the test coverage you like in your own code, but that does not help when your code calls into someone else's. This is why Smalltalk just isn't as safe as statically typed languages. You can end up with #doesNotUnderstand: messages being sent at run-time as a result of other code.

Like Bob Martin, I have huge experience of both dynamic and static languages, and have used both on major projects. I have simply come to a different conclusion.

Paul said...

Hi Steve,

By typing I am assuming you mean manifest typing. Personally, I have no problem with manifest typing, and like I said I would like the chance to experiment with manifest typing and a dynamic language. But this said, I think you are grossly under-estimating the power of tests...

... I'll take that challenge. Read "Test Driven Development by Example" by Kent Beck, and you will see that reaching 100% test coverage is relatively easy.

... I need to write no more tests without typing then I do with it. I have used TDD both with Java and with Ruby/Smalltalk, and to gain the same amount of test coverage with a dynamic language, I actually need to write less code (both test code and application code).

... Test can cover everything that occurs at runtime, because tests exercise the code and are performed at runtime. It is manifest typing that struggles at runtime, hence the need for type unsafe casts etc.

...Does Not Understand is not inherently unsafe. It is an exception. It is as unsafe as null pointer exception, or class cast exception or a number of exceptions that can occur in Java. This exception should not occur and you can test for it like any other exception (using the test pattern crash test dummy).

... I accept that you have experience of both, but from what you say I don't believe that you have become test infected like Bob Martin. Many XPers will not write a single line of code unitll they have a test for it. This is the beauty of test first. You only write code needed to past your tests. If you can't think of a test (a requirement) then the code isn't needed, and so isn't written.

There is an Agile/XP culture, which if you belong to it changes the meaning of safe programming all together. In this culture the only criterion for safety is tests!

All this said, I see a role for Manifest types, but if you accept and are a part of the TDD culture, that role is not program safety.

So it comes down to your programming style.

Paul said...

Hi Steve,

I understand your doubts. If I and hadn't seen TDD working in practice I would doubt it too. We all have our own ideas about testing, but TDD is a very precise, discipline and methodical approach. Coaching TDD is partly ow I make my money.

Ron Jefferies has created a series of 'bowling' example programs in a number of languages to demonstrate the reasoning and techniques behind TDD. He also compares TDD with 'programming by intent' which is the way we are normally taught (an is still useful).

Take a look At this search for 'bowling at XProgramming.com

All versions of TDD bowling are safe, which dispells the myth that type safety correlates with program safety. BTW guess which version of Bowling is the most elegant and requires the least lines of code?

Paddy3118 said...

Customer requirements are used to generate tests. An implementation that satisfies the requirements should satisfy the customer. Another implementation that satisfies the tests with higher code coverage is a better implementation of the requirements.

The above can drive the production of quality software. Quality defined as something that satisfies the requirements - and only the requirements.

Switch to type safety. How do you define requirements in terms of types? It seems to me that you can only do that by adding tests. Types seem to help in creating the best executable program, but unless the "Manifest type annotations" the author speaks of are a type of provable property over time then type checking has to be thought of as merely a tool in the programmers toolbox and subservient to testing in the overall scheme of deivering quality software.

- Paddy.

steve said...

Paul - I just can't agree with you. It is a simple matter of information theory. If you have Manifest Typing then you have more information in your code about what is happening and what the programmer intends. If you don't have it, then you have to provide that information elsewhere.

Also, you are doing your trick of understimating 'opponents' and trying to mind-read :) Just because I disgree with you does not mean I am not a test fanatic!

And you simply can't test everything during development that happens at runtime. Let me try and explain why. You may be connecting with a library that you didn't write and perhaps don't even have the code for. You can't predict what this library will do in different circumstances. For example, at runtime, with a certain volume of data, or after a certain amount of time, the library may decide to return an instance of different class from the one expected. In a dynamic language, this need not be a subclass of the one expected, and it need not conform to an interface. If the library writer has made a mistake, then *bang* goes your code. With Manifest Typing the code has to conform to a compile-time contract.

You are misunderstanding what I mean by 'safe'. What I mean is 'safe from bugs', and that the program behaves in a predicted manner. Having a #doesNotUnderstand: message sent at run time can certainly not be something that is predicted, and can halt a process.

Also, I strongly doubt that the only criterion for safety is tests in Agile development. As you well know, there are plenty of Agile developers using statically types languages.

And 'least lines of code' is hardly a measure of software clarity - that old idea should have been thrown out years ago. Just compare a few lines of Perl with Java.

And you haven't dealt with most points I mentioned - speed of checking with static typing for example, or performance.

Paul said...

Hi Steve,

This is a difficult one. Which is why I think a concrete example would help. The Bowling Game is a good example because it is well documented. It is a shame we can't pair program and discuss these issues as they arise in the code, but who knows the speed at which Croquet is progressing perhaps we may be able to soon :^)

...First of all third party libraries. When I test, I test my code. I assume that third party code does what it says on the box. So when I test, I mock out the datbase, I assume that Oracle is bug free. I test that my code keeps up it's side of the contract and I assume that Oracle does the same.

Why? Well te first thing is a structural type is not the full story. The full type (e.g JDBC Connection) consists of both structure (method signatures) and behaviour. The behaviour is not enforced by a manifest structural type declartion.

Secondly, the thing with contracts is that you must trust the other party to do their bit. If you don't trust them, then a contract won't help, you're better off not using them at all!

... The points you've raised that I haven't dealt with, aren't relevant to the point I'm trying to make which is that program safety and type safety are two different things, and one does not infer the other.

If I want my program to be safe, then I test my program for safety.

If I want my program to be statically type safe then I'm stuck. If I want my program to be partially statically type safe then I use Java etc.

But if my user asks me does the program work, and does it meet his requirements, and is it stable? Telling him that it is partially type safe doesn't help. He wants to know whether I have tested for correctness and stability. This cannot be done statically, the only way to do this is to exercise the code, and even then I can only vouch for the test cases I've thought of. So coming back to my user, what he is going to say to me is "Have you tested this?" And my answer will be: "Yes, and these are the tests and test results, and I'm pretty confident that these tests demonstrate that the program is safe, and fit for purpose".

If I can't say this, then I've not done my job, manifest types or not.

steve said...

Firstly, you don't have to trust the other party about them writing the parts of a contract that can be expressed as a superclass or interface. That can be checked using information compiled into their libraries.

Bringing up Oracle as an example of third party libraries is irrelevant. Much of the code you will use is not written by Oracle, even if you want to use them as a measure of quality. The code could be written by Jon Smith who has been brought in to help, and does not share your enthusiasm for tests. I am sure you must have had to deal with that kind of coding, as I have.

Telling your customer your code is partially type safe certainly does help - I simply can't understand why you think it doesn't. It is a formalising and automating of contracts that you would otherwise have to write in (fallible) tests. It is a guarantee of those contracts for the future if the code is re-used.

You haven't answered the point I made about Smalltalk. You can claim safety all you like, but you have no guarantee that any object from any third-party library you call at run time can respond to a message you sent. The only way you can guarantee this is if you have full access to the code and write all the tests yourself.

You just can't test everything, and simply repeating that you can does not make it true. For example, some sort of database interface may detect that at run-time you are using larger transactions than you use for tests, or it may notice a larger load, and it may switch to an alternative implementation of some code to deal with that. But, it is buggy, and that alternative implementation is missing a method that you call. So, bang! You get a #doesNotUnderstand:, and the process halts.

You seem to be putting forward a point of view that because Manifest Typing is not a complete guarantee of anything (and no-one is claiming it is), then it is worthless and can be abandoned and replaced by tests. I don't find this a sensible argument. It is like claiming that because seatbelts don't prevent all injuries, that we should abandon them and trust to safe driving!

Paul said...

Hi Steve,

We are having an academic discussion about something that is inherently pratical. A safe program is on that works, period. How you achieve this is up to you as a developer, there is no one way.

What I am saying is that if you test in the right way and rely on tests, you can produce high quality software, bug free, irrespective if you choose to use manifest typing or not.

To me this is just a simple fact born of experience.

...And again, I repeat you can test everything. Please provide an example of code that you believe is untestable and I'll write a test for you. Like I said, I accept your challenge :^).

I don't understand the point you make about interfaces, contracts, types and DoesNotUnderstand.

Oracle can contain bugs whether I use manifest types or not. Oracle may not obey the interface contract whether I use manifest types or not. Manifest types are irrelevent here. The issue is whether Oracle has bugs and whether I should be testing their code.

Please give a concrete example of what you mean.

steve said...

"We are having an academic discussion about something that is inherently pratical."

I am being extremely practical, and I am talking about what can happen in the real world.

"I don't understand the point you make about interfaces, contracts, types and DoesNotUnderstand."

"Please give a concrete example of what you mean."

I gave you one in my last post. Let me try and give one in more detail.

Suppose you have a third party library, written by someone else, who has tested poorly. You don't have the source code. This library handles, say, database transactions. (Let's say it is a Smalltalk equivalent of JDBC).

You expect this library to provide you with instances that respond to
a method called #flush (which is used to write objects to the database). You test within the range of transaction sizes you think reasonable (say up to 1000 records). However, the library (without you knowing) has a class designed to handle large transactions efficiently, but unfortunately the fellow never tested flushing for this (as he does this automatically), and ... oops! He actually forgot to implement the #flush method for you to use!

So your code happens to hit a 10,000 record transaction, and you try and call #flush and BANG! You get a totally unexpected #doesNotUnderstand: call.

The point is, you can only test for what you think will happen, and what you think will happen is part of discussion between fallible people (you and the client). Manifest Typing gives you more safety - all instances of that transaction manager would have to conform to a TranactionManager interface, agreed by you and the third-party coder. This interface includes the flush() method, so his code won't even compile if it does meet the Manifest Typing part of the contract.

This was just off the top of my head, but I am sure I can come up with plenty of other examples.

The point is not for you to write a test for what you know. It is about what happens outside the range of what you know.

"Oracle may not obey the interface contract whether I use manifest types or not."

You are missing the point. It must obey the Manifest Type part of the Interface contract, which is far better than no guarantees at all - it is the seatbelt argument.

Paddy3118 said...

Hi Steve, Paul,

Steve, I can't be sure, but I believe you might have been replying to me when you mentioned Paul a few messages back?

In one of your (Steve's), replies you state:

You are misunderstanding what I mean by 'safe'. What I mean is 'safe from bugs', and that the program behaves in a predicted manner. Having a #doesNotUnderstand: message sent at run time can certainly not be something that is predicted, and can halt a process.

A customer would want you to deliver a program that was bug free, which manifest typing could help towards that specific goal, but the customer wants a program that fulfils his requirements. You need to test that you fulfil requirements and you could test to eradicate bugs too.

Also, I strongly doubt that the only criterion for safety is tests in Agile development. As you well know, there are plenty of Agile developers using statically types languages.

The Agile developers will be testing like crazy, no matter what the type of language used.
Something that compiles in a Manifestly typed language, compiles. Period.
It says nothing about its fitness for purpose.

And 'least lines of code' is hardly a measure of software clarity - that old idea should have been thrown out years ago. Just compare a few lines of Perl with Java.

Sorry Steve, I'm not doing very well at explaining myself. What I meant was that untested functionality, or functionality that does not contribute to meeting the requirements, detracts from the quality of the delivered code.  If you think you've finished, re-run your test suite, and find code that is not executed, then what is its purpose? In the real world there is usually the option of leaving the code in, together with an explanation of why it is not exercised by the test suite, but ignoring it is bad. (as is not running code coverage :-) .

On your later topic of  "How do you test external code/libraries". One way is to arrange your code to minimise the places where it interacts with such external code, and arrange it so that you can monitor the data flow between your code and the library to ensure that both adhere to the usage spec. This makes it easier to monitor the interface when you think their may be a problem - as well making it easier for you to submit bug reports when you think the library is malfunctioning.

- Paddy.

steve said...

"Something that compiles in a Manifestly typed language, compiles. Period.
It says nothing about its fitness for purpose."

It certainly does. Suppose you are writing something that implements the JDBC spec. If it compiles matching the interfaces, that shows at the very least that you have all the methods in place.

"What I meant was that untested functionality, or functionality that does not contribute to meeting the requirements, detracts from the quality of the delivered code."

It doesn't, if you are simply providing that functionality in the form of Manifest Typing. This is tested functionality (by the compiler), but provides a contract in at least some ways beyond the scope of your tests, with no extra effort.

Paul said...

Hi Steve,

What Paddy and I are saying is the same. I see your example, and I addressed it before. Let me do it again in more detail.

I do not care about the implementation of the JDBC driver you speak of. It has a contract that specifies how it should behave under all circumstances. The only time I care is when there is a bug in the Smalltalk JDBC driver as you describe.

I am assuming that what you describe is a bug and not a feature. If it were a feature then this behaviour should be part of the stated interface contract, and I should expect it. So assuming the driver has a bug, what do I do?

Firstly I isolate my code and test that to ensure it is bug free. I do this by creating a Mock JDBC driver and ensure that I interact with it as per the contract. Satisfied that my code works as a standalone unit (unit tested) I now need to integrate with the third party library and integration test.

Now if the contract is clear and unambiguous and my unit tests reflect the contracts requirements on my code in a high fidelity way then if the JDBC driver company have tested their code to the same level of fidelity against the contract, I should be able to perform integration tests where the test harness excites my code and my code uses the JDBC driver and I get back expected results. Should I find integration test failures, and if I am sure that my unit testing is not flawed, then I need to monitor the interface and ensure that the JBDC driver code is obeying the contract as Paddy describes.

At this point a number of things could transpire:

A. It turns out that I misunderstood the contract and that my code and my unit tests are flawed. This is a flawed requirement for my code, which equates to a flawed unit test (requirements and tests are equivalent). So the first thing I do is fix the test to reflect the true contract and the true requirement. My code will fail this new unit test in the same fashion that it failed the integration test, at this point I will fix my code to make the unit test pass, and then try integrating again.

B. The JDBC driver is not behaving as specified in the contract. Through my monitoring I have identified the specific failure and I inform the third party in the hope that this is a known bug and that they have a patch for it.

C. The contract turns out to be ambiguous and poorly specified. My assumed understanding of the interface turns out to be different from what the third party has actually implemented. So I update my unit tests to reflect the true contract, and I amend my documented understanding of the interface to remove the ambiguity. I also suggest to the third party that they do the same. I fix my code making it pass the new unit tests and integrate again.

The specifics of the example you give falls into scenario B. The testing of the JDBC Driver is a responsibility of the third party. My code works and is safe. The JDBC driver does not work and is unsafe. If I choose to use it then my whole program will be unsafe. How do I deal with this scenario? I believe is your question. My answer is the same whether you have manifest types or not. The answer is to smoke test third party software on receipt, and use third party software that has a wide user base and has a reputation for being bug free and safe. If the third party software has a standard interface I could choose to switch to a different third party vendor.

I would give the same advice to a Java programmer as I would give to a Smalltalk programmer. Just because in Java I get a static interface which won't compile if I mispell a method name doesn't help me much with real bugs. In Smalltalk I would find this out in a second the moment I integrate and the notifier informs me of the missing method name and launches the debugger. This is why I say Strongly typed with no loop holes is important. Smalltalk will tell you exactly where the contract has been broken straight away the moment you exercise the interface. If you test, and run all your tests all the time, or if you choose to smoke test the third party library as I describe, you will find out straight away, not in production, not during field trials, but straight away in development. So the advantage you point to does not exist. With Java you find out the moment you compile, with Smalltalk you find out the moment you run your tests. Given that with Java you should be testing anyway (because a static type check does not guarantee correct behaviour and is not a seat belt, but is more akin to a flimsy length of cotton) the chances are that you will discover real bugs not typos at about the same time with Java as you would with Smalltalk. And Typos will be discovered a few moments later in Smalltalk the minute you run your tests.

Sorry for the length, but I wanted to be clear.

Paul said...

Hi Steve,
There is a subtlety in your scenario which I didn't see on first reading:

So your code happens to hit a 10,000 record transaction, and you try and call #flush and BANG!

So the third party implementation changes dynamically at 10, 000 record transactions. Sure this will require a lot of thorough testing by the third party. But Java allows the same thing too. Hibernate does it all the time. Also all these web frameworks sucking strings over a socket and parsing them into ints, floats etc. Your compiler can't help you here either. In fact java specifically allows type unsafe behaviour so that you can do these things, which is why Java is only partially type safe.

This is why the bottom line in any language is that the only way to guarantee correct behaviour is to test.

steve said...

Regarding one post:
"If the third party software has a standard interface I could choose to switch to a different third party vendor."

But what if it doesn't?

Also, you seem to have missed the point of my example. You can only smoke test what you have thought of smoke testing. My example, poor though it was, illustrated that all it takes is a drift outside of the test range you have thought of, and you can get problems that Static Typing could have revealed beforehand.

I can give you a real example of how this kind of testing can fail, and why this important in the real world. In one place where I work, some badly written code was running a large business. It has been used for years, and tested so as to meet all the original specifications. However, it had a limit on stock values, and because it used an unsafe language (I am not talking about type safety here, just illustrating the limits of testing), when the company grew over the years, it passed the stock value limit and the software crashed in odd ways.

The fact is, we have to deal with real life, and not some ideal company where people have to hand all the current and likely future specifications and can spend time working out all posible tests. Having additional safety can help at least some way to avoiding such disasters.

The fact is that Static Typing gives you automatically broader guarantees of safety than testing alone. I can prove it! For any given range of tests (and you aren't allowed an infinite number of tests), I can specify the existence of some hypothetical code in a dynamic language that will run beyond the limit of any one of those tests, and which breaks the interface contract.

To paraphrase Donald Rumsfeld, it is the unknown unknowns you have to worry about!

Regarding the other post:

"But Java allows the same thing too."

No, it doesn't. Because if you define an interface for that transaction manager, then every thing that is a passed to you to that should have the role of a transaction manager must conform to that interface, so Java will not allow the missing method example I gave to happen.

"Hibernate does it all the time."

No, it does not. When hibernate provides persistence-capable object s, it provides proxies, which are basically subclasses of your existing classes. Therefore you know that the methods you defined can be called. The contract is maintained.

"In fact java specifically allows type unsafe behaviour so that you can do these things,"

No, this is irrelevant. This is exactly equivalent to reading data from a file. Yet no-one would claim that C's 'fscanf' was type unsafe because you don't know whether or not text will safely parse as an 'int'.

"which is why Java is only partially type safe.

This is why the bottom line in any language is that the only way to guarantee correct behaviour is to test."

No, the second statement does not follow from the first. I agree that Java is only partially type safe (although I would say 'overwhelming' type safe), but you can't logically conclude from that that testing is the only way to guarantee correct behaviour. You are ignoring the 'seatbelt' argument I have earlier.

Java's small lack of type safety is not random - you can know where it happens, so it can definitely help with guaranteeing correct behaviour.

Having read around a bit, I think I have some idea where your attitude comes from - correct me if I am wrong!

Static Typing has, in the past, been used in a very non-Agile way. It has been part of a rigid initial design, and used to freeze things in very non-Agile way. Well, I certainly don't use it like that. I frequently refactor my code to use different types. These days, Static/Manifest Typing certainly need not mean that you need have fixed idea. I would say it is a useful tool for managing changing ideas.

I can also see an argument that dynamic languages can possibly encourage better code, because the lack of type safety encourages more tests in order to feel secure.

Paul said...

Hi Steve,

When you are using the dynamic proxy API and CGLIB, then all bets are off. Your compiler can't help here. Also when everything is a string which you need to parse, should your parsing rountines have a bug, or if you do not handle parsing exceptions properly your compiler can't help here either.

Dynamic casts, conversions, dynamic proxies, reflection, CGLIB, all runtime exceptions are all dynamic behaviour in Java that your compiler can't check for you.

So my point is that you need to test for these things yourself.

Paul said...

Hi Steve,

There are so many factual errors in your last post, I don't know where to begin. Java libraries perform all maner of sins. Hiberante for instance does not subclass your class, what it does is use CGLIB to write a bytecode interface on the fly (no compiler check), it then uses that interface with the dynamic proxy API which then creates a proxy dynamically to an Hibernate class (no compiler check between proxy and class)that could be anything they want. So at 10,000 records the hibernate class used by the proxy could change and methods needed by hibernate may not exist. If they do not test for this it will fail just like your example.

Paul said...

Hi Steve,

Try using the dynamic proxy API. Here is an article on it:

Explore the dynamic Proxy API

It uses reflection like no tomorrow. in that "Invoke" method call all bets are off. It catches it's own DoesNotUnderstand, which in Java is called a InvocationTargetException. If Hibernate changes implementation on the fly using class.ForName at 10,000 records it could cause a ClassCastException or if they choose to use reflection an InvocationTargetException when you call method.invoke on "flush" and it doesn't exist.

Anyone doing this stuff would need to test thoroughly! You can't say "Well it compiled and I've got manifest types so it should work"


I've seen, the well it compiles so it should be fine attitude before, which explains why so much software is total crap.

Paul said...

Hi Steve,

Where the testing attitude comes from is the reality of poor quality software, which has plagued the software industry for years. We have been using manifestly typed software since the 70's and it hasn't helped much. It is like that length of cotton thread I referred to, when what we really need is a six point racing harness. C is manifestly typed, but that doesn’t stop it blowing up with “Segmentation Fault – Core dump” at runtime. After years of this, people eventually worked out, that what really mattered was Strong typing at runtime, so at least you could stand a chance of tracking down the bug when it occured. So C++ has Strong typing and Exception handling.

Even with this, an “Internal System Error – Please contact the System Administrator” message on the screen doesn’t help your user much, especially when he is needs that vital end of year report out for tomorrow. So eventually people finally woke up to the fact that the only way to guarantee that your code will work and will not let you down is to test it thoroughly. If you don't test it you can't guarantee that it works. Not a novel idea really, but you’d be surprised (or perhaps not :^)) at how little testing goes on.

The real issue is that the sharp end is at runtime not compile time. You can check all you like at compile time, but what matters is how your code behaves at runtime.

Think about the number of times you have stepped through code using a debugger and found some strange anomaly. The code kind of works but it isn't actually behaving as you had expected. A typical example of this is bounds checking and loops. The only way to get accurate feedback on what your code is actually doing at runtime is to run it.

The thing is, that the code is not the source code; the code is a collection of bits that are run on the hardware. It is how these bits behave at runtime that counts. So if there is a subtle bug in your compiler or a bug in your VM that is stopping your code from working (it does happen), then the only way you are going to find out is to test.

I would agree with you if manifest typing had indeed greatly increased software quality, but I spent most of my C++ career tracking down memory leaks and awry pointers. Now with Java, before I started working on XP projects, I spent a lot of time tracking down null pointer exceptions.

The idea that the compiler can take care of most things and all you need to do is test the exceptional cases seems blatantly naive to me. It just goes totally against my experience. I’m sure if you think about it and think about your real life experiences dealing with bugs, it blatantly goes against your experiences too.

2:36 PM

steve said...

You continue assume that because type safety isn't perfect that it is useless.

Note the sub-title of the article you linked to:

"Use dynamic proxies to bring strong typing to abstract data types".

Also, the Class.forName() argument is just plain silly. Sure, some idiot could do that to screw up your code. But we are back to the seatbelt argument again. Also, is it silly to have laws because some people break them?

This does not mean you are guaranteed things will work, and it does not mean that should not be tested, but it helps.

"Anyone doing this stuff would need to test thoroughly!"

Well duh! Of course :)

"You can't say "Well it compiled and I've got manifest types so it should work""

How many times do I have to repeat: I am not saying that.

"I've seen, the well it compiles so it should be fine attitude before, which explains why so much software is total crap."

And how many times do I have to repeat that is not my attitude!

I absolutely don't think that 'because it compiles it will be fine'. I think tests are vital (in fact, I have found it shocking how many of my own dumb mistakes it finds :)

All I am saying is that Strong Typing assists with checking. That is all. It isn't perfect - nothing is.

You seem to be arguing against a position I am not taking.

steve said...

Would you mind if I held off more discussion until tomorrow - you have raised some interesting issues I need to think about in detail (which, after all, is the point of such discussions for me).

I am not admitting you are right yet though :)

steve said...

OK. Further thoughts. This will probably be my final comment here, as I think I now have enough to post an article myself.

First, you are wrong about Hibernate. It does indeed create subclasses. It uses CGLIB to create dynamic proxies of existing classes, and if you read the GBLIB documentation, you will see the following statement:
"Essentially, CGLIB dynamically generates a subclass to override the non-final methods of the proxied class."

Your point about Class.forName() and use of reflection is a good one, but I think it misses a point about the situation in Java: the situations where compile-time type safety is broken are well-defined - especially in Java 5, where generics can be used. To quote from the Hitch-Hikers Guide to the Galaxy: "We have rigidly defined areas of doubt and uncertainty". If you have code and you know it uses reflection, and dynamic class loading and casting, you know you have potential unsafeness. But in code where you aren't using those, you just don't have that unsafeness.

However, I am absolutely and definitely not saying this is any excuse for lack of testing.

What I am trying to put forward is the 'seatbelt' principle. Because we are all human and fallible. Sure, things can be well tested in complete test coverage without loopholes. But to, me, that is rather like saying "all you have to do is hire good programmers". Well, this is not an ideal world. Static Typing is something you can use in parallel with testing to help ensure safety.

I will develop this argument (along with others) in far more detail, in an article in the next few days. Thanks for the debate - it has been very, very interesting and thought-provoking.

Paul said...

Hi Steve,

I am right about Hibernate. Please take a look at the reflection API. Java reflections have no means to generate a subclass. What happens is that the reflection API is used to generate an Interface by instantiating your class, using the reflections API to get the method signatures and then using CGLIB to create an interface from those method signatures. This is needed because the dynamic Proxy API uses an Interfaces not a class to create a proxy. Inside the proxies Invoke method you use method.invoke to invoke methods on a target class. Method.invoke will throw an InvocationTargetException if you spell the method name wrong, just like a DoesNotUnderstand in Smalltalk. Don't take my word for it. Look at these API yourself, they are all available online.

BTW. The reason why I know this is because I have used JMock which has exactly the same problem as Hibernate and uses CGLIB and the dynamic Proxy API in exactly the same way.

Take your time and think it through. You got some things factually wrong in your previous comments about types. Strong/Weak typing is about dynamic type safety. Manifest types and type inference is about static type safety these are two different things. Use the C2 Wiki, it is a great source. BTW I'm not saying that partial type safety is useless. I am saying that static type safety is not the same as program safety.

BTW. Your comment about needing good programmers is exactly right. That's why XPers programme in pairs. Two brains are better then one! Programming is an intellectual exercise. Automating programming is a dead end. What is needed is more effort spent mentoring programmers and giving them the skills they need. The fact that most programmers do not know how to test in a disciplined, methodical and effective way is an indictment on our industry as a whole.

You cannot automate the process of creating tests. Type systems do not automate the creation of tests. Type errors are just one of many types of error possible in a computer program. For safe programs you need good programmers who know how to create good tests, preferably test first. This is just the simple truth of it.

I look forward to your post, but like I said, take your time and do your research. I'm quite happy to wait!

steve said...

What happens is that the reflection API is used to generate an Interface by instantiating your class, using the reflections API to get the method signatures and then using CGLIB to create an interface from those method signatures. This is needed because the dynamic Proxy API uses an Interfaces not a class to create a proxy. Inside the proxies Invoke method you use method.invoke to invoke methods on a target class. Method.invoke will throw an InvocationTargetException if you spell the method name wrong, just like a DoesNotUnderstand in Smalltalk. Don't take my word for it. Look at these API yourself, they are all available online.

I know, and the result acts as a subclass, which is what I said, and which is what the writers of CGLIB say. I take their description of what their product does over yours! If fact, in Spring, if CGLIB fails to generate a subclass (because any part of the class being proxied is final), you get the following exception message:
"Couldn't generate CGLIB subclass of class"

Please note the word 'subclass'!

And...


"Take your time and think it through."

A bit condescending, I think.

"I am saying that static type safety is not the same as program safety."

I agree!

"Automating programming is a dead end."

In fact, automating programming is becoming increasingly popular, with frameworks like Rails, Grails, Seam and so on doing most of the work for you.

"Type systems do not automate the creation of tests."

Who said they did? Not me.

"Type errors are just one of many types of error possible in a computer program."

Obviously. But having type checking by a compiler is obviously a useful way to check for a lot of these types of errors (although it should not be the only way).

"The fact that most programmers do not know how to test in a disciplined, methodical and effective way is an indictment on our industry as a whole."

Ah! Now you are starting to describe the real problem I am talking about.

"For safe programs you need good programmers who know how to create good tests, preferably test first. This is just the simple truth of it."

Yes, absolutely. However, combine this statement with the statement above and you precisely describe the situation I am considering.

Most developers are poor at testing, but for safe programs we need developers are good at testing. So, by your own argument, we need something else to help (at least in some way) with program safety. That is where Static Typing can help, in the less-than perfect real world you describe. It is the 'seatbelt' approach - it is no substitute for good driving, but wise anyway.

Paul said...

Hi Steve,

No intent to be condescending. The CGLIB documentation is wrong, simply because java does not allow subclassing at runtime. Having said this, I can see why they do not want to describe what they actually do :^).

I can see where you are going. The proof of what you say is if you would write less tests in a manifestly typed language then you would with a strongly typed dynamic language.

My experience tells me you write virtually the same tests in both cases, and perhaps shorter more expressive tests in the dynamic language. The bowling examples by Ron Jefferies tend to concur with this.

I never think of types when I test, I think of requirements and test against those.

It will be interesting to see how you prove that you need less testing with manifest types.

Paul.

steve said...

"The CGLIB documentation is wrong"

Hmm. Shall you tell them or shall I? :)

"simply because java does not allow subclassing at runtime."

Well, actually, it does allow a form of it, in the way it allows dynamically generated implementations of Interfaces.

Technically, when you are using CGLIB, you are using more than Java! It is extending the language/framework to allow dynamic subclasses. One should not be concerned about how they are implemented, but by how they behave... which is like subclasses. (Perhaps we should call them 'duck subclasses' - they walk like subclasses, and quack like subclasses :)

"I can see where you are going. The proof of what you say is if you would write less tests in a manifestly typed language then you would with a strongly typed dynamic language."

No, I not saying that at all... definitely not. I would not expect fewer tests if someone used manifest typing.

What I am saying is that manifest typing backs up your tests - they are a useful form of failsafe - like a seatbelt! Of course, this
is only regarding safety. There are other uses for types as well.

When developing, I don't actually think of types either. I also think of requirements. But I use types to help describe requirements, and their explicit statement in code is like a form of note-keeping - it helps remind me what the requirements are for a bit of code. It makes some requirements explicit in code rather than implicit; well, at least that is the way it works for me.

steve said...

Well, actually, one example where testing would be reduced by manifest typing has just come to mind! It is not a reduction in the number of tests, but a reduction in the amount of code in a test.

Here is a Smalltalk test of some code that returns a tax value (you will have to forgive me if it is not correct Smalltalk - I have not coded much for a year or two).

| tax |
tax := taxCalculator calcTax.
(tax isKindOf: Currency) ifFalse: [
self error: 'Should have returned a Currency instance'].
(tax > MaxTaxValue or: tax < 0) self checkTaxRange: tax

Now, look at the Java equivalent

BigDecimal tax = taxCalculator.calcTax();
checkTaxRange(tax);

Note the difference? Part of the Smalltalk test should be that the tax calculator returns a form of Currency. If it returns a Float or some such, that is bad, as tax calculations should always be worked out to fixed precision for checking.

With Java, you don't have to include that code - the declaration that the value is expected to be a BigDecimal does all that for you.

The use of BigDecimal here is a contract between caller and function that makes explicit what is required, and saves code within tests.

Paul said...

Hi Steve,

Looked into CGLIB, and you are right. They do effectively create a subclass at runtime by using byte code enhancement (Enhancer.java). It works like I say, but they also set the super class as your class on the new proxy. So the proxy becomes a subclass of your class. A class created dynamically like this could still fail your 10,000 record transaction test, just as it could in Smalltalk (a DoesNotUnderstand or an InvocationTargetException). The difference is that in Smalltalk the implementation would be a lot more elegant and perhaps easier to test.

Anyway, this kinds of proves my point that you should test requirements. Here the design requirement (specification) is for a dynamic implementation class change at 10,000 record transactions. This is something you can test for (test that my driver uses a different implementation class for transactions greater than 10,00 records). You would need this test for both Smalltalk and Java as the design requirement is the same.

Anyway thanks for the info.

Paul said...

Hi Steve,

I see your example. Interesting. It seems to me that you are testing for errors instead of requirements. You are worried about the type of tax.

If the requirement is that the tax should be in a certain range (I think this is what you mean?) then just test for range. If tax doesn't support the right comparison message, then your test will fail with DoesNotUnderstand.

With SUnit, an explicit test failure:

self should: [ booleanExpression ]

Or an implicit failure like an uncaught exception such as DoesNotUnderstand is a test failure.

So you don't need the type test:

testTaxInRange
| tax |
tax := TaxCalculator calcTax.
self should: [tax between: 0 and: MaxTaxValue].

Which looks pretty similar to the Java example.

steve said...

You are right about CGLIB in one way - it is hugely inelegant!

"You would need this test for both Smalltalk and Java as the design requirement is the same."

But we are back to my original point - you can only test the design requirements you know. What I have increasingly found over the years is that software ends up being used beyond its design requirements! This is just a fact of life.

"If the requirement is that the tax should be in a certain range (I think this is what you mean?)"

No, it is far more complex than that. This is definitely a requirement, and not an error. It is a requirement that tax values be calculated to fixed precision, and that rounding is done according to certain rules. This is why so much financial code is done using BigDecimal - it meets the requirements of such calculations. 'Real' numbers don't, and neither do integers.

So, before you do any math with the number, you have to check that it meets the requirements. This is why the #isKindOf: test is crucial. Knowing it is of the specified class should give you that assurance (the functionality of that class is either standard, or can be tested elsewhere).

The point is, with dynamic typing you have to use some equivalent of #isKindOf:, whereas with Manifest Typing you can simply declare the variable type.

This is a good example, as I have come across precisely this failure in software written with dynamic typing, and it led to a long-term (but fortunately small) financial error.

Paul said...

Hi Steve,

We are very close ...:^)

... Back to the dynamic code change. It is a horrible bug I agree, but it stems from the design specification. If a third party decides to design their code like this they need to test it. The design has nothing to do with language choice.

I don't believe it is a problem with the interface contract. So I don't think the client you requires a 10,001 record transaction is going beyond the contract. It is just an implementation bug by the third party vendor.

... Back to your example. You BigDecimal in not the design requirement. the design requirement is the percision and rounding you need. It so happens that in java this is satified by BigDecimal.

So what you need are test that test for the appropriate percision and rounding you need.

Relying on BigDecimal is a bit lazy and potentially dangerous. For example your customer could change the percision and rounding requirements and you've got a "shot gun" chnage senario. You will need to change BigDecimal all oer your code to something else.

What I would do is implement a Money class that could wrap BigDecimal.

...But I agree, in Smalltalk you may need just one more test:

testThatTaxIsMoney

Or you may choose to explicitly test rounding and pecision:

TestTaxPercisionAndRounding2.34567
self should: [ tax = 2.34]

TestTaxPercisionAndRoundingFor2.344321
self should: [ tax = 2.33]

Or at some stage in both Java and Smalltalk you may need to test something that is unique to money:

testTaxConversionToDollars

In which case you would not need an explicit type test in Smalltalk.

But I think you have a point and we are thinking the same way. Tests are against the design requirements. To test a domain object class I would reckon on about a dozen tests. So I can live with one additional type test sometimes. My gut feeling is to use the explicit percisionandrounding test, that way I can use different percision for tax then for other things. But I've never worked in finance, so I bow to your judgement on this one.

Keep trying to think of examples.

steve said...

"I don't believe it is a problem with the interface contract. So I don't think the client you requires a 10,001 record transaction is going beyond the contract. It is just an implementation bug by the third party vendor."

You are right. It is a problem with the interface contract, and it is an implementation bug by the third party vendor, but assigning blame does not help the poor client when this happens, and it is something that could possibly have been detected by Manifest Typing. This is not YAGNI - if you are using Manifest Typing anyway, you get this potential protection for free.

Regarding the other point - you still are having to write your own tests (taxIsMoney), or even a more general purpose test (somethingIsMoney) for something that is given for you automatically by Manifest Typing:

BigDecimal thingThatShouldBeMoney;

"Relying on BigDecimal is a bit lazy and potentially dangerous. For example your customer could change the percision and rounding requirements and you've got a "shot gun" chnage senario. You will need to change BigDecimal all oer your code to something else."

No, BigDecimal is certainly not dangerous - on the contrary, its use is widely recommended. It can deal with all the different precision and rounding requirements - that is what is designed for, and why it is so very widely used in finance. It already has all the flexibility you describe.

So this definitely is a real case where Manifest Typing saves you at least a few lines of code. Actually, I now realise it saves you a lot more....

"To test a domain object class I would reckon on about a dozen tests. So I can live with one additional type test sometimes."

But, you see, this is far more general than just the simple example I gave. Once you have tested the domain object class, Manifest Typing assures (well, at least as much as #isKindOf:) you in each test (in fact, almost everywhere) that you are dealing with the that domain object class (or a subclass) and nothing else.

Because of duck typing, unless you check the 'kind' of objects given to you by sure (especially with 3rd party code) that what you are getting is what you think you should be getting. So, you should be using #isKindOf: all over the place, not just occasionally.

(The situation is even worse in languages like Ruby, where even knowing the class of an object is not enough, as methods can be changed on-the-fly at runtime).

If you don't think this is right, just look back at the tax calculation. Duck typing means that any object could have been returned that understands math methods, and it could have falsely passed a test.

I think this is a highly useful example, as it has illustrated a point I have been trying to make all along: you can only test what you know to test, and that might not be enough. Someone brought in to code financial work who was not a tax expert could have written the #calculateTax method wrongly, returning a 'Real' number. This would not have been detected by the calling code without checking. However, simpy by asking for a BigDecimal result (which the person writing the calling code, being a financial expert, would naturally do, would instantly find out the other developers mistake - it would not even compile, let alone run a test! In this case, Manifest Typing helped reveal the error.

And, I repeat, I have seen exactly this kind of wrong-type-used error in real dynamic code.

Paul said...

Hi Steve,

We are very close. I think you are going to discover that it works out about the same in tests needed and test coverage needed, although I take your point about manifest types making the code more explicit.

A point you made that I missed:

But we are back to my original point - you can only test the design requirements you know.

This isn't a requirement it is a bug, in third party code. It is down to the third party to test their software, and if it is full of bugs, hopefully they won't be in business for long :^) So I meant to say they need to test not you.

BTW. Smalltalk has an advantage here, because with Smalltalk you always have the third party source, so you could chose to fix their bug if you had to.

Paul said...

Steve,

I thought we where close, but I now unerstand what you are on about. Your concern is implicit interfaces versus explicit interfaces.

Your #flush example could happen as equally in Java as in Smalltalk. In your Currecncy example though Smalltalk could happily run with the wrong type of object untill it hits a doesNotUnderstand or an explicit breach of the interface.

In reality however this seldom happens. I've been using Ruby for quite sometime and the human brian is pretty good at matching types naturally, especially if you use good names. So the implicit contract is normally not an area for bugs.

Testing requirements means running the code, and if type errors do exist, your tests don't get very far.

In practice the type of problem you describe doesn't actually occur, but I agree from a theoritical stand point you have a point.

I think it comes down to something I said a while back. Testing is practical. Duck typing + tests works and produces high quality code. Manifest typing + tests works and produces high quality code. Duck or Manifest typing without tests can produce crap.

I've think you've got your work cut out proving that manifest typing improves program safety in the face of proper tests.

But I do understand your concern, I just don't think it's real. Not sure if it can be proved one way or the other...

Paul said...

Hi Steve,

This is why it isn''t real, because of naming. So back to your currency example:


checkTax: aCurrency
"Do something with aCurrency object"

In Smalltalk by convention you use the type of object as part of the interface parameter name. In java because of manifest typing you don't need to do this. Kent Beck wrote a good book about programming idioms for Smalltalk where he mentions this one.

So in Smalltalk you rely on convention whilst coding,with Java you rely on manifest types. I think this is that note-book thing you speak of to help you whilst you code. Ultimatey with both languages though you rely on rumtime tests, which are definitive.

I think the implicit/explicit interface thing is about helping the programmer. One way you rely on naming conventions, the other way you rely on type annotations.

But I still think that runtime tests ultimately guarantee program safety and that tests do not rely on conventions or type anotations to work their magic.

That's my guess..

steve said...

"Your #flush example could happen as equally in Java as in Smalltalk."

No, it could not. I have been over this. In Java you have explicit interfaces, so any TransactionManager (or whatever) is compiled against this interface. You could say 'well people use reflection, or Class.forName() and casting', but those are rarely used constructs in Java, so by definition it will happen less often in Java.

"In your Currecncy example though Smalltalk could happily run with the wrong type of object untill it hits a doesNotUnderstand or an explicit breach of the interface."

No - I don't think you are getting the point. I am talking in that example about Number subclasses. They have almost all methods in common. There is no reason why one should ever come across an explicit breach of an interface. After all, all are you doing in your code - simple math.

"I've think you've got your work cut out proving that manifest typing improves program safety in the face of proper tests."

But, as I have discussed, there is no such thing as 'proper tests'.

I think you are trying to have it both ways. You introduce the terms 'full coverage' and 'no loopholes', but now you talk about how things don't happen 'in practice', and how some things in Ruby 'seldom happen'.

That, at least to me, is not the language of full coverage!

I think I have clearly shown (certainly to my own satisfaction) how Manifest Typing gives you more safety.

"So the implicit contract is normally not an area for bugs."

Doesn't matter - we want as complete coverage as possible.

"Testing requirements means running the code, and if type errors do exist, your tests don't get very far."

Well, I have just shown that in some circumstances, they can, and can lead to very, very hard-to-trace issues (as in the financial code I mentioned). Some of these issues can only arise after runs of substantial length.

The convention of using the name of an object is all very well, until someone ignores it. Manifest Typing is simply a stronger form of guarantee that what you are getting is what you want to get. It isn't perfect, but that what is? From this conversation, tests certainly aren't either.. so use them both!

"One way you rely on naming conventions, the other way you rely on type annotations."

One way you rely purely on people, the other you get the assistance of the compiler or runtime as well.

"But I do understand your concern, I just don't think it's real. Not sure if it can be proved one way or the other..."

You just can't say that, because I have already said how the kind of type error we are talking about led to problems in financial software I was having to deal with. I am afraid it is a real concern based on experience.

Paul said...

Hi Steve,

You could say 'well people use reflection, or Class.forName() and casting', but those are rarely used constructs in Java

...This is exactly the kind of things your Java code will need to do to meet the design requirement as you describe it (a dynamic code change for transactions > 10,000 records).

...I actually agree with your Currency example though. The issues here are interesting. I also agree with your point on numbers, you code would work for sometime untill you got somewhere where the difference between a Real and a Currency made a difference. The checking by the compiler really does help here.

For me the issue is how common is this, and what would I need to do to prevent it:

1) checkTax: aCurrency

2) checkTax: tax <Currency> <^Boolean>

3) boolean checkTax(Currency tax)

Option 2 (Strongtalk) seems ideal to me since you have the type check and it is still late-bound.

Options 1 and 3 both have downsides in my opinion. I wonder if this problem is just specific to numbers and finance or whether it occurs elsewhere too?

Paul said...

Hi Steve

This concerns me a little:

I am talking in that example about Number subclasses. They have almost all methods in common. There is no reason why one should ever come across an explicit breach of an interface.

If there really isn't a difference between a Real or a Currency why have two different classes?

Also why not add a method to Number that always guarantees to give you a currency when you want one:

Number>>asCurrency
^ Currency new: self.

So you could do

tax := anyOldValue asCurrency.

Interesting though. Keep digging. I'm looking forward to your post on this stuff.

steve said...

"This is exactly the kind of things your Java code will need to do to meet the design requirement as you describe it (a dynamic code change for transactions > 10,000 records)."

Of course it isn't. They can simply provide a subclass or an alternative implementation of an Interface, but with different behaviour.

"Option 2 (Strongtalk) seems ideal to me since you have the type check and it is still late-bound."

Well, if you think late binding is useful :) But, I can see how this would definitely help.

"I wonder if this problem is just specific to numbers and finance or whether it occurs elsewhere too?"

As I posted earlier, this is a general problem. All this problem requires is a truly polymorphic situation, where one class has in common all the methods you are going to use but has different internal behaviour. Typing gives information about that internal behaviour: you know that Integer's implementation of '+' is different from Float's implementation of '+', which is different from Currency's implementation of '+'.

I can immediately see other examples where this might happen: Networking, where you are using the wrong implementation of 'socket', but don't know it, for example.

An (ugly) solution would be have explicit method names:

currencyResult := (myCurrency addMoney: otherValue) divideByMoney: divisionValue.

This is why typing is, in at least some respects, useful, and type safety really is not an oxymoron!

Paul said...

Hi Steve,

You've convinced me! There definately is a problem here that tests do not address and types do.

Paul said...

Hi Steve,

Maybe I conceeded prematurely. I must admit I'm at your disadvantage because I know nothing about finance. But I had a little chat with the guys in the office and they raised the point I made earlier. A Number is a Number is a Number :^)

The representation of floating point numbers are defined by IEEE and provides the maximum precision available by the hardware. So Currency is just for presentation.

So you should use the highest precision representation for numbers (which Number should support) you can for all your calculations then perform a conversion (using asCurrency) at the last minute when you need to present your result.

Is this true?

steve said...

"The representation of floating point numbers are defined by IEEE and provides the maximum precision available by the hardware. So Currency is just for presentation."

This is very wrong, and it is scary that someone thinks that!

The whole point of Currency handling is that it is done with guaranteed accuracy. In many programming languages currency handling is handled by actually storing the text digits of the number (such as in BCD format). In others, it is stored as a precise integer, with the decimal point position marked (this is the way Java does it).

The point is that if you store things as floating point numbers, you always get rounding issues - no matter what the precision, they are almost always inexact when handing decimal values. And that is unacceptable for financial use.

Here is an aricle from IBM explaining what I mean:
http://www-128.ibm.com/developerworks/java/library/j-jtp0114/"

"Floating point numbers are not exact, and manipulating them will result in rounding errors. As a result, it is a bad idea to use floating point to try to represent exact quantities like monetary amounts. Using floating point for dollars-and-cents calculations is a recipe for disaster."

Currency is NOT just for presentation, and anyone who thinks that should not be allowed near the code for financial software. You need to politely reprimand your colleages :)

However, you are concentrating too much on the number example. As I explained in the previous post, this is indeed a general problem. I gave the example of network sockets - if given the wrong class of Socket or Stream, then a call of 'flush' might not work as you think - it might pass the test, but not have the desired behaviour, just like in the Number example.

steve said...

Sorry - your blog software truncated the URL for the link: it is:

http://www-128.ibm.com/developerworks/java/
library/j-jtp0114/

Paul said...

Hi Steve,

I think the issue occurs when you want to guarantee a specific implementation of an Interface. So for streams, sockets, numbers etc, the different types of implementations share a common interface like you say, but have different internals.

Paradoxically one of the things Strongtalk does is to create a Protocol (Interface), for every class automatically, because of the desire to seperate Type (Interface) from Implementation (Class). The Annotations it uses are the Protocols so any class that implements the protocol stated would pass the type check. So if you got Real to Implement Currency then Strongtalk would accept Reals.

The Class is not a Type argument holds water for me. I think a better way in Java would be to use a Marker Interface so:

class Currency extends BigDecimal implements ICurrency.

That way BigDecimal becomes just one way to implement arbitrary precision maths.

Then you could do:

boolean checkTx(ICurrency tax)

And not be dependent on BigDecimal, but be sure of the behaviour implied by the marker ICurrency.

Lessons learned for me:

1) You cannot test what you can't anticipate.
2) There are errors that can be missed with tests and are detected by manifest types.
3) If I want to depend on a specific implementation of a Type, then write a test to check for it.

I think if I obey lesson 3, then I should still be safe using Ruby, Smalltalk etc. But you have highlighted a vunerability that I hadn't anticipated before.

Cheers.

steve said...

It has been a very useful discussion - one of the most interesting I have had for a long time, and I really appreciate your contribution.

I am beginning to see the advantages of the StrongTalk approach - I can't yet see any flaws (at least in terms of safety) in the idea of combining StrongTalk typing with testing.

You are right - approach 3 is a good one.

But bear in mind that Ruby does have that vunerability of the ability to change methods and classes on-the-fly. Remember on TSS I mentioned how a major Ruby developer had come across problems that led him to doubt the stability of Ruby for large projects? It was that issue. However, with a well-managed team, I would expect the approach you suggest would bring most of the advantages of Manifest Typing.

It has been a pleasure to debate with you.

Isaac Gouy said...

paul wrote in the blog entry ... a concise definition for type safety. I found it on the C2 wiki ...

Type safety is a term of art in language design and type theory - they don't talk about static type safety, or dynamic type safety, or various degrees of type safety, or ...

The C2 wiki goes on to reference the old Cardelli type safety quadrant, and if you actually read Cardelli's paper you'll discover that when he says type safety he means "ruling out all untrapped errors in all program runs".

When JVM throws a NullPointerException it is a demonstration of type safety, it is an example of the definition which you spurned as not definitive - "Type safety is the property that no primitive operation ever applies to values of the wrong type."


paul wrote in the blog entry So using the "degrees of type safety" argument, Java could be said to be more type safe then say Smalltalk. ... So type safety is relative.
Only if you ignore what the people who design programming languages and type systems say they mean by type safety.


paul wrote in the blog entry So how do I know that my program is Safe? Well simple, I test it!
No - that's how you find out if your program has errors.

type safety is about what happens when your program has a particular kind of error, not about whether it has a particular kind of error.

type checking is about detecting whether your program has a particular kind of error - a type error.

Isaac Gouy said...

paul wrote in the blog entry The downside of manifest typing is that all type systems use 'structural types'. Structural types are based on the structure of your code.

I don't think so.

The things that are "manifest" in manifest typing are the names of the types - hence nominal typing.

In contrast, structural typing is based on the structure of the types.

Paul said...

Hi Isaac,

Still throwing stones...:^) I'm still waiting to hear what you think? Is that so difficult or is your Ego better served picking holes?


Any way let me address your points..

"paul wrote in the blog entry So how do I know that my program is Safe? Well simple, I test it!
No - that's how you find out if your program has errors."

... Well since I write my tests before I write code, it is impossible to detect errors in code that doesn't yet exist. My test are an executable specification for the code to be.

"In contrast, structural typing is based on the structure of the types."

... So we are back to word games :^) Yes, and types have structure and behaviour. In the same way code has structure and behaviour. My point was that structural typing relies soley on the external structure of a type not the behaviour. Last time I looked all my Types passed as code :^).

Isaac Gouy said...

paul wrote Still throwing stones...
Why would I throw stones - you've already broken the glass.

Why do you continue to claim that caml is a "pure functional language" - do you have a problem admitting and correcting factual errors?


paul wrote Any way let me address your points..
... Well since I write my tests before I write code, it is impossible to detect errors in code that doesn't yet exist. My test are an executable specification for the code to be.

And that still has nothing to do with type safety (and that was my original point).


Is the "Handbook of Computer Science and Engineering" definitive enough for you?
In Chapter 103, Cardelli helpfully defines:
Safe language: A language where no untrapped errors can occur.
Type: A collection of values. An estimate of the collection of values that a program fragment can assume during program execution.
Type safety: The property stating that programs do not cause untrapped errors.
Typing error: An error reported by a typechecker to warn against possible execution errors.
Untrapped error: An execution error that does not immediately result in a fault.
etc

paul wrote ... So we are back to word games :^)
As much of your blog entry is about the meaning of type safe, and your "goal is to communicate and exchange ideas" it's surprising to me that you characterize the distinction between nominal typing and structural typing as a word game.


paul wrote Yes, and types have structure and behaviour. In the same way code has structure and behaviour. My point was that structural typing relies soley on the external structure of a type not the behaviour.
In Java, create classes A and B subclasses of Object - they have the same structure - does Java accept instances of A and B as the same type? (Nominal type system.)

Paul said...

Hi Isaac,

Java, create classes A and B subclasses of Object - they have the same structure - does Java accept instances of A and B as the same type? (Nominal type system.

Yes, this was the point raised by Steve Zara, made after my original post, which I have conceeded to be true.

This is the purpose of producive debate where people discuss what they think and exchange ideas.

You use the word fact as if there is this great reference book in the sky defining all known facts. The truth is there isn't. What there is are opinions, and ideas, some published some not.

You would gain a lot more credibility in my eyes if you could actually state your own opinion and why you believe what you do!

Paul said...

Hi Isaac,

Type safety: The property stating that programs do not cause untrapped errors.

Yes, but we have broken this down to where the error is trapped, either statically (compile time) or dynamically (runtime). We have also discussed the advantages/disadvantages of trapping errors at compile time versus runtime.

Having trapped an error, our user is still without a correctly operating program as originally specified. This is what I mean by program safety.

So type safety does not infer program safety as I have defind it. This is where tests, my executable specification step in.

Do you disagree?

Paul said...

Hi Isaac,

Coming back to your definitive factual definitions:

Type safety: The property stating that programs do not cause untrapped errors.

So If there is an error in my logic in my code resulting in incorrect behaviour, and hence an error, then my Type Safe language will detect it will it?

This is where you've got to put down the reference manual and start thinking for yourself :^).

Paul said...

Hi Isaac,

Java, create classes A and B subclasses of Object - they have the same structure - does Java accept instances of A and B as the same type? (Nominal type system.

Yes, so lets compare this to your definition of type safety:

Type safety: The property stating that programs do not cause untrapped errors.

So by your definition a dynamically type safe language like Smalltalk is correct in accepting A as equivalent to B. So are A and B equivalent or aren't they? This is why we have distinguished between static type safety and dynamic type safety.

Isaac, you are not thinking. Too busy trying to score points and boost your Ego. If you can't do better I won't be responding to your comments in feature. Your input lacks Quality and does not add Value to the debate.

Paul said...

Hi Isaac,

Java, create classes A and B subclasses of Object - they have the same structure - does Java accept instances of A and B as the same type? (Nominal type system.

Well we have already covered this earlier in the debate, if you had taken the time to read you would know. But let's cover it again for your benefit.

Firstly a Class is not a Type. In Java Class A and Class B could be the same Type and share a common Interface. If you specifiy an Interfcae say InterfaceC which is implemented by both classes then A and B can be equivalent and interchanged where InterfaceC is specified as the required type. In other situations where a specific implementation, Class A or Class B is specified they can't.

I Said:

So are A and B equivalent or aren't they?


Well Sometimes you do want equivalence and sometimes you don't. Duck typing assumes equivalence if the two classes are structurally the same type. Strongtalk goes one better by defining a Type (Protocol) per class automatically. So Type A and Type B become true seperate types in the Type system. In Strongtalk Class A implements Type A automatically, but can also be made to implement Type B too, making Class A an implementation of Type A and of Type B. The same could be done with Class B also.

BTW. What is a nominal type system? (This is a chance to redeem yourself :^))

Paul said...

Hi Isaac,

I guess I'm picking on you, but you're just too easy a target. Funny that the moment you present original thought of your own (your Java Class A and B example), it's ripped to threads. Even your qoutes of other peoples ideas and opinions have been ripped to threads too.

I don't mind educating you, but it would be so much easier if you showed a little humility. It is this lack of humility and arrogant tone that gets to me!

I see it in programmers all the time, and I believe it to be a significant factor in the general poor quality of Software. The people writing the Software can't admit to themselves that maybe they don't know, and perhaps they should ask questions and listen, and perhaps they should write a test to be sure and perhaps by running the test they may learn something.

Please dwell on my words before responding. Humility is important for all of use, including you!

Paul said...

Hi Isaac,

Why do you continue to claim that caml is a "pure functional language" - do you have a problem admitting and correcting factual errors?.

I mentioned Caml once as an example to distinguish imperative programming from functional programming with respect to program correctness. Caml is the only functional language I've tried. (I don't see Common Lisp as a purely functional language).

TheCaml tutorial I used said that it supported a pure functional style, does this make it an example of a pure functional language? I'm not sure, and if I stated this in error thanks for the correction.

But you haven't corrected me tough have you? You could have stated an example of a pure functional language and why Caml doesn't qualify, but you haven't. You could have even responded to the substance of my comment and overlooked this erroneous example which I used to make a broader point, but again you didn't. So I am saying rather than throw stones, question your own motives, and the Quality of your discourse and what if anything you are contributing!

Isaac Gouy said...

paul wrote in comments Caml is the only functional language I've tried. ... does this make it an example of a pure functional language? I'm not sure, and if I stated this in error thanks for the correction.

You contrasted "an imperative program" with "pure functional languages like caml (no side effects ..." when simply reading "What is Caml?" would tell you that Caml supports imperative programming, this is an egregious mistake.

Isaac Gouy said...

paul wrote in comments I don't mind educating you, but it would be so much easier if you showed a little humility. It is this lack of humility and arrogant tone that gets to me!

Those comments presume the other person lacks knowledge and presume you to be the knowledgeable teacher who will educate them.

Those are not the comments of someone whose "goal is to communicate and exchange ideas".

I suggest you imagine I had addressed those comments to you.

Isaac Gouy said...

paul made a sequence of ad hominem remarks
"This is where you've got to put down the reference manual and start thinking for yourself"
...
"Isaac, you are not thinking. Too busy trying to score points and boost your Ego. If you can't do better I won't be responding to your comments in feature. Your input lacks Quality and does not add Value to the debate"
...

Those are not the comments of someone whose "goal is to communicate and exchange ideas".

I suggest you imagine I had addressed those comments to you.

Isaac Gouy said...

paul wrote in comments So type safety does not infer program safety as I have defind it.
As I've already said:
type safety is about what happens when your program has a particular kind of error, not about whether it has a particular kind of error.

afaict what you mean by program safety is no different from what everyone else calls program correctness, and it has nothing to do with type safety.


paul wrote in comments So If there is an error in my logic in my code resulting in incorrect behaviour, and hence an error, then my Type Safe language will detect it will it?
If your logic error causes an error forbidden in the language and the language is safe then the forbidden error will be detected. That is not the same as detecting your logic error.


paul wrote in comments So by your definition a dynamically type safe language like Smalltalk is correct in accepting A as equivalent to B. So are A and B equivalent or aren't they? This is why we have distinguished between static type safety and dynamic type safety.
Neither Krishnamurthi's nor Cardelli's definitions of type safety have anything to say about whether a particular type safe language should accept instances of A and B as the same type.

Type compatibility and equivalence are orthogonal properties to type safety.

And I haven't confused type safety with static type checking and dynamic type checking, even if you have.


paul wrote in comments Firstly a Class is not a Type. In Java Class A and Class B could be ...
You have already conceeded this point - the type system is based on the name of types not the structure of types.

Paul said...

Hi Isaac,

Thanks for the constructive tone. My tone will be as equally constructive.

..Yes, my central point is with regards to program correctness. Type safety is a poor cousin to program correctness IMO, hence the title of my post (Is Type Safety and Oxymoron?).

..I think you missed the point about a Class not being a Type. Briefly, a Class is a concrete implementation of an Interface Contract. That same contract could be implemented several ways. I see the interface contract as the Type, not a given concrete Implementation. Duck typing and early bound manifestly typed languages like Java check and enforce interface contracts (Types) differently, with diferent assumptions and different consequences. IMO the Java type system is restrictive and leads to excessive coupling. Steve Zara was arguing that duck typing is too loose. I have described the Strongtalk Type System as an example where I believe you can gain the best of both approaches.

Isaac Gouy said...

paul wrote in comments ..Yes, my central point is with regards to program correctness. Type safety is a poor cousin to program correctness IMO

Once again, type safety is not related to program correctness.

Let's try a simple analogy - passenger safety in cars - safety from bad stuff happening when something goes wrong. Seatbelts are a way to increase passenger safety, and air-bags are a way to increase passenger safety.

Neither wearing a seatbelt nor having an air-bag decrease the risk that something might go wrong. They both decrease the risk of bad stuff happening when something does go wrong.

Type safety does not decrease the risk that something might go wrong - type safety decreases the risk of bad stuff happening when something does go wrong.


paul wrote in comments Type safety is a poor cousin to program correctness IMO, hence the title of my post (Is Type Safety and Oxymoron?).

In the blog entry you wrote "No program is Type Safe, and to claim so is a bit of an oxymoron" - that is an example of confusing type safety with program correctness. Any program written in a type safe language is type safe - that does not mean the program does not have errors, it means there's a limit to the damage those errors can do.

Being confused about what type safety means does not make type safety an oxymoron.

Isaac Gouy said...

paul wrote in comments ..I think you missed the point about a Class not being a Type. Briefly, a Class is a ...

My specific question was "does Java accept instances of A and B as the same type?" and you replied "Yes, this was the point raised by Steve Zara, made after my original post, which I have conceeded to be true."


It's been nearly 2 decades since William Cook wrote Inheritance is not subtyping.

"The Theory of Classification" is a series of articles on object-oriented type theory, aimed specifically at non-theoreticians.

Isaac Gouy said...

paul wrote in comments Thanks for the constructive tone. My tone will be as equally constructive.

paul previously wrote in comments I guess I'm picking on you, but you're just too easy a target. Funny that the moment you present original thought of your own (your Java Class A and B example), it's ripped to threads. Even your qoutes of other peoples ideas and opinions have been ripped to threads too.

Those are malicious remarks.

Please show how the example of Java not using structural typing has been "ripped to threads".

Please show how the "quotes of other peoples ideas and opinions have been ripped to threads".

Or retract those malicious remarks.

Paul said...

...I agree that Type safety does not infer program correctness. I have bloggd as such. Stee Zara has provide an example of where type safety 'assists' with program correctness. I have conceeded this to be true, but I see it as more of a side effect of the confusion of Classes with Types in languages like Java. I have offered Strongtalks Manfiest Type System as an example of a conceptually sound alternative.

... I'll just state this simple point once again: A Class (implementation) is not a Type (Interface). No number of quotes will persuade me otherwise.

... By your own definition type safety does not exist. It is a conceptual idea that breaks down when it comes to creating a practical language implemention. As stated in my blog all common programming languages are partially type safe.

... I have no reason to take back my comments, since despite several attempts you still fail to grasp the basis of the argument (which incidently as already been debated and concluded!). Either state in your own words why you disagree with what I have said above, or give up.

Isaac Gouy said...

paul wrote in comments ... I'll just state this simple point once again: A Class (implementation) is not a Type (Interface). No number of quotes will persuade me otherwise.
Please show where I said a class was the same as a type.

For the entire length of your programming career the reasons why a class is not a type have been well understood.

As you might have guessed from the title, William Cook's paper (the "quotes"?) explains why a class is not a type.

Isaac Gouy said...

paul wrote previously in comments Ron Jefferies has created a series of 'bowling' example programs ... All versions of TDD bowling are safe, which dispells the myth that type safety correlates with program safety.

You are mistaken testing was insufficient - some programs were not correct.

Paul said...

Hi Isaac,

Well spotted. Interestingly it was the Haskell recursive version that had the flaw. Unlike the imperative language implementations, I guess that this was the only version that could have been proven correct or incorrect mathematically (not sure need to look at it in detail).

... I want to rephrase your comment. It wasn't testing that proved insufficient, but the tester. It was a test in the Ruby version by another tester, that revealled the flaw in the Haskell recursive code.

...This is a very important point. The most important contributing factor to program safety is the programmer. Tests in themselves are no guarantee. What is important is the person doing the testing, and the skills and experience that can be brought to bare.

This is why Agile development values people over tools. Even the best tester can have a bad day, which is why team work is beter than relying on any single individual.

So in this instance Tests + Common Code Ownership detected the flaw. It was through sharing the code on the web that Dan and Ron, discovered the bug with the help of others.

After all this talk of type safety, and program corectness - we often tend to forget these human factors. Program safety as I have defined it, IMO is best served by a set of values and principles observed by a team of people and supported by the tools. The values, principles, and people are the critical factors, tools are secondary.

Paul said...

Hi Isaac,
Read a bit more about the Haskell recursive bug. What I found most interesting was Ron's open and honest nature. His willingness to share his code, and accept criticism and input from others. This comes from his values I believe. I have debated with Ron on several Agile forums and this comes across. So despite all his experience, he is willing to start from a postion of not knowing. He is happy to say I haven't got all the answers.

This attitude can result in a culture where others feel safe in sharing their uncertainty too. Software development is hard, and pretending that we have all the answers doesn't help. Being able to say, I don't know, and I could do with some help here is a great bonus. Unfortunately, amongst many in the software industry, this type of openess is a sign of weakness leaving you open to attack, but I see it as a sign of strength.

This is why I was so peeved, by your intital tone, and in case you haven't noticed, my starting place is I don't know .

Isaac Gouy said...

paul wrote in comments I want to rephrase your comment. It wasn't testing that proved insufficient, but the tester.

You are mistaken.

You seem not to understand the most basic truth of testing - "Program testing can be used to show the presence of bugs, but never to show their absence!" E.W.Dijkstra, 1972.

Isaac Gouy said...

paul wrote in comments "This is why I was so peeved, by your intital tone, and in case you haven't noticed, my starting place is I don't know."

Perhaps you do believe that your "starting place is I don't know" but any honest scrutiny of your comments on this blog would overturn that belief.


paul wrote previously in comments ... I have no reason to take back my comments, since despite several attempts you still fail to grasp the basis of the argument ...

Please show how the example of Java not using structural typing has been "ripped to threads".

Please show how the "quotes of other peoples ideas and opinions have been ripped to threads".

sth said...

Why would you want to write tests for something (type safety) that the compiler can check for you?

If the compiler (even partially) makes sure the correct types are used, you don't need to test these cases manually. So it makes life easier for the programmer and of course it's nice to have.

Btw: Haskell (http://www.haskell.org/) is a statically typed language and there the compiler can catch a lot of errors you would otherwise need to test for.