Weakly/Dynamically typed languages confuse me

Pages: 12345
No surprise this turned into a "which is better" thread. :)

@LB: re: age. All I'm saying is that you're too young to close your mind to things, whether they be related to coding or not. I'm not commenting at all about how much you know or your skill level. It's just my opinion anyway, it's your life to think what you will.

helios wrote:
Is this program syntactically valid? Does it compile?

My mindset was that if it doesn't compile, then it isn't syntactically correct. I can see your examples as logic errors though.

I feel your not seeing my point though, rather I need to give another example of a logic error:
1
2
3
4
int amountOfBunnies( )
{
  return potatoes.length();  // potatoes is a class I made myself, length() returns an int :)
}


Type errors are not fatal in Ruby, you'll get an exception for this: 1 / "string".

Ruby is written in C, and it is possible to write straight C from within Ruby code. I would imagine something is similar for Python. I have been able to write C++ libraries that I could use from within Python. So, like other's have said before, if you need a quick program, try something dynamic. If that program is too slow or cumbersome, extract methods into compiled libraries.


Which abstractions in particular do you have in mind?
One class of examples results from ability to do eval(). For the simplest example, you can store external data such as configuration in source files, and load them at runtime with a single statement. Or, you can easily allow users to extend your program by writing scripts in the language you use. Those scripts could even morph your data at runtime, or add methods to your classes or something. In general, static languages make it hard to create highly dynamic/reflective systems.

@rapidcoder
Thanks, I will read it when I have some free time.
Last edited on
1. eval() is a typical feature of scripting languages and, more generally, language implementations which include the compiler as part of the runtime. It has nothing to do with the type system. For example, Mono has an extension to the standard .NET library that exposes the compiler to the program being executed.
2. The requirement that the configuration file or script be in the host language is artificial, and adds nothing to the abstractive power of the language. A program in C++ that does its scripting in Lua is not less abstract than one in Python for that reason.
3. Reflection is a feature set orthogonal to type staticity.
Reflection is a feature set orthogonal to type staticity.
Example: both Java and C# have reflection.

I have seen an article on Java where author changed private final field of type of private inner class in running program using reflection and application plugin system.
1. eval() is a typical feature of scripting languages and, more generally, language implementations which include the compiler as part of the runtime. It has nothing to do with the type system.
Ok, this is not related to type staticity. Only that all dynamic languages have that feature, but very few of static ones.

3. Reflection is a feature set orthogonal to type staticity.
I wouldn't say it is completely orthogonal. In theory yes, but somehow many static languages drop all that information at compile time.

So, an example when static typing sucks... lets see... what language? Ok, lets suppose you have a list of objects of different types (that's already hard). You want to process them in a specific way, depending on the type. Oh, and you want it to be be extensible. Something like this:

def process_objects(obj_fun_map, obj_list):
   return [obj_fun_map[type(obj)](obj) for obj in obj_list]

obj_fun_map = {
   int: lambda i: i+1,
   str: lambda s: s+'!!!'}

print process_objects(obj_fun_map, [1, 'abc'])
[2, 'abc!!!']
I have seen an article on Java where author changed private final field of type of private inner class in running program using reflection and application plugin system.
That is doing dynamic programming in Java. Have I said that all static languages have a way to escape the type system when one needs to?
1
2
3
4
5
6
7
8
9
def processObjects(objFunMap: Any => Any, objList: List[Any]) = 
  objList.map(objFunMap)

def objFunMap(obj: Any) = obj match {
  case i: Int => i + 1
  case s: String => s + "!!!"
}

println(processObjects(objFunMap, List(1, "abc")))

 
List(2, abc!!!)


Statically typed? Yes. Verbose? No. Lacks any abstraction? No.
Ugly? Yes. Storing ints mixed with strings in a single collection for different processing is not how you should program... ;)

Last edited on
In theory yes, but somehow many static languages drop all that information at compile time.
But is that by virtue of the fact that they're statically typed, or just conscious decisions on the part of the language designers?

