Moderators: phlip, Moderators General, Prelates
torne wrote:Yakk wrote:On the other hand, I write reasonably complex programs that are designed to fail if I screw up the static type checking.
When programming in a dynamicly typed language, I'm forced to manually verify all of the things I would staticly check using language constructs.
I've never written code to verify things that a typing system could've verified for me; what would be the point? If those things needed verifying, I would've used types. Unit tests pass, thus all statically checkable problems must have already been eliminated. I write programs that don't pass their test suite if I've made any error, statically checkable or otherwise. Seems easier. :)
Yakk wrote:torne wrote:Yakk wrote:On the other hand, I write reasonably complex programs that are designed to fail if I screw up the static type checking.
When programming in a dynamicly typed language, I'm forced to manually verify all of the things I would staticly check using language constructs.
I've never written code to verify things that a typing system could've verified for me; what would be the point? If those things needed verifying, I would've used types. Unit tests pass, thus all statically checkable problems must have already been eliminated. I write programs that don't pass their test suite if I've made any error, statically checkable or otherwise. Seems easier.
This results in more complex unit tests that need to be kept in sync with the interface documentation of the procedure. The static type restrictions give you a language-understood interface specification, and a language-enforced compile-time test of both calling and called code.
Given infinite effort and care, all you need are bits. Programming constructs more advanced than pushing bits are still useful, because they reduce the effort and care required to write correct programs.
And you mean to tell my you write perfect test suites? You are joking, right?
Rysto wrote:What kind of toy programs are you writing?
Yakk wrote:This results in more complex unit tests that need to be kept in sync with the interface documentation of the procedure. The static type restrictions give you a language-understood interface specification, and a language-enforced compile-time test of both calling and called code.
Given infinite effort and care, all you need are bits. Programming constructs more advanced than pushing bits are still useful, because they reduce the effort and care required to write correct programs.
And you mean to tell my you write perfect test suites? You are joking, right?
torne wrote:And you mean to tell my you write perfect test suites? You are joking, right?
If the test is wrong, the test fails, and I'll notice and fix the test.
torne wrote:Rysto wrote:What kind of toy programs are you writing?
I'm mostly a hard-realtime embedded kernel developer, though I work on a lot of other stuff too in my own time. The kernel's public APIs are documented in great detail (as they are for the customer to use, who may not have the source) - internal APIs should be self-explanatory from the code. Writing any documentation for them is at best a waste of time, and at worst a barrier to future maintenance.
davean wrote:What about the ones that pass when they shouldn't?
If you want to get it right, you need a proof of your code.
Personally, I'd prefer to see formal methods applied to any kernel stuff instead of just fallible unit tests. Of course, testing every combination of branches if a good start, but I seriously doubt you actually test EVERY combination. And yes, an if statement that has multiple conditions, each one is separate.
EvanED wrote:Really? So you have function names like DoSomething_mustBeCalledWithIOLockHeldAtIRQLPassiveMightSleep(void* nonnullThingy)?
tendays wrote:As soon as you have a moderately expressive type system, type checking becomes undecidable. So a type checker will either be sound but incomplete (anything it accepts is guaranteed to work but it will reject perfectly valid programs) or complete but unsound (if it rejects something then it's guaranteed to be bad - things that go through might fail at runtime). (Also, some are neither sound nor complete, obviously)
tendays wrote:OTOH I am currently coding something (amusingly, it is a type system checker) in Java 1.4, which does not have generics, and every line I write makes me regret not having done it in caml or something. E.g. I have huge Sets and Maps and it already happened once that I put an object of a wrong type in there so debugging was uncool. Not to mention the map() or fold() methods that I had to emulate by hand ...
tendays wrote:Not to mention the map() or fold() methods that I had to emulate by hand ...
Rysto wrote:tendays wrote:Not to mention the map() or fold() methods that I had to emulate by hand ...
There shouldn't be any problem implementing a version of map() in Java. I can't think of any way to implement a clean version of fold(), though. However, it sounds like you're trying to program in a functional style. That's not going to work out for you in Java.
import java.util.ArrayList;
import java.util.Iterator;
//how many lines of code for map/reduce? WAY TOO MANY
public interface Functionalable<T> {
Iterator<T> iterator();
T reduce(T t1, T t2);
boolean filter(T t);
}
public class Func {
public <T, S extends Functionalable<T>> T reduce(S s) {
T t;
Iterator<T> i = s.iterator();
//get first
if (i.hasNext())
t = i.next();
else
return null;
//reduce
while (i.hasNext())
t = s.reduce(t, i.next());
return t;
}
public <T, S extends Functionalable<T>> ArrayList<T> filter(S s) {
ArrayList<T> tt = new ArrayList<T>();
Iterator<T> i = s.iterator();
T t;
//filter into array list
while (i.hasNext()) {
t = i.next();
if (s.filter(t))
tt.add(t);
}
return tt;
}
}
public class FuncTest implements Functionalable<Integer> {
ArrayList<Integer> ii = new ArrayList<Integer>();
public FuncTest(int... list) {
for (int i = 0; i < list.length; i++)
ii.add(list[i]);
}
public boolean filter(Integer t) {
return (t >= 10 && t <= 20);
}
public Iterator<Integer> iterator() {
return ii.iterator();
}
public Integer reduce(Integer t1, Integer t2) {
return t1 + t2;
}
}
public interface MapFunction<D, R> {
public R apply(D x);
}
public static <D, R> List<R> map(MapFunction<D, R> f, List<D> list) {
List<R> out = new ArrayList<R>(list.length()); //ok, I'll admit this isn't optimal
for(D x : list) {
out.add(f.apply(x));
}
return out;
}
public interface FoldFunction<T> {
public T apply(T x, T y);
}
public static <T> T foldl(FoldFunction<T> f, T init, Iterable<T> list) {
for(T x : list)
init = f.apply(init, x);
return init;
}
public static <T> T foldr(FoldFunction<T> f, T init, Iterable<T> list) {
foldr_helper(f, init, list.iterator());
}
private static <T> T foldr_helper(FoldFunction<T> f, T init, Iterator<T> it) {
if(!it.hasNext())
return init;
return f.apply(it.next(), foldr_helper(f, init, it)); //are arguments guaranteed to be evaluated left-to-right in Java?
}tendays wrote:As soon as you have a moderately expressive type system, type checking becomes undecidable. So a type checker will either be sound but incomplete (anything it accepts is guaranteed to work but it will reject perfectly valid programs) or complete but unsound (if it rejects something then it's guaranteed to be bad - things that go through might fail at runtime). (Also, some are neither sound nor complete, obviously).
public static List map(List l,Func f) {
List a = new ArrayList();
for (Iterator i = l.iterator();i.hasNext();)
a.add(f.f(i.next()));
return a;
}Tools.map(local,new Func() {
public Object f(Object o) {
return ((DepSt)o).clean();
}});local.map( o -> o.clean() )cdfh wrote:I sometimes find Haskell is a bit too strict, however. For example, maybe I have a list of $things, where I have no idea what type each item will be. Maybe they are all objects I would like to kill (and eat).
cdfh wrote:The thing is, of course, what if +item+ doesn't respond to kill()? That would be annoying, but we could tell much earlier, when push()ing things onto the stack, that they would cause problems.
Could we not have a nice syntax for a run-time type-checker (do we already?)?
list.elements_obey(:kill, :eat, :sleep)
cdfh wrote:But of course, some of this could be done at compile-time, like in Haskell - although, it would be much harder to do in a non-static, non-functional language.
invariant x.responds_to?('kill')cdfh wrote:(note: this should not create a halting problem, since we're not running the code - we're just looking at the code)
...
if foo() then x.delete("bar") else x+=1;
(with an argument which is a string - we would trace the argument for the delete function to check that a string would not violate any of its typing)
cdfh wrote:Realistically, I don't know how useful this would be. I think the compiler could gather a lot of information about the possible state of all the variables at different places in the code, but it would require a fair time to compute (probably an unreasonable length of time for general purpose ruby programs - it would probably want to safe the compiled code).
Yakk wrote:fortyseventeen, I didn't know that runtime typed languages had faster runtime performance!
Yakk wrote:A dynamically typed language is basically a language that disallows static types. And I find that annoying.
Explain please. It's well-known that dynamically typed languages generally improve programmer efficiency at the cost of speed and memory.
If you're referring to the fact that all type errors can be found at once during compilation, rather than one at a time during interpretation, I don't see that as much of an advantage. CS instructors and coder friends of mine all agree that it is best to fix one bug at a time, since errors can cascade very easily, and bugfixes can very easily make bugs.
Although that's true of languages that call themselves "dynamically typed", that's not necessarily true of all lanugages that contain dynamic typing constructs.
It's concievable that one could write a Java library that declares an interface for every method, uses nothing but Objects as method arguments, and uses interface casts in order to aceess their methods. This would be a fairly verbose, but complete, duck-typing system. You could then begin to mix dynamic and static elements together, since the language natively supports static typing.
Personally, I would find such a system overly complicated, but it's one possible way of combining the two paradigms, out of the many ways that already exist.
I would prefer a new language that started from the assumption that dynamic typing is desired, but that allows types to be explicitly specified. Still, what reasons would one have to use such a language?
Yakk wrote:I do not find that dynamically typed languages generally improve programmer efficiency.
Dynamically typed languages do make it easier to toss out a short program by an unskilled programmer. Programs of any reasonable size (say, that take more than 1 programmer-year to build) are aided by static type contracts.
Scripting languages can improve programmer efficiency at the cost of speed and memory and precision of instruction.
Many scripting languages are dynamically typed: ie, they lack static type-checking facilities. This is unfortunate, and a flaw in the language in question.
Type errors and contracts can be found and enforced staticly, thus making simpler tests and more reliable runtime execution.
I am not aware of a serious language that lacks dynamic typing constructs. There are, effectively, no serious "pure static typed languages".
...
Yes. You can do this in C++, you can do this in C, you can do this in every "staticly typed" language I can remember programming in.
You cannot, meanwhile, use static type contracts in perl, python or (as far as I know) ruby.
I suppose the difference is, in a "staticly typed language", it presumes wanting to bypass the type system is an exception, not a rule. As such, it makes the syntax required non-trivial.
I would prefer a language with a better ability to describe and generate static types.
It is a ridiculously rare event that someone actually wants a container that holds anything. It is almost always the case that the thing that goes into the container, and the thing that comes out, is actually a specific kind of thing.
fortyseventeen wrote:Yakk wrote:I do not find that dynamically typed languages generally improve programmer efficiency.
Dynamically typed languages do make it easier to toss out a short program by an unskilled programmer. Programs of any reasonable size (say, that take more than 1 programmer-year to build) are aided by static type contracts.
Have you built many large projects in scripting languages? I find that cases for code reuse are encountered far more frequently than cases for type contracts. Any project can be broken up into indiviually verifiable pieces, as small as needed to ensure reliability and compatibility. If anything, scripting languages are far more scalable, even though the community around them (notably Perl) may give them a bad name.
Scripting languages can improve programmer efficiency at the cost of speed and memory and precision of instruction.
Very true, and this is why C and C++ are called "systems" langauges. Where and when preciseness is critical, they should be used.
Many scripting languages are dynamically typed: ie, they lack static type-checking facilities. This is unfortunate, and a flaw in the language in question.
I'm sorry that you think of it as a flaw. It's simply an alternate paradigm, with its own uses. Maybe you are not clear on its pros/cons.
Type errors and contracts can be found and enforced staticly, thus making simpler tests and more reliable runtime execution.
Static and dynamic typing are not the same as type safety, or strong typing, which is to what I think you are referring to when you mean that it improves testability and reliability.
I am not aware of a serious language that lacks dynamic typing constructs. There are, effectively, no serious "pure static typed languages".
...
Yes. You can do this in C++, you can do this in C, you can do this in every "staticly typed" language I can remember programming in.
I suppose that if you wanted to write a dynamic typing library for C, this would be true, but dynamic languages are already written (most of them in C) for this purpose. I would still argue, though, that C's types are in fact completely static. It's the weakness of C types that would allow any possible dynamic functionality. (In other words, it fakes its way around.)
You cannot, meanwhile, use static type contracts in perl, python or (as far as I know) ruby.
Correct. I have personally found no reason for there to be such contracts in said scripting languages, though, outside of what we have already discussed (speed, reliability, etc.). It's a simple trade-off, with no lack of functionality, which is why there are different languages across the perpendicular spectrums of type dynamics and safety.
I would prefer a language with a better ability to describe and generate static types. :)
I'm ignorant of any such advances in current static languages (they seem to be doing pretty well already), but I know that Ruby 2.0 will feature a metacomplier and runtime machine, so that cases in which object (duck) type can be predetermined, will be. e.g. if an object is known not to contain a quack method at the time that it is sent the 'quack' message, the compiler will be able to report this before execution begins. Again, this is only an issue of efficiency.
Yakk wrote:However, the inability to express type requirements does reduce the ability of the language to express some concepts. I'm all for a language that can dynamically type everything: but the fact that such languages do not provide the option to enforce types is annoying. It is an entire category of useful and pithy automatic code generation that is tossed out with the bathwater.
The only argument I have been trying to make (which I believe is shared by the other proponents of dynamic typing) is that given that a tradeoff must be made, I would rather lose the ability to conveniently enforce static typing contracts than the ability to ducktype freely without inserting casts, extra interfaces, etc all over the place. The reason I choose to make the tradeoff this way is because in my experience of programming operating systems, compilers, web applications, network servers, embedded graphics engines, and deviant sexual toys, it has universally been the case that very few of my programming errors have ever been caught by the use of static typing contracts. Thus, I choose dynamic typing because I find it quicker to code that way - if nothing else, there are simply less keystrokes involved Smile
evilbeanfiend wrote:The only argument I have been trying to make (which I believe is shared by the other proponents of dynamic typing) is that given that a tradeoff must be made, I would rather lose the ability to conveniently enforce static typing contracts than the ability to ducktype freely without inserting casts, extra interfaces, etc all over the place. The reason I choose to make the tradeoff this way is because in my experience of programming operating systems, compilers, web applications, network servers, embedded graphics engines, and deviant sexual toys, it has universally been the case that very few of my programming errors have ever been caught by the use of static typing contracts. Thus, I choose dynamic typing because I find it quicker to code that way - if nothing else, there are simply less keystrokes involved Smile
and the only argument i make is that you favour dynamic typing because of the areas you work in. if you wrote critical software for hospital equipment you might insist on static typing (and possibly static logic analysis) to guarantee your code won't kill anyone. both are useful, for a specific job one may be more useful than another, but any discussion about one being better in general is pointless. (essentially i think we are all in heated agreement on this)
yy2bggggs wrote:By performing all of the typing dynamically, you are allowed to execute your code earlier. The disadvantage of doing this is that you could miss some of your contracts (by not executing them); thus, you could have a bug. But the ability to run your code without fulfilling all of the contracts in itself is a benefit; it allows you to test your code before you have all of your t's crossed and your i's dotted, so if your overall design doesn't work, you don't need to go further. The advantages are comparable to prototyping, only the prototype is your actual code.
yy2bggggs wrote:Rysto wrote:Wait, so now it's an advantage to find bugs later in the development cycle?
No.
Monad m => key -> coll key value -> m value(key -> value -> b -> b) -> coll key value -> b -> bUsers browsing this forum: Farpappestals and 2 guests