printf is not statically typed. That's the problem. What's the type of printf? it's int printf(const char *,...)
The "..." part means that the function has to figure out what the types of all the arguments are. printf accomplishes this by, at runtime, parsing the string format and deciding what types to use.
Contrast with a string representation function from a language like Haskell: show::(Show a) => a -> String
No matter what argument is provided to the function show, as long as it observes the type constraint (Show a), a sensible result will be returned. Since it is known at compile time whether the argument observes the class constraint, there's never a surprise that value is printed incorrectly.
It is also true when testing is included and pylint are used, which is the issue.
I can write a 500 line C# program and it works reliably first time with no crashes, type inference errors or framework exceptions thrown. The only errors will be algorithmic or functional (i.e. based on requirements).
Your tests should be picking the majority of your algorithmic and functional errors, so I'd have to assume you're talking about the state of the code before thorough testing.
What's it like after that testing? How's the long term maintainability? I'm much more interested in possible problems - and it could go either way - in the long term than before I've finished writing it.
I tend not to write oodles of test cases up front in favour of simple scenario based tests applied later on. I add pre/post/invariant-condition checks in the code as I write it. It is tested incrementally by hand.
The condition checks prevent the what if's and tell you why something broke. The scenario tests ensure that it does what is asked of it.
I rarely get bugs raised against my code (7 this year out of about 112,000 lines of c# written). Not bad!
It's been my experience that python (or any other similar language) is less prone to type errors when testing and pylint are used. I still prefer C++ and a strong type system when I do more critical work, but I can get by with the fast, more reckless languages too. There's a place for both.
This is why use strict in Perl is such a godsend because it catches all variable name typos at compile time.
Update: Just noticed you said type and not typo related! Still use strict does catch a lot of silly errors that unfortunately do slip into code with other dynamic languages.
No, I meant "type", exactly as I wrote it. I was not referring to situations where the programmer accidentally types a character or two incorrectly, without realizing it. I'm talking about cases where the programmer intentionally writes code in a certain way, but does not realize it violates a type constraint of some sort. Maybe he passes a string to a function that expects a numeric value. Or maybe he renames a method, but misses one invocation. You get the idea.
Please don't assume that what was written was wrong, especially when it wasn't.
I believe you, because that intuitively makes sense, but can anyone give me a typical real-world example where this could easily happen?
So an application, say a web application in Python using some framework, what kind of type-related coding mistakes could you make? And which ones would be subtle enough to only be caught after it's in the customer's hands? (assuming a reasonable minimum of testing before that)
I'm asking not out of disbelief, but just because I'm having a real hard time coming up with an example that makes me "yeah I'd fall for that and only realize until after I delivered".
Is it possible that maybe, just general good coding style in Python makes it so that either you don't even consider sending a `str` to a function that expects a `float`, or that if you do (something like that) it fails almost immediately with a run-time Exception, way before you think of delivering?
For most of my Python programs, once they're done, if you'd analyze them, the types sent over all code paths probably would be static :) I'd think it pretty strange if a program would suddenly call functions with different parameter types, depending on user-input, unless you actively decided that it should do so by design (like something that handles arbitrary JSON-like structures), in which case you probably intend to restrict the amount of types it will be intended to handle.
So in that sense, you end up with what is mostly a statically typed program in the end, even if written in a dynamic language, no?
One thing I do agree that if your program is written dynamically typed and intends to keep on being very dynamic about its typing even after it's done during run-time then, yes, it's probably almost impossible to make sure it does not crash or throw exceptions, given arbitrary user input. But in that case, often this is expected, and not the kind of program you'd consider an end-user application, but rather a sort of tool script thing that can indeed crash on unexpected input. Which is also useful :)
So, and correct me if I'm wrong, but I'd conclude that the difference is that with dynamic typing, there's less red tape during development. But a well written program intended for end users probably ends up with mostly static types de facto because otherwise it'd be fairly unpredictable. However, if you'd have used a statically typed language, you would not have to assume "well written" because the static typing brings guarantees with it.
Some advantages of static typing show up in programming in the small. Errors in forgetting to check for null/nil/None are a common class of errors that are caught by many static type systems. Switch/match statements that leave out possibilities without providing a default (especially if a new value is added to an enum) are also common errors that are caught by some type checkers.
One hidden (and seldom discussed) feature of static type systems is that in multi-team projects, they can enforce and document abstraction layers between teams. I've even seen static type systems act as impartial arbiters when an argument arises as to if a given change broke one side of an abstraction barrier, or if the change merely exposed a latent silent bug on the other side of the abstraction barrier.
You can use a function with different types in a way the original programmer didn't think about. Obviously this has good and bad aspects.
My "whoa" moment with python typing came when I implemented euclid's algorithm (for the gcd of two numbers). Shortly later, I wrote a polynomial class, and then I was able to use my original function on polynomials without changing a single character
In some statically typed languages that would be possible if I'd thought to write my original function generically. But at the time I hadn't even thought about calling it with anything other than integers.
Playing statically-typed advocate for a moment: in Haskell, you wouldn't have had to put a type down for the function, and the compiler would have inferred the type
gcd :: Num a => a -> a -> a
which means it is a function that takes 2 a's and returns an a, with the restriction that a is of typeclass[1] Num, that is numbers.
So, very much possible and assumed without thinking about it too much.
Small correction; with the most common implementation of Euclid's algorithm the compiler would infer
gcd :: Integral a => a -> a -> a
since writing Euclid's algorithm requires using `mod` or `rem`, both of which only work for integral types (NB. there are many integral types beside ints, for example polynomials, formal power series and gaussian integers).
The Integral class captures the mathematical idea of a Euclidean domain, which can loosely be thought of as "number systems for which the Euclidean algorithm works".
Well, pray that the library function is general enough.
Unfortunately, this is not the case for your gcd type,
as gcd can be generalized to arbitrary commutative rings.
Nevertheless, writing generic code is of course possible with static types.
Well... kind of. You can define a gcd operation on arbitrary commutative rings, but two elements of the ring don't necessarily have a unique gcd. If you want a unique gcd for any pair of elements, you need to specialize your commutative ring to a unique factorization domain.
If in addition you want to write your gcd function using the Euclidean algorithm, you need to specialize again to Euclidean domains (of which polynomials and power series are an example).
The `gcd` function in the Haskell base library operators on Integral types, which are the programmatic representation of Euclidean domains, so I would argue that it is "general enough".
That's pretty trivial to do in C++, which everybody knows is the least useful static language out there. You discovered operator overloading, not dynamic typing.
If the goal is to be able to invoke a method on an object without checking for anything other than whether a method with the correct signature exists, that isn't a feature that's supplied by dynamic typing. It's supplied by duck typing, which is yet another axis (alongside dynamic/static and strong/loose). A popular example of a statically typed language that has had duck typing for a very long time is Objective-C.
Or "generics". That's what I was trying to say in my last line; you can do that, but only if you think to use them when you originally write the function.
The the core of it for me is you can almost always do x under a particular static type system. But you can almost always do the same thing under /any/ dynamic type system.
You can generate fieleds of ORM objects based on the values returned by a query. If you're writing a game, you can represent in-game objects directly with language objects and add more properties and methods when they're modified.
In short, you can cut down on abstraction layers when working with behaviors that are inherently runtime.
It's perfectly possible to have a dynamic type system with closed objects (can't add properties). Then again it's not really possible to have a static type system with open objects (at least, I can't think how you would).
There exist type systems that can handle "strong" (i.e. type-changing) updates, but the ones I know of will only permit it for unique (linear/affine) references. You might be able to take advantage of the guarantee that properties are not being removed, so the update just turns the object into a subtype of what it once was. Then other pieces of code with reference to that object can continue on type-safely, but they won't be able to use those new properties without somehow being informed about them (statically).
... of which type? Map<String,Object>? Then you have to use casts to convert Object to Integer for example.
If static typing would be good enough, you would not need casts. A cast basically says: "Hey compiler forget about type checking, just believe me that this thing has that type."
A dynamically typed language can still be strongly typed. For example, Python will give you an exception on a type error, where C/C++ could wreck havoc in your memory.
A hashtable doesn't have methods. In a dynamically typed language, I can generate object methods based on the data currently available in the database (either columns or even row content) and take advantage of all the language's syntactic features while doing that.
In respect to any language, the database is inherently dynamic, because it doesn't share your type system and the schema can be redefined at any point after compilation of your code. A statically typed language doesn't magically change that. The best you can do is to write some classes in advance and then hope nothing gets out of sync.
So, to put it in simpler terms, some things are inherently dynamic, and dynamic languages make working with them easier.
The part of LINQ you refer to works by running a code generator against database schema before writing any code. In other words, it is not a language feature (you can generate code in any language), it is highly complex and it is a maintenance liability. Generally, any kind of code generation is a crutch. Ability to dynamically modify objects removes the need for it.
The way to go in .NET is the opposite direction: Entity Framework with Code First approach.
And smart use of data structures, algebraic types and composition for the latter. The benefit of this is you build a sort of theory for your game, sometimes it predicts and suggests things that would not have been obvious to you otherwise.
Metaprogramming is prevalent in python/ruby, regardless of how confusing it is, because it is super useful. Being able to dynamically construct types and classes (e.g. via decorators in python) allows you to do tons of stuff that is otherwise really hard for library writers, even though you shouldn't be using metaprogramming all over the place day-to-day. Rails, SQLAlchemy and a ton of other libraries/frameworks use metaprogramming all over the place.
Static languages lack this ability, but because it's so useful, they end up re-implementing the same thing anyway but using reflection/bytecode-manipulation/extra-compile-hooks/code-generation/etc.. like in Spring/AspectJ/Play/ASP.NET!
Although the end result is kinda the same, I'd argue metaprogramming via generating java source code is way harder than metaprogramming in python, where creating a new class and setting its methods or descriptors is as easy as creating/fleshing out any other object. Given how powerful/useful metaprogramming is, I think that that's the biggest gain of dynamic over static languages.
That's possible because the language runs on a VM that contains metadata about your data structures, not because of the dynamic typing. If classes are implemented as a hash table of it's fields, then it's easy to add/remove members at runtime.
explanation -- MetaOCaml provides what is essentially a typed eval for Objective Caml. OCaml's first-class modules provide the ability to construct types, classes, and modules at run time.
In other words, statically typed languages do have this ability.
I find this type of introspection highly annoying in any language. If the lib/framework is poking around in your class internal data you loose control over your objects. The useful abstraction which an object provides is gone and it's reduced to a struct. As an example it is hard to dynamically generate the contents in Djangos Meta-class. Another example is that it's very hard to keep immutability and maintain invariants in Hibernate entities.
I'd like to say the following to the creators of similar frameworks: Just give me the interface and I will implement it. And keep your hands off my internals!
What makes Python's metaprogramming fundamentally different from the dynamic class generation that exists in statically typed languages such as C#? And what about it is facilitated by dynamic typing?
Part of why I suggest C# is that a lot of .NET libraries make heavy use of this functionality. Windows Communication Foundation, NHibernate, and Moq come to mind. So my impression had always been that it was approximately the same level of reflection, just with perhaps more syntactic sugar in one case.
Meta programming is much much easier in dynamically typed languages like ruby. It completely eliminates the use of interfaces, because you can call any method on any object. If you want to do it defensively, just check if the object has the method you want on it before calling it.
I think statically typed languages have a lot of ceremony around doing something, whereas dynamically typed languages just get out of your way.
Both can do the same (they are turing complete), but some things are harder to do in statically typed languages because of the underlying implementation, like reflection or eval().
Static typing is safer (more compile-time checks) but more verbose. Type errors are found by the compiler before the program runs.
With dynamic typing you write less, but requires better understanding of the underlying types so you don't get runtime errors. Type errors propagate through the program until it generates a runtime exception or wrong results.
>Static typing is safer (more compile-time checks) but more verbose
No, explicit typing is more verbose. Static typing does not mean java. Many statically typed languages have type inference, and have for a long time. They are no more verbose than dynamically typed languages.
Hindley-Milner like type systems can deduce the type of vars from how they are used. In the square example, using the "*" operation give the information to the type system that x is a number.
One useful distinction I make is between types and datatypes. Types are a mathematical theory in which you are able to prove things about the terms of you program.
Datatypes are like types but without the structure or logic. They don't prove anything and are basically just sets. You have to bring your own rules. Dynamic languages are unityped, you can see this by noting working only on just a top type shares many properties (not all, e.g. flexibility) of being in a dynamic language. Dynamic languages however can have datatypes, which make sure operations on the collections a particular object inhabits makes sense according to rules the language designer defines.
For your example, in a dynamic language I can state that r,a and b all belong to the same type but their datatypes may vary from moment to moment. In a statically typed language I cannot. Without types a ST language will assume that a and b are the same universal type. But I could give just a a top type and then your example would work. But I lose type safety. To get it back I could use interfaces, inheritance or algebraic datatypes each with their own pros and cons.
For those who don't realise, the challenge is to write a function `f` that takes three arguments, the first of which is a function which is applied to the second and third arguments and returned in a pair.
The difficulty with doing this in a statically typed system is that once you've fixed the type of the function you pass in, you can't then apply it to two different types. The standard way to solve this would be to specify that the function you pass in is valid for all types [1]
f :: (forall s. s -> s) -> a -> b -> (a,b) -- [2]
f r a b = (r a, r b)
f id 5 "Test" -- returns (5,"Test")
However, the challenge also specified that you do it without writing a type signature for `f`. I admit that in this case I am pretty stumped. The closest I can get is to first define a wrapper
data S = forall a. Show a => S a
instance Show S where Show (S a) = show a
and then write
f r a b = (r a, r b)
f id (S 5) (S "Test") -- returns (5,"Test")
but that involves wrapping everything up before passing it into the function, to disguise the fact that we really have different types.
There's an interesting point to be made here, though - "wrapping everything up in one type" is essentially what dynamic type systems do, except that it's baked into the language instead of being implemented by the programmer on an as-needed basis.
Here, my declaration for the type S says "any type as long as it has a Show instance" (which allows it to be printed). I could instead have specified "any type that has a Num instance" which would mean that I could pass in a mix of ints and floats, and still do arithmetic with them. I get some of the benefits of dynamic typing (flexibility) but I still get to keep type checking.
The Python approach is "any type, and we'll check at runtime that it has the appropriate methods". So it's ultimately flexible, but as a result loses any form of compile-time checking.
Looking at it this way, I think that the challenge you've set me is one that cuts to the core of the difference between dynamic and static type systems. No, I can't write the function you want in a statically typed language without doing something clever with types. But the clever thing I have to do with types is re-invent duck typing, but in a limited way which still gives me compile-time checking.
Anyhow, thanks for the insightful comment. It certainly made me think...
[1] This requires the language extensions ExistentialQuantification and Rank2Types, but neither of those are particularly controversial.
[2] Note that the type signature
forall a. a -> a
is inhabited by exactly one function - the identity function. This carries over to the Python example too - the function `f` isn't safe to use unless the
first argument is the identity function, which makes it a bit of a useless function to define in the first place.
>> There's an interesting point to be made here, though - "wrapping everything up in one type" is essentially what dynamic type systems do, except that it's baked into the language instead of being implemented by the programmer on an as-needed basis.
This is imprecise - a dynamic language does not really need to "wrap everything in one type" - look at Dart or Groovy. What makes it 'dynamic' is the dynamic dispatch. All dynamic languages I know dispatch based on runtime argument types, but this is only one particular form of dynamic dispatch. Declaring types in dynamic language typically is used at runtime merely as an assertion, that at this point the argument must conform to the said type.
Dynamic dispatch differs from Polymorphic dispatch in the way that polymorphic dispatch takes in account the runtime type of the target (the object whose method you are calling), but the types of the parameters are fixed at compile time.
Many dynamic languages also provide a way to customize the dispatch logic (i.e. call a default handler if no match exists) - typically via some form of Meta Object Protocol. Often the dynamic languages provide features like higher order functions, continuations, pattern matching, etc. but this has nothing to do with their dynamicity - same features are available in some static languages as well.
That's actually the only place ML^F ever requires type annotations - when you want to take a polymorphic function as argument and use it at different types. MLF is basically as strong as inference for polymorphic types gets, so it's not surprising annotations might be needed here too. (intersection types handle the example nicely, but I don't know of any languages using them).
If you want to faithfully translate the idea that 5 and "Test" are different values that can be passed into the same places but told apart by tests if you want, you might translate the second line to something like f (\x -> x) (Left 5) (Right "Test"), which does pass.
Lots of people don't realize that strong/weak typing is orthogonal to static/dynamic typing. Strong typing systems will throw an error if the wrong type is used. A weakly typed language will attempt to coerce or cast the variable.
Python is much more strongly typed than both Javascript and C.
Another dimension is the explicitness of your variable languages. This isn't quite orthogonal since static languages have to allow you to specify a type and dynamic languages have to have some way for you to not specify a type but many static languages allow you to infer a type and many dynamic languages allow you to specify types. (You can always use asserts if they don't explicitly allow it).
My point is that inferred types always have the potential to provide surprising results. It is unlikely in static languages, but it's possible. If you've defined "square x = x * x" it will allow you to square anything that has the splat operator defined for, even if that would be nonsensical. For that reason inferred static languages generally don't overload the splat operator, so it's a bad example. But I hope you still get my point.
And the mix can produce interesting results. For eg. in Python:
def double(x):
return x * 2
print double(2) # => 4
print double("X") # => XX
If you were expecting a number to be returned then this isn't good :(
The * operator isn't overloaded in Perl so instead we get:
sub double { $_[0] * 2 }
say double(2); # => 4
say double('X'); # => 0
Perl is more weakly typed than Python so 'X' get coerced to zero with the multiplication operation. So at least a number is returned but I would prefer a warning or error:
use warnings;
say double('X'); # produces runtime warning
use warnings FATAL => 'all';
say double('X'); # produces runtime exception
What you can do with your statically typed language obviously depends on the strength of the type system. Haskell for example is quite good, but Java is very limiting.
Luckily there is a workaround for Javas static typing limitation: Casts. Every time a Java programmer uses a cast, you can see that static typing has limits.
In a dynamic languages you can do dynamic dispatch - i.e. dispatching based on the actual runtime type of the arguments of a function, as opposed on the declared or statically inferred type. That among other things obviates the need for the visitor pattern and can also be used as rudimentary form of pattern matching.
The crucial moment in my understanding of dynamic languages was when I was learning about the Abstract Factory pattern by implementing it in PHP. After writing different factory classes and all the shenanigans I've realized that all of that can be replaced with a simple
72 comments
[ 947 ms ] story [ 1448 ms ] threadIn python you just print
The "..." part means that the function has to figure out what the types of all the arguments are. printf accomplishes this by, at runtime, parsing the string format and deciding what types to use.
Contrast with a string representation function from a language like Haskell: show::(Show a) => a -> String
No matter what argument is provided to the function show, as long as it observes the type constraint (Show a), a sensible result will be returned. Since it is known at compile time whether the argument observes the class constraint, there's never a surprise that value is printed incorrectly.
I can write a 500 line C# program and it works reliably first time with no crashes, type inference errors or framework exceptions thrown. The only errors will be algorithmic or functional (i.e. based on requirements).
The same is not true with python.
I have the same experience with both. Go figure.
What's it like after that testing? How's the long term maintainability? I'm much more interested in possible problems - and it could go either way - in the long term than before I've finished writing it.
I tend not to write oodles of test cases up front in favour of simple scenario based tests applied later on. I add pre/post/invariant-condition checks in the code as I write it. It is tested incrementally by hand.
The condition checks prevent the what if's and tell you why something broke. The scenario tests ensure that it does what is asked of it.
I rarely get bugs raised against my code (7 this year out of about 112,000 lines of c# written). Not bad!
Update: Just noticed you said type and not typo related! Still use strict does catch a lot of silly errors that unfortunately do slip into code with other dynamic languages.
Please don't assume that what was written was wrong, especially when it wasn't.
So an application, say a web application in Python using some framework, what kind of type-related coding mistakes could you make? And which ones would be subtle enough to only be caught after it's in the customer's hands? (assuming a reasonable minimum of testing before that)
I'm asking not out of disbelief, but just because I'm having a real hard time coming up with an example that makes me "yeah I'd fall for that and only realize until after I delivered".
Is it possible that maybe, just general good coding style in Python makes it so that either you don't even consider sending a `str` to a function that expects a `float`, or that if you do (something like that) it fails almost immediately with a run-time Exception, way before you think of delivering?
For most of my Python programs, once they're done, if you'd analyze them, the types sent over all code paths probably would be static :) I'd think it pretty strange if a program would suddenly call functions with different parameter types, depending on user-input, unless you actively decided that it should do so by design (like something that handles arbitrary JSON-like structures), in which case you probably intend to restrict the amount of types it will be intended to handle.
So in that sense, you end up with what is mostly a statically typed program in the end, even if written in a dynamic language, no?
One thing I do agree that if your program is written dynamically typed and intends to keep on being very dynamic about its typing even after it's done during run-time then, yes, it's probably almost impossible to make sure it does not crash or throw exceptions, given arbitrary user input. But in that case, often this is expected, and not the kind of program you'd consider an end-user application, but rather a sort of tool script thing that can indeed crash on unexpected input. Which is also useful :)
So, and correct me if I'm wrong, but I'd conclude that the difference is that with dynamic typing, there's less red tape during development. But a well written program intended for end users probably ends up with mostly static types de facto because otherwise it'd be fairly unpredictable. However, if you'd have used a statically typed language, you would not have to assume "well written" because the static typing brings guarantees with it.
One hidden (and seldom discussed) feature of static type systems is that in multi-team projects, they can enforce and document abstraction layers between teams. I've even seen static type systems act as impartial arbiters when an argument arises as to if a given change broke one side of an abstraction barrier, or if the change merely exposed a latent silent bug on the other side of the abstraction barrier.
My "whoa" moment with python typing came when I implemented euclid's algorithm (for the gcd of two numbers). Shortly later, I wrote a polynomial class, and then I was able to use my original function on polynomials without changing a single character
In some statically typed languages that would be possible if I'd thought to write my original function generically. But at the time I hadn't even thought about calling it with anything other than integers.
[1] A typeclass is a like an interface.
The Integral class captures the mathematical idea of a Euclidean domain, which can loosely be thought of as "number systems for which the Euclidean algorithm works".
Nevertheless, writing generic code is of course possible with static types.
If in addition you want to write your gcd function using the Euclidean algorithm, you need to specialize again to Euclidean domains (of which polynomials and power series are an example).
The `gcd` function in the Haskell base library operators on Integral types, which are the programmatic representation of Euclidean domains, so I would argue that it is "general enough".
http://www.youtube.com/watch?v=XGyJ519RY6Y (video only, the presentation was liveish coding in emacs and I haven't found the source files)
Agda of course pursues the anti-thesis of dynamic typing.
If the goal is to be able to invoke a method on an object without checking for anything other than whether a method with the correct signature exists, that isn't a feature that's supplied by dynamic typing. It's supplied by duck typing, which is yet another axis (alongside dynamic/static and strong/loose). A popular example of a statically typed language that has had duck typing for a very long time is Objective-C.
In short, you can cut down on abstraction layers when working with behaviors that are inherently runtime.
If static typing would be good enough, you would not need casts. A cast basically says: "Hey compiler forget about type checking, just believe me that this thing has that type."
In respect to any language, the database is inherently dynamic, because it doesn't share your type system and the schema can be redefined at any point after compilation of your code. A statically typed language doesn't magically change that. The best you can do is to write some classes in advance and then hope nothing gets out of sync.
So, to put it in simpler terms, some things are inherently dynamic, and dynamic languages make working with them easier.
(I am assuming that the types of values returned by a query are finite and known.)
cough Ur/Web [2] cough
Both these languages have strongly-typed queries. Database queries are not "inherently runtime".
If you don't know what type of data is stored in your database, your code is probably incorrect.
[1] http://en.wikipedia.org/wiki/Language_Integrated_Query
[2] http://www.impredicative.com/ur/
The way to go in .NET is the opposite direction: Entity Framework with Code First approach.
And smart use of data structures, algebraic types and composition for the latter. The benefit of this is you build a sort of theory for your game, sometimes it predicts and suggests things that would not have been obvious to you otherwise.
Metaprogramming is prevalent in python/ruby, regardless of how confusing it is, because it is super useful. Being able to dynamically construct types and classes (e.g. via decorators in python) allows you to do tons of stuff that is otherwise really hard for library writers, even though you shouldn't be using metaprogramming all over the place day-to-day. Rails, SQLAlchemy and a ton of other libraries/frameworks use metaprogramming all over the place.
Static languages lack this ability, but because it's so useful, they end up re-implementing the same thing anyway but using reflection/bytecode-manipulation/extra-compile-hooks/code-generation/etc.. like in Spring/AspectJ/Play/ASP.NET!
Although the end result is kinda the same, I'd argue metaprogramming via generating java source code is way harder than metaprogramming in python, where creating a new class and setting its methods or descriptors is as easy as creating/fleshing out any other object. Given how powerful/useful metaprogramming is, I think that that's the biggest gain of dynamic over static languages.
cough MetaOCaml [1] cough
cough first-class modules in OCaml [2] cough
explanation -- MetaOCaml provides what is essentially a typed eval for Objective Caml. OCaml's first-class modules provide the ability to construct types, classes, and modules at run time.
In other words, statically typed languages do have this ability.
[1] http://www.metaocaml.org/
[2] http://caml.inria.fr/pub/docs/manual-ocaml-4.00/manual021.ht...
I'd like to say the following to the creators of similar frameworks: Just give me the interface and I will implement it. And keep your hands off my internals!
Part of why I suggest C# is that a lot of .NET libraries make heavy use of this functionality. Windows Communication Foundation, NHibernate, and Moq come to mind. So my impression had always been that it was approximately the same level of reflection, just with perhaps more syntactic sugar in one case.
I think statically typed languages have a lot of ceremony around doing something, whereas dynamically typed languages just get out of your way.
(define curry (f . cargs) (lambda moreargs (apply f (append cargs moreargs))))
Static typing is safer (more compile-time checks) but more verbose. Type errors are found by the compiler before the program runs.
With dynamic typing you write less, but requires better understanding of the underlying types so you don't get runtime errors. Type errors propagate through the program until it generates a runtime exception or wrong results.
No, explicit typing is more verbose. Static typing does not mean java. Many statically typed languages have type inference, and have for a long time. They are no more verbose than dynamically typed languages.
Datatypes are like types but without the structure or logic. They don't prove anything and are basically just sets. You have to bring your own rules. Dynamic languages are unityped, you can see this by noting working only on just a top type shares many properties (not all, e.g. flexibility) of being in a dynamic language. Dynamic languages however can have datatypes, which make sure operations on the collections a particular object inhabits makes sense according to rules the language designer defines.
For your example, in a dynamic language I can state that r,a and b all belong to the same type but their datatypes may vary from moment to moment. In a statically typed language I cannot. Without types a ST language will assume that a and b are the same universal type. But I could give just a a top type and then your example would work. But I lose type safety. To get it back I could use interfaces, inheritance or algebraic datatypes each with their own pros and cons.
The difficulty with doing this in a statically typed system is that once you've fixed the type of the function you pass in, you can't then apply it to two different types. The standard way to solve this would be to specify that the function you pass in is valid for all types [1]
However, the challenge also specified that you do it without writing a type signature for `f`. I admit that in this case I am pretty stumped. The closest I can get is to first define a wrapper and then write but that involves wrapping everything up before passing it into the function, to disguise the fact that we really have different types.There's an interesting point to be made here, though - "wrapping everything up in one type" is essentially what dynamic type systems do, except that it's baked into the language instead of being implemented by the programmer on an as-needed basis.
Here, my declaration for the type S says "any type as long as it has a Show instance" (which allows it to be printed). I could instead have specified "any type that has a Num instance" which would mean that I could pass in a mix of ints and floats, and still do arithmetic with them. I get some of the benefits of dynamic typing (flexibility) but I still get to keep type checking.
The Python approach is "any type, and we'll check at runtime that it has the appropriate methods". So it's ultimately flexible, but as a result loses any form of compile-time checking.
Looking at it this way, I think that the challenge you've set me is one that cuts to the core of the difference between dynamic and static type systems. No, I can't write the function you want in a statically typed language without doing something clever with types. But the clever thing I have to do with types is re-invent duck typing, but in a limited way which still gives me compile-time checking.
Anyhow, thanks for the insightful comment. It certainly made me think...
[1] This requires the language extensions ExistentialQuantification and Rank2Types, but neither of those are particularly controversial.
[2] Note that the type signature
is inhabited by exactly one function - the identity function. This carries over to the Python example too - the function `f` isn't safe to use unless the first argument is the identity function, which makes it a bit of a useless function to define in the first place.This is imprecise - a dynamic language does not really need to "wrap everything in one type" - look at Dart or Groovy. What makes it 'dynamic' is the dynamic dispatch. All dynamic languages I know dispatch based on runtime argument types, but this is only one particular form of dynamic dispatch. Declaring types in dynamic language typically is used at runtime merely as an assertion, that at this point the argument must conform to the said type.
Dynamic dispatch differs from Polymorphic dispatch in the way that polymorphic dispatch takes in account the runtime type of the target (the object whose method you are calling), but the types of the parameters are fixed at compile time.
Many dynamic languages also provide a way to customize the dispatch logic (i.e. call a default handler if no match exists) - typically via some form of Meta Object Protocol. Often the dynamic languages provide features like higher order functions, continuations, pattern matching, etc. but this has nothing to do with their dynamicity - same features are available in some static languages as well.
If you want to faithfully translate the idea that 5 and "Test" are different values that can be passed into the same places but told apart by tests if you want, you might translate the second line to something like f (\x -> x) (Left 5) (Right "Test"), which does pass.
No, you don't. Type inference was invented in 1969. 43 years ago.
Another dimension is the explicitness of your variable languages. This isn't quite orthogonal since static languages have to allow you to specify a type and dynamic languages have to have some way for you to not specify a type but many static languages allow you to infer a type and many dynamic languages allow you to specify types. (You can always use asserts if they don't explicitly allow it).
My point is that inferred types always have the potential to provide surprising results. It is unlikely in static languages, but it's possible. If you've defined "square x = x * x" it will allow you to square anything that has the splat operator defined for, even if that would be nonsensical. For that reason inferred static languages generally don't overload the splat operator, so it's a bad example. But I hope you still get my point.
And the mix can produce interesting results. For eg. in Python:
If you were expecting a number to be returned then this isn't good :(The * operator isn't overloaded in Perl so instead we get:
Perl is more weakly typed than Python so 'X' get coerced to zero with the multiplication operation. So at least a number is returned but I would prefer a warning or error:Luckily there is a workaround for Javas static typing limitation: Casts. Every time a Java programmer uses a cast, you can see that static typing has limits.