Your example is akin to me showing how much the C preprocessor sucks at metaprogramming. Just because you can do something doesn't mean you should, and IMO, uses of variant types in static type systems are misguided in 90% of the cases.
Damn. I knew it will end that way. It seems Scala is worth looking at ;)

Although it is a little unfair, because you use it like a dynamic language here. Those pattern matching based on type must be in general done at runtime, except maybe in this small example when compiler sees everything.

EDIT: Your example suggests that it may actually be possible to take the best of both worlds, and create a language that uses static typing mostly but yet is succinct and powerful, and provides dynamic features for the cases when they're more appropriate.
Last edited on
2. The requirement that the configuration file or script be in the host language is artificial, and adds nothing to the abstractive power of the language. A program in C++ that does its scripting in Lua is not less abstract than one in Python for that reason.
Except that it's easier to make an extensible program if the language it's written in is the same as the extension language.
you use it like a dynamic language here


Nope. The example is still fully typechecked, only the types are very wide (general) to match your example, so the typechecker doesn't really do much checking. But I'd never write code like that.

Let's try to pass a wrong kind of a processing function to our processObjects:

1
2
3
4
5
6
scala> println(processObjects((s: String) => s + "???", List(1, "abc")))
<console>:9: error: type mismatch;
 found   : String => String
 required: Any => Any
              println(processObjects((s: String) => s + "???", List(1, "abc")))
                                                 ^


In this case, your Python example would process normally if list contained only strings, but would fail miserably in runtime if the list contained an int.
Last edited on
Except that it's easier to make an extensible program if the language it's written in is the same as the extension language.
What makes it easier is the fact that the language supports extensibility to begin with. That carries with it simple interfacing between the extendee and the extensor. For example, there's no reason why a C++ compiler couldn't allow this:
 
extern "Python" int foo(int);
while doing all the work of generating boilerplate code for interfacing with the Python C API and doing a pseudo-linking step.
@rapidcoder
I don't see how it is different between Python and Scala. If you don't know what the list contains in advance (at compile time), there must be runtime error in both languages.

EDIT: I was confused by your example not matching its description. The example just misses a function for int's. In this case, assuming the compiler does not see the declaration of list, a runtime error must occur. I guess a wrong kind of processing function will make the Scala compiler generate an error. In Python a runtime exception will occur (that's not failing miserably ;)
Last edited on
After reading the massive response to this thread, I'd have to say that only rapidcoder and helios are giving sold, convincing arguments.

@Lowest0ne: re: age: I'm not closing my mind of to anything, I'm just very comfortable with static type checking.

Lowest0ne wrote:
My mindset was that if it doesn't compile, then it isn't syntactically correct.
Syntax is far from being considered at the linker stage.

I haven't looked at Scala but have been meaning to since every time I point out something wrong with Java I am told it is "fixed in Scala".
Last edited on
No, because I could restrict the list to be only of items of some type. And then if I passed a wrong list, I'd get a compile time error. The purpose of the example was to show that mere existence of a static type system doesn't disallow to write code in a Pythonic way. That's why I wrote it like in Python - this is far from how you code in static languages.

For getting most help from the type system you should use as narrow and specific types as possible, not the widest (Any / Object / void*, whatever your language supports).

So, nice try, but you still failed to show any abstraction what a dynamic language enables, that static can't.

Btw, a slightly better example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sealed trait Fruit
sealed trait SlicedFruit extends Fruit
case class Banana(amount: Double) extends Fruit
case class Mango(amount: Double) extend Fruit
case class Apple(amount: Double) extend Fruit

case class SlicedBanana(amount: Double) extends SlicedFruit
case class SlicedMango(amount: Double) extends SlicedFruit
case class SlicedApple(amount: Double) extends SlicedFruit

def makeSalad(fruit: List[Fruit], fruitSlicer: Fruit => SlicedFruit) = 
  fruit.map(fruitSlicer)

def knife(fruit: Fruit): SlicedFruit = {
  case Banana(amount) => SlicedBanana(amount)
  case Mango(amount) => SlicedMango(amount)
  case Apple(amount) => SlicedApple(amount)
  case x: SlicedFruit => x
}


