6 ms·
It might be worth learning an ML-family language
- hacker_9 10y agoNews just in: Languages with static type systems give better errors about types.
- melling 10y agoI think you missed the point. The author didn't say learn Java, Go, or C, for example. "I think the reason for this is that in an ML family language, where the types are static but inferred, you are constantly playing a learning game with the compiler as your teacher'
- devishard 10y agoThis is not only missing the point, it's easy to prove false: Python: `foo = '1' + 1` gives `TypeError: Can't convert 'int' object to str implicitly` C: `char bar[] = "1"; char* foo = bar + 1;` gives literally no error. Good errors occur because of strong/weak typing, not because of static/dynamic typing.
- castratikron 10y agoYou always need to check array bounds in C, what else is new?
- devishard 10y agoApparently it's new to some people that C is a statically-typed, weakly-typed language.
- castratikron 10y agoIf you're talking about the cast from an int literal to a char pointer, that's not what's happening. That's common C shorthand for incrementing a pointer to point to the next element. For example, the char pointer increment compiles to: 4004fb: 48 83 c0 01 add $0x1,%rax While an int pointer increment compiles to: 40050c: 48 83 c0 04 add $0x4,%rax Because on my particular platform, an int is 4 bytes and a char is 1 byte and the memory is byte addressable.
- devishard 10y agoI'm aware of what's happening. It's definitely a norm in C and it produces desirable results in many cases (I've written similar structures myself many times). All I'm saying is that from a type perspective it's a bizarre choice to allow that.
- catnaroek 10y agoGood errors messages occur because you have enough information to craft said messages. Python has its fair share of utterly uninformative error messages, mostly due to the language's inability to comprehend the structure of user-defined types. Python programmers don't call these “type errors”, but a Haskell or ML programmer would.
- devishard 10y agoOkay, but how do you gather enough information to craft said messages? I posit that there's nothing stopping you from gathering that information at runtime; it just typically isn't done to the same extent in dynamic languages as in ML-family languages. A type system that gathers lots of type information is equivalent to a strongly-typed language IMHO, and a type system that gathers little type information is a weakly-typed language. Static languages can be strongly typed (Haskell, ML) or weakly typed (C) and dynamic languages can be strong(ish)ly typed (Python) or weakly typed (JavaScript).
- catnaroek 10y ago> Okay, but how do you gather enough information to craft said messages? There are two sources of such information: the static context (e.g., in Scheme, which variables are in scope) and the dynamic environment (e.g., again in Scheme, the type of an object, and, in Python, pretty much everything). The static context grows whenever you bind variables in the program text (e.g., when you define a procedure). The dynamic environment grows when the control flow reaches such binders (e.g., when it enters a called procedure). > I posit that there's nothing stopping you from gathering that information at runtime; it just typically isn't done to the same extent in dynamic languages as in ML-family languages. A fundamental limitation of dynamic analysis (that applies to any dynamic language, already invented or to be invented in the future) is its inability to “peek into the future”. For example, if you create an empty mutable list object, dynamic analysis can't tell you what its element type is supposed to be, until you actually start inserting elements into it. Static analysis, on the other hand, can determine what the element type is solely from inspection of the program text, e.g., if you make a procedure that inserts strings into the list, the list's element type must be a supertype of string, whether you actually call the procedure or not. Here's an example of a type error that Python couldn't possibly diagnose: http://ideone.com/0KUVVb http://ideone.com/0KUVVb > A type system that gathers lots of type information is equivalent to a strongly-typed language IMHO This isn't true. C++'s type system gathers a lot of type information, yet C++ isn't “strongly typed” (assuming that term means anything at all) by any stretch of the term. Rather than “strongly-typed”, a more useful term is “sound”. A type system is sound when it actually protects the language's basic abstractions. For example, Standard ML has a sound type system, and there's a formal proof of this fact. Java's type system is actually unsound, but it makes up for this deficiency by inserting runtime checks to turn wrong operations (e.g., invalid downcasts) into runtime exceptions (just like in Python!). C++'s type system is also unsound, and performing wrong operations is simply undefined behavior.
- milcron 10y agoStatic+strong typing is a nice combination, which is probably what hacker_9 meant to say. ML family is particularly nice -- with Hindley-Milner type inference, you get a lot of static guarantees with none of the verbosity of languages like Java.
- catnaroek 10y agoIt's not just type inference that makes programming in ML so nice. Also important are: (0) Parametricity: Type variables are never case-analyzed. As a result, types convey useful information about what functions may or may not do. Haskellers call this “free theorems”. Java's `instanceof`, C++'s `sizeof` and template specialization, and GHC's `TypeFamilies` are blatant violations of parametricity that make types less informative. (1) A clear distinction between data (sums of products) and operations (functions). In a general-purpose language, operations will always be somewhat ill-behaved: they may raise exceptions, fail to terminate, etc. Data tends to be better behaved, and algebraic laws can be stated about it, which hold even in the presence of effectful and/or non-terminating functions. This makes ML superior to Haskell, not to mention object-oriented languages. (2) A module system that enforces abstraction boundaries between subsystems of a larger system. ML's opaque signature ascription (which has no counterpart in Haskell or object-oriented languages) ensures that the internal representation of abstract types is only visible in the module where they're implemented. In a well architected ML program, trying to violate another module's internal invariants is a type error!
- devishard 10y ago> Static+strong typing is a nice combination, which is probably what hacker_9 meant to say. That's a rather generous interpretation, given that hacker_9 said nothing about strong types, and instead made a claim about static vs. dynamic languages. I do agree with your assertion, though; learning Haskell and OCaml prepared me for writing Python in ways that many of my peers in the Python community are unprepared. One big example is the difference between str and bytes in Python 3: a lot of Pythonistas find this distinction an annoyance, but I find it extremely useful, largely because I learned how to leverage such type differences to reduce errors from ML family languages.
- SixSigma 10y agoJulia is interesting in this regard (and Go I think - I used it's ancestor: Limbo). You can start with function foo(bar) pass end function and then add types later when you've found out what was inferred function foo(bar::Int64) pass end function I enjoy this style, it means I can create the story as I go along without re-writing types every time
- sdegutis 10y agoWait, does Julia really use the syntax `end function` to end a function? I haven't seen that kind of redundancy since I first learned how to program inside QBasic 3 for MS-DOS!
- catnaroek 10y agoThe benefit of inference is that you get the best of both worlds: you don't manually type-annotate anything, yet types are still statically checked. Does Julia do this?
- zeveb 10y agoCommon Lisp does this too: (defun foo (bar)) vice (defun foo (bar) (declare (fixnum bar) (ignorable bar))) And of course then the compiler can gripe if you try to call (foo "123").
- ozten 10y agoI think Rust borrows enough from ML to give you the skill he is pointing at. Static types with plenty of places where type is inferred.
- catnaroek 10y agoRust is wonderful, but inference is actually one of its weakest points. It has only one feature that's incompatible with global inference (multiple types can have methods with the same name), but sadly that's enough to require you to manually annotate `Foo<Bar<Qux<T>>, Herp<Derp<U>>>`s in a lot of places. Rust arranges things so that these places are often, but not always, function type signatures. In Haskell and ML, you can write tricky recursive functions without annotating a single type at all. Often, I couldn't even infer the type on my own in a reasonable amount of time! (By which I mean “under 5 seconds”, since my patience for these things is rather limited.) The type checker does it for me.
- vvanders 10y agoThat's not been my experience so far, mind providing a more concrete example? Only place I've had to annotate is when I'm using dynamic dispatch(&mut io::Write for instance) or collect() where I don't have enough information to determine type.
- catnaroek 10y agoIn Rust, function types need to be annotated. In Haskell or ML, I can have functions, especially recursive functions, with monstrous inferred types (although, of course, one then makes type synonyms for the sake of a more readable API) that would have to be explicitly annotated in Rust. The reason why this isn't much of a problem for Rust programmers, is that idiomatic Rust code uses vectors and slices much more often than recursive data structures. But if you tried to roll your own recursive data structures, like those from Okasaki's book, you'd quickly feel the pain.
- caconym_ 10y ago
- xntrk 10y agoI like the strong mission statement "it might be worth..."
- steego 10y agoHis qualifying words make him sound weak and pensive. My money says he's not even a modern visionary/renaissance man.
- muglug 10y agoMy alma mater's introductory CS course was entirely in Standard ML, and was a great (if occasionally painful) way to learn some of the fundamentals of CS without requiring any background knowledge.
- agentultra 10y agoYou get much more than type-checking from ML-style languages. It's worth learning one of these languages well enough to understand the fundamental concepts at play. If all you can see is static type analysis and arcane signatures you haven't dug deep enough yet. The renaissance of functional programming happening in JS is the only thing that makes me happy about working in that language. Having immutable.js, ramda, ramda-lens, redux, and various monads (such as data.task and data.maybe) makes my code much more effective than using the majority of JS's more dynamic features... and it doesn't even have static type analysis.
- catnaroek 10y ago> people routinely seem to get confused as to whether something is a value of a type, a strategy for producing values of that type, or a function that returns a strategy for producing the type. In other words, they aren't sharp enough thinkers. It's the same problem as conflating singleton lists or sets with their lone element, constant functions with their output value, or “any list” with “list of anything”. Dijkstra put it quite nicely: > About the use of language: it is impossible to sharpen a pencil with a blunt axe. It is equally vain to try to do it with ten blunt axes instead.