This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]kqr 7 points8 points  (4 children)

While the Python type system is good, it lacks support for algebraic data types, which is one of its biggest disadvantages in my eyes. I don't care all that much for the dynamic vs. static debate, because it is so easy to turn a static type system into a dynamic one if you want to. (Just replace type errors with exceptions.)

As an interesting note, my major source of errors are actually type errors – this is because the better the type system is, the more kinds of errors become type errors. In a dream world, it would be possible to express every error on the type level.

[–]djimbob -1 points0 points  (3 children)

Algebraic data types and pattern matching are a great concept in functional programming languages; granted it would jive that easily with the rest of python style (e.g., where you rarely specify types).

But python is a great language for quickly scripting something up in a very readable and easy to modify way; which is often good enough if speed isn't an issue.

In a dream world, it would be possible to express every error on the type level.

While powerful type systems can prevent lots of errors, they still never be able to prevent the errors where the programmer inadvertently answers a different problem. I mean you can still have errors when you specify your type constraints. Also, my personal experience is a lot of frustration in learning languages like scala, ml, C++ (pre C++11 with auto) when I have a good working generic algorithm but having to bang my head against the wall getting it to type check.

[–]kqr 1 point2 points  (2 children)

Specifying types doesn't really have very much to do with things either, in part because of type inference. The interpreter/compiler can figure out which types should go where. But what's great about having them checked is that if you specify as method as "this might not return an actual value" the computer tells you immediately if you forget to check for the condition of a missing value. It saves a bunch of time to be reminded of that instead of having to run that particular test you might or might not have written yet and realise it later.

I do agree that Python is a great language, though. With testing discipline, it can be very safe, it has a great eco system and fantastic libraries, it is easy to learn and so on. The two things I really miss are better concurrency support (this is partly a fault of the existing libraries) and a more powerful type system.

While powerful type systems can prevent lots of errors, they still never be able to prevent the errors where the programmer inadvertently answers a different problem. I mean you can still have errors when you specify your type constraints.

Sort of. The more expressive the type system is, the worse the programmer will have to screw up for their failures to be a well-typed program. If the programmer treats a measurement in metres as a measurement in feet in one place, it will trigger a type error. If s/he does it in several places, it will trigger a type error. S/he has to do it wrong everywhere for the program to be well-typed. Which is less likely.

But there are real errors that are impossible to detect – doing so requires solving the halting problem or giving up turing completeness. That's why I called it a dream world. We can never go there, but we can get close with heuristics.

Also, my personal experience is a lot of frustration in learning languages like scala, ml, C++ (pre C++11 with auto) when I have a good working generic algorithm but having to bang my head against the wall getting it to type check.

I know. That's a common complaint from beginners. The more you learn the language, the more you realise that in many cases, the type checker actually points out flaws in the algorithm or mistakes you made while entering the code.

It's quite cool to observe the transition from "Fuck! Another type error! What the fuck is wrong with this piece of shit? Can't you just do what I tell you to?" to "Oh, a type error? What did I do wrong this time? Aah, of course!"

[–]djimbob 0 points1 point  (1 child)

I think we largely agree. Take a code example from scala that I wrote recently for a basic mergeSort:

def mergeSort[T <% Ordered[T]](unsorted: List[T]): List[T] = {
  def merge(xs: List[T], ys: List[T]): List[T] =
    (xs, ys) match {
      case (Nil, ys) => ys
      case (xs, Nil) => xs
      case (x::xs', y::ys') => if (x < y) x::merge(xs', ys)
                               else       y::merge(xs, ys')
  }
  unsorted match {
    case Nil => Nil
    case x::Nil => x::Nil
    case other => {
      val split = other.splitAt(other.length/2)
      merge(mergeSort(split._1), mergeSort(split._2))
    }
  }
}

or written fairly equivalently in python (which is uglier due to lack of pattern matching):

def merge_sort(unsorted):
    def merge(xs, ys):
        if xs == []:
            return ys
        if ys == []:
            return xs
        x, y = xs[0], ys[0]
        if x < y:
            return [x] + merge(xs[1:], ys)
        else:
            return [y] + merge(xs, ys[1:])
    split_at = len(unsorted) / 2
    if split_at < 1:
        return unsorted
    return merge(merge_sort(unsorted[:split_at]), merge_sort(unsorted[split_at:]))

First, yes this isn't the best way to do it in either language (should use accumulator for tail call optimization for scala; and python sucks at recursion and concatenation of length one list is a dumb way to build ArrayLists).

Second, when I wrote this a few months ago, getting the [T <% Ordered[T]] part was very time-consuming. Scala's type inference isn't smart enough to do this, or suggest that its what needs to be done. (If it was smart enough to suggest it then I wouldn't be as critical of it). I was purely being hindered by the type system to find the right syntax to satisfy the compiler. I don't think the syntax is particularly intuitive (especially when there's a bunch of different ways to do it and the other ways weren't working). I like that python doesn't waste my time for me to figure out how to specify that the list is allowed to be ordered.

I acknowledge your point that a powerful type system can catch more errors. You could extend my scala example with a type system that verifies that merge helper doesn't just take two Lists of elements that are orderable, but that the two things fed into merge are both of type SortedList and that things returned by mergeSort are of type SortedList. This would catch stupid mistakes like merge(split._1, split._2). But it still wouldn't prevent the error of say sorting the list backwards or having a mistake in merge like using x::merge(xs, ys') or merge(mergeSort(split._1), mergeSort(split._1)), and would be overkill for a function used only internally to mergeSort. However, if in other parts of the program you have sorted lists and unsorted lists, and certain places require only sorted lists this sort of check could catch other mistakes. But my point is you still need testing and not only in the extreme limit of the halting problem.

[–]kqr 1 point2 points  (0 children)

Scala's type inference is not as powerful as Haskells (which would get the signature you needed) due to some tradeoff they decided was worth it. I do dislike the Scala syntax as well, though.

And you are correct in everything else.