Who cares, if the OOP is fast?Aethix said:Crazy abuse of dot notation, for one thing. I think it uses forced OOP, like Java and VB.NET.
Last edited by a moderator:
Who cares, if the OOP is fast?Aethix said:Crazy abuse of dot notation, for one thing. I think it uses forced OOP, like Java and VB.NET.
did you intentionally leave out B, D and Cg?dflemstr said:C, C++, C#, Javascript, Objective-C, GLSL, HLSL and Google Go look awfully Java-ish in syntax. Why would anyone want to program in those languages?Aethix said:Google go looks awfully Java-ish in syntax. Why would anybody want to program in it?
Yep.darkblu said:did you intentionally leave out B, D and Cg?
Which is a Good Thing. OOP is a horribly broken concept, and languages that try to fix that are in one of two categories:sindbad said:It's not even OOP, more like struct-based.
I don't see how Go's system allows you to side-step the issues with OOP. It leaves the responsibility of getting it right to the individual developers, which I think is a bad idea.dflemstr said:Yep.darkblu said:did you intentionally leave out B, D and Cg?
Which is a Good Thing™. OOP is a horribly broken concept, and languages that try to fix that are in one of two categories:sindbad said:It's not even OOP, more like struct-based.
1. Languages like e.g. Python where you try to circumvent the bad effects that OOP has (You have conflicts with multiple inheritance? Well, whatever, just throw a random error; the developer will fix it eventually. Don't like the problems with common safe OOP type systems? OK, let's just skip types all together).
2. Languages like e.g. Scala where you try to fix the issues with OOP (And it actually succeeds, but there are still some rough edges. From the core library linked list implementation: "sealed abstract class List[+A] extends LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqLike[A, List[A]] { /* ... */ }")
Go enables you to use polymorphism, "inheritance" and interfaces without any of the OOP problems. It's a win IMO.
I'm not sure where you're coming from with the "bad name", but in the commercial game development scene Lua is still very much the preferred and popular choice in scripting languages.Gary13579 said:Lua has a bad name in the gaming scene, I think from the PSP days and LuaPlayer. But really, almost every application could be better with a lil lua inside.
It does? I don't see how.sindbad said:I don't see how Go's system allows you to side-step the issues with OOP. It leaves the responsibility of getting it right to the individual developers, which I think is a bad idea.
func (s string) ElementAtIndex(i int) Element {
//...
}
Yes, exactly. How does this not match what I said?sindbad said:for 1., you really haven't used Python much, have you? Conflicts with multiple inheritance are properly reported, but hard to fix. This is why the idiom of doing multiple inheritance in Python is mixins.
Yes, I agree, except about that it's hard to understand - it's not, really. You can describe it using around 600 words or less. The problem with it is rather that you, as a library designer, because it's so over-engineered, have to give the end-user extremely much freedom if you want him/her to be able to use your library in any way that he/she wishes (e.g. if you want the user to be able to add a sort method to integers, so that its bits can be sorted *efficiently* using qsort. You can do that in Scala).sindbad said:for 2., Scala has a highly over-engineered type system. I doubt regular developers understand enough of it to hack on it, which could lead to problems later on in the language's life. Still better than Java's half-done type system, though.
dflemstr said:(As a side note: when new programming languages come out, I will always try to argue *for* them instead of against them, mainly because I want to encourage people that are experienced with other languages to give me their point of view on the language that builds on *their* background, so that I can form a picture of the new language myself)
It does? I don't see how.sindbad said:I don't see how Go's system allows you to side-step the issues with OOP. It leaves the responsibility of getting it right to the individual developers, which I think is a bad idea.
For example, take
Polymorphistic compatibility
In other OOP languages, say that you have a "sort" function that accepts lists of stuff. Then, let's say that you want to use that function, without changing it, to also sort Strings. Then, let's say that that sort function uses some method on List called e.g. "elementAtIndex(x)", which Strings don't have, and you consequently have to "add" that method to String in order to sort it.
Python solves this using duck typing, which is a good enough solution, except that you have to actively add a method to instances of String, and you can't really say that "every time someone anywhere requests a call to 'elementAtIndex' on any 'String' anywhere, do this". Also, it isn't type safe, which is a problem for another discussion some other time.
Scala solves this using implicit type casts, so that you can create an implicit method that the compiler automatically uses to transform "mystring.elementAtIndex(0)" to "string2list(mystring).elementAtIndex(0)". This is called the "can be treated as" pattern (in contrast to the "is a" pattern that you use with inheritance). The already compiled "sort" function won't use that implicit however.
Another alternative in Scala is to mix in List into String, so you say "(new String("....") with List[Char]).elementAtIndex(0)", which works like duck-typing except that it's type safe. None of those solutions are relatively fast, however; both a wrapper and a mixin require a call equivalent to "malloc()" in C, and dynamic memory allocation is never a good thing.
In Go, however, you can simply add a method:
...and *poof*, the string struct has a ElementAtIndex method all of a sudden, and it's as fast as if you had compiled in the function from the start when defining "sort()"Code:func (s string) ElementAtIndex(i int) Element { //... }
This does already eliminate the need for inheritance, "real" polymorphism, class definitions and so on.
Yes, exactly. How does this not match what I said?sindbad said:for 1., you really haven't used Python much, have you? Conflicts with multiple inheritance are properly reported, but hard to fix. This is why the idiom of doing multiple inheritance in Python is mixins.
Yes, I agree, except about that it's hard to understand - it's not, really. You can describe it using around 600 words or less. The problem with it is rather that you, as a library designer, because it's so over-engineered, have to give the end-user extremely much freedom if you want him/her to be able to use your library in any way that he/she wishes (e.g. if you want the user to be able to add a sort method to integers, so that its bits can be sorted *efficiently* using qsort. You can do that in Scala).sindbad said:for 2., Scala has a highly over-engineered type system. I doubt regular developers understand enough of it to hack on it, which could lead to problems later on in the language's life. Still better than Java's half-done type system, though.
Yeah yeah, I know all of that (except for that you could add methods to str; I thought it was a built-in type that can't be touched)...sindbad said:You can just say def f(i): ....; str.ElementAtIndex = f. It's not particularly idiomatic, but that's because there are much better solutions to that problem in Python. For Ruby it is somewhat idiomatic to monkey patch, so the syntax is nicer.
def render(x):
"""Draws a renderable object to the screen"""
if hasattr(x, 'render'): #reasonable check to see if x has the right type
Graphics.pushMatrix()
Graphics.translate(x.pos) #oops! x might not have a pos attribute
x.render()
Graphics.popMatrix()
#object oriented; works
class Renderable:
pos = 1, 2
def render():
print 'Stuff'
x = Renderable()
render(x)
#duck typing; maybe doesn't work
#(imagine Box being in a separate, non-touchable library or whatever)
class Box:
points = (1,1),(1,0),(0,1),(0,0)
def r(): print "Rendering box"
Box.render = r
x = Box()
render(x) #oops, doesn't work; no way to check the contract and x doesn't have "pos"
Dude, I'm using Scala, so my code basically consists of nothing else except mixins. I think that they are nice too, but they don't necessarily solve all problems. And ABCs seem to again be a hack solution to a problem that could have been solved properly; they solve the same problem that implicit conversions do in Scala (and the solution isn't that good either as I said before) or also the problem that's solved by abstract classes or interfaces in any other language, in which case this is a Python bugfix and not a feature.sindbad said:If you use multiple inheritance willy-nilly, you will have hard to debug problems. If you use mixins, however, it is very unlikely to get problems and if you do get them, they're easy to debug. Check out ABCs, as well.
I'm not adverse to subclassing; if I need abstraction, I use OOP, and if I need performance, I use non-OOP. OOP is a convenience abstraction that makes your software run slower, with the benefit that you're able to grasp what your code is doing more easily. Go offers the best of both worlds IMO.sindbad said:You can add a sort method to integer in python as well, if you really want to. But it's bad manners to do so, why exactly are you so adverse to subclassing?
I've gone too out of touch with them, but for different reasonssindbad said:I think you've gone too out of touch with dynamic languages to remember just how much better they are.
dflemstr said:Yeah yeah, I know all of that (except for that you could add methods to str; I thought it was a built-in type that can't be touched)...sindbad said:You can just say def f(i): ....; str.ElementAtIndex = f. It's not particularly idiomatic, but that's because there are much better solutions to that problem in Python. For Ruby it is somewhat idiomatic to monkey patch, so the syntax is nicer.
Only problem is when you have something like this:
And yeah, that's sloppy code, but the thing is: with interfaces or strong typing, you aren't *able* to be sloppy, while with code like this, you *can* be sloppy but get buggy code out of it.Code:def render(x): """Draws a renderable object to the screen""" if hasattr(x, 'render'): #reasonable check to see if x has the right type Graphics.pushMatrix() Graphics.translate(x.pos) #oops! x might not have a pos attribute x.render() Graphics.popMatrix() #object oriented; works class Renderable: pos = 1, 2 def render(): print 'Stuff' x = Renderable() render(x) #duck typing; maybe doesn't work #(imagine Box being in a separate, non-touchable library or whatever) class Box: points = (1,1),(1,0),(0,1),(0,0) def r(): print "Rendering box" Box.render = r x = Box() render(x) #oops, doesn't work; no way to check the contract and x doesn't have "pos"
I find ABC to be a pretty good solution to a hard, complex problem. I don't think Scala solves it any better, but perhaps beter for someone who thinks static typing is a good idea.dflemstr said:Dude, I'm using Scala, so my code basically consists of nothing else except mixins. I think that they are nice too, but they don't necessarily solve all problems. And ABCs seem to again be a hack solution to a problem that could have been solved properly; they solve the same problem that implicit conversions do in Scala (and the solution isn't that good either as I said before) or also the problem that's solved by abstract classes or interfaces in any other language, in which case this is a Python bugfix and not a feature.sindbad said:If you use multiple inheritance willy-nilly, you will have hard to debug problems. If you use mixins, however, it is very unlikely to get problems and if you do get them, they're easy to debug. Check out ABCs, as well.
I think D is a better Go, except for the green threads in go. I also don't see why statically typed pre-compiled, non-OO code must certainly be slower than JITed dynamic OO code. It may be so with current compilers/JITs, but that's changing. JavaScript has very fast JITs that work very well for OO code because they were designed for that. Have a look at PyPy as well.dflemstr said:I'm not adverse to subclassing; if I need abstraction, I use OOP, and if I need performance, I use non-OOP. OOP is a convenience abstraction that makes your software run slower, with the benefit that you're able to grasp what your code is doing more easily. Go offers the best of both worlds IMO.sindbad said:You can add a sort method to integer in python as well, if you really want to. But it's bad manners to do so, why exactly are you so adverse to subclassing?
dflemstr said:I've gone too out of touch with them, but for different reasonssindbad said:I think you've gone too out of touch with dynamic languages to remember just how much better they are.
Exceptions are expensive, and I really mean expensive. They have to capture the entire call stack, and for e.g. recursive functions (let's say that you want to render a mesh with graphical nodes) it's really slow. Even JIT won't convert an exception throw/catch into a simple jump (The HotSpot JVM can do that, but it's pretty unique). A type check is fast, since it only involves one integer comparison, which is a O(1) operation.sindbad said:You'd never actually do that in Python. You'd either just call .render and catch the exception (and it will throw a different exception if Box.render is doing the wrong thing), or if you really want to check, do isinstance(x, Renderable). Python isn't javascript, there are strict, explicit types and things don't change types just because you call a method.
Which restrictions? Using your above argument, you're basically saying that to get bug-free code, you have to either do type checks in Python, or use expensive exceptions. The former method puts you on the same level as statically typed languages do, except that you have to do more typing in Python, and the latter method is just inferior...sindbad said:Saving programmers from themselves by adding hard restrictions has never worked as well as strongly suggesting good practice
def render(x: {def render(): Unit; def pos:(Int, Int, Int)}) = /*...*/
I've never seen a language that does that.sindbad said:(and if the language makes good code natural, even better).
I don't think Scala has a better solution either, but it's at least a more foolproof model. The main difference, I think, between static and dynamic typing systems, is that with static types, the one who uses a {library, piece of code, unit} has to adjust to that library and live by that library's standards, while with dynamic typing, it's the library that has to adjust to all its users. There are multiple shades of gray, of course; Scala has structural types, traits, type variables and type parameters, which give the caller a lot of freedom, and Python has instance checks and ABCs, which givesthe library creator a lot of freedom.sindbad said:I find ABC to be a pretty good solution to a hard, complex problem. I don't think Scala solves it any better, but perhaps beter for someone who thinks static typing is a good idea.
sindbad said:I think D is a better Go, except for the green threads in go.
Well, it isn'tsindbad said:I also don't see why statically typed pre-compiled, non-OO code must certainly be slower than JITed dynamic OO code.
/** Produces a list of 4 ints */
def f(x: Int) = concat(List(x, x), List(x, x))
/** Produces a list of 4 floats */
def g(x: Float) = concat(List(x, x), List(x, x))
/** Concatenates two lists with any type of elements */
def concat[T](x: List[T], y: List[T]) = x ::: y
Java HotSpot is the fastest JIT compiler on the market (arguably; LLVM can be faster sometimes). It, too, is designed for OO code.sindbad said:It may be so with current compilers/JITs, but that's changing. JavaScript has very fast JITs that work very well for OO code because they were designed for that. Have a look at PyPy as well.
dflemstr said:This is Blasphemy!lulzfish said:The syntax is confusing and it's not nearly as good as The One True Language, which is Lua.
Exceptions are the proper way to do it and JITs should give us cheap exceptions (if they don't, the should be improved). You're confusing dynamic typing with duck typing. A dynamic language has the freedom to either duck type or check for specific types or shapes of objects. It is often a good idea to check what kind of objects you are given through the entry points to your library.dflemstr said:Exceptions are expensive, and I really mean expensive. They have to capture the entire call stack, and for e.g. recursive functions (let's say that you want to render a mesh with graphical nodes) it's really slow. Even JIT won't convert an exception throw/catch into a simple jump (The HotSpot JVM can do that, but it's pretty unique). A type check is fast, since it only involves one integer comparison, which is a O(1) operation.
And if you check if x is an instance of Renderable, well, then you aren't using a dynamically typed language any longer, since you are checking types. And duck-typing a Renderable then becomes impossible, too, btw.
My point was that in Python you rarely need to check types, and when you do you have the entire expressiveness of the language at your disposal to check for anything you want. Indeed in most functional languages you get similar freedom in checking. Also have a look at Python 3's function argument annotations, which can be used for checking types as well.dflemstr said:Which restrictions? Using your above argument, you're basically saying that to get bug-free code, you have to either do type checks in Python, or use expensive exceptions. The former method puts you on the same level as statically typed languages do, except that you have to do more typing in Python, and the latter method is just inferior...
I know that Go has Erlang-like concurrency and that's the only thing that I find better in Go than in D.dflemstr said:D not only has horrible threading support, but also no way of using those threads beyond the usual lock model. Go has channels, which remind me of Unix pipes or Scala/Erlang actors, and they are far superior to anything you can do with shared state or locks in other procedural languages.
Yes, I did mean the opposite What stops a JIT from specialising functions at runtime? In fact, that's exactly what Python's psyco does (and similar to what PyPy's JIT does). Lazy is almost always better.dflemstr said:Well, it isn'tsindbad said:I also don't see why statically typed pre-compiled, non-OO code must certainly be slower than JITed dynamic OO code.
If you meant the opposite of what you said, then I'll say that statically typed languages can be optimized more than dynamic languages indeed. With statically typed languages, you can do various code predictions, like for example removing a piece of code that the optimizer knows will never be called.(snip)
[/quote] Yes, HotSpot is quite possibly the best JIT on earth right now. LLVM's JIT is quite frankly crap in comparison (even mono's is better). It's quite good as a static compiler, but not yet as useful as they make it out to be for making JITs.dflemstr said:Java HotSpot is the fastest JIT compiler on the market (arguably; LLVM can be faster sometimes). It, too, is designed for OO code. (snip)sindbad said:It may be so with current compilers/JITs, but that's changing. JavaScript has very fast JITs that work very well for OO code because they were designed for that. Have a look at PyPy as well.
I don't know about you but I only use exceptions in my code when something exceptional occurs, aka that isn't part of your program's logic. As a side example, take for instance the parsing of ints in strings (which is done with the int() function in Python):sindbad said:Exceptions are the proper way to do it and JITs should give us cheap exceptions (if they don't, the should be improved).
Well, in most statically typed languages I'm given that choice too, so I don't see how this point is relevant, as it kinda defeats the purpose of having a dynamically typed language if you don't use it and vice versa. Granted, using type checking in Python (where a type check is as expensive as a string comparison) is cheaper than using dynamic types in Scala (where a dynamic call is as expensive as a hash table lookup + string comparison), but both options are available, and there are multiple shades of grey like e.g. Go where dynamic types are as cheap as static types (dynamic invocation will always have some overhead but it's less than usual in Go)sindbad said:You're confusing dynamic typing with duck typing. A dynamic language has the freedom to either duck type or check for specific types or shapes of objects. It is often a good idea to check what kind of objects you are given through the entry points to your library.
Well, the thing is that in other languages, I get the same concise code as in Python, but I get type checking for free anyways (aka, if I don't check types myself the compiler does it for me anyways; less typing). The difference isn't whether you create type annotations, because that's just syntax; the difference is in the fundamental system of the language. A type-safe language can give you as much freedom as a dynamic language can.sindbad said:My point was that in Python you rarely need to check types, and when you do you have the entire expressiveness of the language at your disposal to check for anything you want. Indeed in most functional languages you get similar freedom in checking. Also have a look at Python 3's function argument annotations, which can be used for checking types as well.
//I'm NOT mentioning Scala her because in Scala, types can be left out everywhere except for when declaring new functions.
-- Haskell (type-safe):
concat x y = x ++ y
concat [1, 2, 3] [4, 5, 6]
--→ [1, 2, 3, 4, 5, 6]
concat "abc" "123"
--→ "abc123"
concat "123" 5
-- No instance for (Num [t])
-- arising from the literal `5' at <interactive>:1:22
-- Possible fix: add an instance declaration for (Num [t])
-- In the second argument of `Main.concat', namely `5'
-- In the expression: Main.concat [2, 3, 4] 5
-- In the definition of `it': it = Main.concat [2, 3, 4] 5
#Python:
def concat(x, y): return x + y
#This works for all compatible types too, like the above.
concat('123', 'abc')
#→ '123abc'
#BUT Python will behave in an unexpected way sometimes because of the missing type-safety, e.g.:
concat(5, 6)
#→ 11
#What? Why didn't I get 56? (Don't answer that, I'm not dumb :P )
I did never say that a type-safe language would specialize that method offline. I just said that even online, a JIT can't do anything in that particular situation, since it has no way of doing branch prediction because it doesn't know the type of the arguments that might be passed to the top function.sindbad said:What stops a JIT from specialising functions at runtime? In fact, that's exactly what Python's psyco does (and similar to what PyPy's JIT does). Lazy is almost always better.
I just said that LLVM occasionally generates better code, even in JIT mode, than HotSpot does.sindbad said:LLVM's JIT is quite frankly crap in comparison (even mono's is better). It's quite good as a static compiler, but not yet as useful as they make it out to be for making JITs.
It's still far from being as fast as other JIT implementations for statically-typed languages, and that's my point. And just the fact that actual *research* is required and new algorithms have to be *developed* before a dynamic JIT even has a chance of having the same speed as a static JIT says one thing or two about dynamic languages and their compilability.sindbad said:Well, PyPy's current JIT is much faster than Unladen Swallow at synthetic benchmarks. The one disadvantage PyPy has (besides the crappy regex engine) is that it can't use CPython C extensions, only ctypes.
try:
a = int(num)
except ValueError as err:
handle(num, err)
a, err = int(num)
if err is not None:
handle(num, err)
Not in my booksindbad said:If the user inputs a wrong number, that's exceptional.
...which is why exceptions are bad in such situations (because the function *would* return int), and you *shouldn't* return an int but instead something that is easier to handle. Exceptions from such a function *would* make the code blow up if not handled.sindbad said:If there were no exception and you assumed that the return from int() is an actual int, your code could blow up later.
Quite frankly, alternative number two.sindbad said:What do you find more readable? this
Code:try: a = int(num) except ValueError as err: handle(num, err)
or this?
Code:a, err = int(num) if err is not None: handle(num, err)
//Scala:
val myInt = tryParse("123") match {
case Some(validInteger) => validInteger //myInt = validInteger
case None => doSomething("error while parsing");
}
process(myInt)
//Go in the future (yes, it has horrible syntax, we all agree on that):
switch nr := parseInt("123") {
case nr.isDefined:
process(nr.get);
default:
doSomething("error while parsing");
}
Java has a failed exception architecture, yes. That doesn't make exceptions any cheaper or more predictable in other languages, however.sindbad said:Also, Python is not Java. There are no checked exceptions and exception hierarchies make sense. I don't remember ever having to rethrow an exception in Python, although I may have done so at some point.
Of course, and that guard is expensive (considering that many functions we write nowadays sometimes are one-liners that e.g. just convert a type to a string).sindbad said:About JIT specialisation, you don't seem to understand how it works. The specialised version has a guard. Every time the function is called with new types, a new specialised version is compiled.
Eh, why wouldn't partial evaluation work? Are you confusing dynamically typed languages with scripting languages now, or what do you mean?sindbad said:It may be that dynamic JITs have less potential for optimisation, but that doesn't mean they can't be fast enough. Even so, many techniques still can't be applied to static languages, but work quite well with runtime optimisation on dynamic languages (like partial evaluation).
This is simply wrong. Everything in python is an object and you can extend any type whatsoever. When JITing you can do stuff like virtuals, where you don't actually create a full object until it's needed, but that's just an implementation detail of some JITs.dflemstr said:there is an uniform class hierarchy, but you can't e.g. extend integers, strings, floats etc because they aren't real classes; they are just value types with pimped wrapper methods. Ergo, not every value is truly a class at runtime.
My point was that you can't extend integers etc, because integers are 4-byte values. How ever the JIT represents extended ints at runtime, it cannot be as a 4-byte value. Reread that paragraph with this in mind. Oh, and with virtuals, well, that's yet another fork required on each method call, unless you make the call stack stateful by introducing some kind of stack sentinel that marks the call stack as being primitive.sindbad said:This is simply wrong. Everything in python is an object and you can extend any type whatsoever. When JITing you can do stuff like virtuals, where you don't actually create a full object until it's needed, but that's just an implementation detail of some JITs.
The Abacus: Very Very Fast™ compared to counting on your fingers. Doesn't tell me anything about how it relates to a Texas Instruments Voyage 200.sindbad said:Re specialisation: My point was that even if theoretical performance potential is smaller, dynamic languages with good JITs can be very, very fast.
This might be true, but do I have to take your word for it? I see no reason for it being so.sindbad said:Re partial evaluation: Most statically typed compiled languages (including java, i'm not sure about scala) are built in such a way that partial evaluation is very hard or impossible to implement. The opposite is true for dynamic languages (especially if strongly typed).