1
2
3
4
5
6
7
8
9
10
11
12
makeSalad(List(1, 2, 3), knife)  // type error
makeSalad(List(Apple(2)), knife)  // ok
makeSalad(List(SlicedApple(2), Banana(10)), knife)  // ok
makeSalad(knife, List(Apple(2), Banana(10)))  // type error (wrong order of args)
makeSalad(List(Apple(2), "banana"), knife)  // type error


def brokenKnife(fruit: Fruit): SlicedFruit = {
  case Banana(amount) => SlicedBanana(amount)
  case Mango(amount) => SlicedMango(amount)
  // compile-time warning: match is not exhaustive
}

Last edited on
Lowest0ne: Sorry, I just saw your reply now.
My mindset was that if it doesn't compile, then it isn't syntactically correct.
That was partly the point of my questions.
I don't agree with this, of course. In particular, Unknown<N> can't be evaluated until it and the caller are fully parsed and checked for syntax errors. That error is purely a type error.
I made this a while ago as a proof of concept. The concept being a c++ backend with python frontend:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//foo.h
#ifndef FOO_H
#define FOO_H

#include <string>

class Foo{
  public:
  void bar( const std::string& );
};


extern "C" {
  Foo* Foo_new() { return new Foo(); }
  void Foo_bar( Foo* foo, const char* str ){ foo->bar( str ); }
}
#endif 

1
2
3
4
5
6
7
8
//foo.cpp
#include "foo.h"
#include <iostream>

void Foo::bar( const std::string& str )
{
  std::cout << "Hello" << " " << str << std::endl;
}


Foo is then compiled into a library.

1
2
3
4
5
6
7
8
9
10
11
12
13
// fooWrapper.py
from ctypes import *
lib = cdll.LoadLibrary('./libfoo.so')

class Foo( object ):
  def __init__(self):
    self.obj = lib.Foo_new()

  def bar(self, string ):
    lib.Foo_bar( self.obj, string )

f = Foo()
f.bar("World")


1
2
$ python fooWrapper.py
Hello World


Which I know barely relates to anything in this thread, but this thread made me think of it. There are a few other ways to do that, but I think this is the least intrusive on the actual c++ files.
Last edited on
Lowest0ne wrote:
std::cout << "Hello" << " " << str << std::endl;
Heh, why not just include the space in the same string literal as "Hello"? You could have even used string literal concatenation XD
Last edited on
I read my yesterday responses and they seem quite chaotic. Hopefully this post will make things more clear.

I never said you cannot do something in statically typed language. You can always (?) achieve the same effect by getting rid of the type system, e.g. using void* and/or type cast, adding runtime checks. After all, the Python interpreter is implemented in C. It’s just a matter of how easy and natural it is.

I was mistaken by linking some language features to dynamic typing. They are independent in principle, it’s just that they happen to be found much more often in dynamic languages.

I was also not aware of the possibilities of the type system in some less mainstream statically typed languages. I guess it’s not the static typing that sucks, it’s the design of the type system in some languages. I should have been more specific. A critique that is valid for a particular language X is not necessarily valid for statically typed languages in general.

I also look at things from a very practical point of view. Back to the example with extension language - it’s true that extending C++ with Lua is equivalent to using Python for both the application and extension language, it’s just that the latter so much easier. Not in theory, but in reality.

So, thanks for the discussion. Finally, I have a small exercise for rapidcoder ;)
>>> class Foo:
	def doit(self, a, b):
		return (a + b)

	
>>> foo = Foo()
>>> foo.doit(10, 20)
30
>>> Foo.doit = lambda self, s: s.upper()
>>> foo.doit('abc')
'ABC'


EDIT: Changed the example so that the new method expects different types of arguments. The first version didn't illustrate the point well. And the point is that there's a limit on what you can statically analyze. To do certain things you need to widen your static type definitions so much it's hardly static typing any more. Theoretically the compiler could know when the type changes, and verify that before and after this happens the method is called with the right types of arguments. But it's not easy to do and thus statically typed languages either limit what you can do, or give you ways to escape the type system.
Last edited on
Pages: 12345