Live data from Hacker News

Dynamic type systems are not inherently more open

lexi-lambda.github.io

271–280 of 286 posts

Re: Dynamic type systems are not inherently more open

#271

Earlier quoted context omitted.

I'm quite sure it's not. Statically-typed programs can recursively parse data, and they can also monadically (sequentially) parse data. The same is true of dynamically-typed programs.

Likewise academically speaking, statically typed programs can simulate Universal Turing Machines, QED. If the detailed requirements describing the inputs require dynamic typing, then the statically typed program has to whip up a facsimile of that dynamic typing. It's not an implementation detail; it shows up in visible, testable system behaviors. And then, there you are, with the possibility of errors that are not ca…

I don’t get it. Why are you concerned with compile time?

The debate that the article is addressing is the supposed difference of capabilities between programs written with static vs dynamic types, at runtime.

Re: Dynamic type systems are not inherently more open

#272

Earlier quoted context omitted.

Likewise academically speaking, statically typed programs can simulate Universal Turing Machines, QED. If the detailed requirements describing the inputs require dynamic typing, then the statically typed program has to whip up a facsimile of that dynamic typing. It's not an implementation detail; it shows up in visible, testable system behaviors. And then, there you are, with the possibility of errors that are not ca…

I don’t get it. Why are you concerned with compile time? The debate that the article is addressing is the supposed difference of capabilities between programs written with static vs dynamic types, at runtime .

The situation is not written entirely with static types if the run-time processing requires the expression of dynamic typing. That's based on some actual dynamic typing from the language, or else some ad hoc dynamic types cobbed together in the program itself.

Re: Dynamic type systems are not inherently more open

#273
post #166

Earlier quoted context omitted.

If you think it helps to quickly write some code without types (I don't really agree with that in general, maybe in some specific cases), but you want to, when you're happy with the design, just add the types, there are many languages that support that! On the top of my head: * Dart (just don't give a type and the variable is dynamic) * Groovy (add @CompileStatic or @TypeChecked when you're ready) * Racket (start usi…

Look at this C++ code: std::vector > s; s.push_back(Pair ("a", MyInt(1))); s.push_back(Pair ("b", MyInt(2))); s.push_back(Pair ("c", MyInt(3))); Gross. I'm being punished. Look at this Rust code: let mut s = Vec::new(); s.push(Pair::new("a", 1)); s.push(Pair::new("b", 2)); s.push(Pair::new("c", 3)); Both are statically typed, but one uses inference. It practically feels like Python everywhere except where type declar…

“Look at this C++ code”

YOU BASTARD NOW I’M BLIND!

(In practice I’m pretty sure you’d typedef that, as much for maintainability as readability. Do agree though that type inferencing kicks the tar out of manifest types, at least up to the point the type inferencer gets lost.)

Re: Dynamic type systems are not inherently more open

#274
post #189

Earlier quoted context omitted.

I thought the article was good and addressed a real point of confusion, as evidenced by the two included comments (from Reddit and HN). You can consume arbitrary data using a program written in a statically typed or dynamically typed language. Whether it's decoupled from changes in the data depends on how the code is written and the data model, which have nothing to do with static vs dynamic typing. To me the stronge…

> You can consume arbitrary data using a program written in a statically typed or dynamically typed language. Really? Okay, below is something that meets the definition of "arbitrary data". Can some static program process it and evoke its full meaning without the programmer having to develop an ad-hoc dynamic typing system? (defun defset-expander (env macform name params newval setform) (with-gensyms (getter setter a…

You picked a strange example since so many Lisps were implemented in statically typed languages.

Here's one in C (less than 200 lines): https://carld.github.io/2017/06/20/lisp-in-less-than-200-lin...

Here's one in Haskell: https://www.defmacro.org/ramblings/lisp-in-haskell.html

Re: Dynamic type systems are not inherently more open

#275
post #274

Earlier quoted context omitted.

> You can consume arbitrary data using a program written in a statically typed or dynamically typed language. Really? Okay, below is something that meets the definition of "arbitrary data". Can some static program process it and evoke its full meaning without the programmer having to develop an ad-hoc dynamic typing system? (defun defset-expander (env macform name params newval setform) (with-gensyms (getter setter a…

You picked a strange example since so many Lisps were implemented in statically typed languages. Here's one in C (less than 200 lines): https://carld.github.io/2017/06/20/lisp-in-less-than-200-lin... Here's one in Haskell: https://www.defmacro.org/ramblings/lisp-in-haskell.html

That first one looks dynamically typed through a List * type, and tags put into pointers. That algebraic type in Haskell also looks dynamic: an Expr can be any one of four things.

Re: Dynamic type systems are not inherently more open

#276
post #166

Earlier quoted context omitted.

If you think it helps to quickly write some code without types (I don't really agree with that in general, maybe in some specific cases), but you want to, when you're happy with the design, just add the types, there are many languages that support that! On the top of my head: * Dart (just don't give a type and the variable is dynamic) * Groovy (add @CompileStatic or @TypeChecked when you're ready) * Racket (start usi…

* Objective-C

TBH, I think that’s as much accidental as by design. ObjC didn’t do generics until just recently, so the standard collection classes (NSArray, NSDictionary, etc) couldn’t specify element types so had no choice but to leave those APIs typed as `id` (effectively `void`).

Also, being a C, you still have to declare* all the types, even when only `id`, which is tedious AF. Also-also being a C (which doesn’t so much have a type system as a loose set of compiler directives for allocating memory on the stack), it’s not as if declaring exact types does much to improve program correctness. You can still stick any old object into an `NSString*` at runtime; it just dumps stack when you try to send it an NSString-specific message is all.

Oh, and one more: the ObjC compiler can have trouble recognizing selectors (method calls) when the receiver’s type is `id`. Being a C, obviously you have to import all the headers just to use them at all, but if in searching those headers it finds two method definitions with the same name but different parameters then its superficially Smalltalk facade quickly breaks down. (I’ve run into this, it’s very annoying.)

Re: Dynamic type systems are not inherently more open

#277
post #274

Earlier quoted context omitted.

You picked a strange example since so many Lisps were implemented in statically typed languages. Here's one in C (less than 200 lines): https://carld.github.io/2017/06/20/lisp-in-less-than-200-lin... Here's one in Haskell: https://www.defmacro.org/ramblings/lisp-in-haskell.html

That first one looks dynamically typed through a List * type, and tags put into pointers. That algebraic type in Haskell also looks dynamic: an Expr can be any one of four things.

This supports the case the article was making. Clearly the type system in Haskell doesn't prevent it from handling arbitrary data - it's hard to imagine a program more open to change in the input data than a Lisp interpreter.

You seem to have a set of rules that you think code in a statically typed language has to follow in addition to the type constraints in order for you to consider it sufficiently 'typed'.

Re: Dynamic type systems are not inherently more open

#278
post #277

Earlier quoted context omitted.

That first one looks dynamically typed through a List * type, and tags put into pointers. That algebraic type in Haskell also looks dynamic: an Expr can be any one of four things.

This supports the case the article was making. Clearly the type system in Haskell doesn't prevent it from handling arbitrary data - it's hard to imagine a program more open to change in the input data than a Lisp interpreter. You seem to have a set of rules that you think code in a statically typed language has to follow in addition to the type constraints in order for you to consider it sufficiently 'typed'.

Well, just one rule: if we are looking at type tags at run-time, it's dynamic typing! Whether that be things put into C pointers, or integer fields discriminating unions, or algebraic sum types with pattern matching.

If someone writes Haskell applications by defining an Expr type that is a sum of several dozen things, and then makes al their function arguments and return values of type Expr, is that still bona fide static typing?

Re: Dynamic type systems are not inherently more open

#279
post #258

Earlier quoted context omitted.

> Contract systems in general have the problem that it is difficult to express higher-order properties about how functions should interact with each other or repeated invocations of themselves Contract systems can express anything that can be expressed about a program. Some allow you to specify separate lemmas (e.g. see http://www.eecs.ucf.edu/~leavens/JML/jmlrefman/jmlrefman_toc... ) . The difference between depende…

> If dependent types would accept unsound proofs, the soundness of the entire type system would be compromised to the point of undefined behavior. This seems like too strong of a statement. Every language that allows for nontotal functions has a type system allowing for unsound proofs. This does not mean the type system is compromised to the point of undefined behavior. Dependent Haskell is explicitly going to be uns…

> If you lie to the type system you just blow up at runtime, e.g. with an exception. Same as in any other language.

That depends on the runtime type system. If you can switch an int for a string (which is not hard in a dependent type system -- rely on the output of some silly arithmetic function that you only tested and omit a proof and use it in some function that indexes types by integers, and you just might cast an int to a string), you will more likely get some UB and quite likely a security problem.

That's why I think that when it comes to correctness contracts are superior to types, and when it comes to tooling and organization, types are superior to contracts. Functional correctness is one area where flexibility is crucial, and soundness is never required because it's impossible anyway. Types, which are used for automatic transformation and code generation is one area where soundness is quite important; we shouldn't mix the two.

> Also out of curiosity, how do you express that, say, a static method with two arguments is associative in JML or any other contract system, Hoare Logic based or otherwise?

I don't know the arcana of JML, but I see no reason why, at least in principle, you couldn't do the same thing:

     pure
     \forall A x,y,z; foo(x, foo(y, z)) == foo(foo(x,y),z)
There's also no reason not to define a predicate `isAssociative`.

However, there is a difference -- not between specification with dependent types and contract systems, but between the semantics of the language where expressions mean something similar to "partial functions" (or even something similar to functions) and a language where they mean something else (like predicate transformers). In the latter, we don't in general represent computation as functions, so specifying something like associativity would normally be done as follows: define `isAssociative` on functions (not computations), and then specify that a computation computes a function (that is associative). That's how I would do it in TLA+, for example; I think that in Why3 as well.

Finally, it's important to remember that the very concepts can be different among languages. For example, all specifications of "higher-order" computations in functional languages (i.e higher order subroutines) become first order when you describe them in TLA+ (https://pron.github.io/posts/tlaplus_part3#higher-order-comp...). Lamport had this to say:

> Comparisons between radically different formalisms tend to cause a great deal of confusion. Proponents of formalism A often claim that formalism B is inadequate because concepts that are fundamental to specifications written with A cannot be expressed with B. Such arguments are misleading. The purpose of a formalism is not to express specifications written in another formalism, but to specify some aspects of some class of computer systems. Specifications of the same system written with two different formalisms are likely to be formally incomparable… Arguments that compare formalisms directly, without considering how those formalisms are used to specify actual systems, are useless.

The difference here is, then, not between specifying with types or with contracts, but between different formalisms.

Re: Dynamic type systems are not inherently more open

#280
post #90

Six notable things I took away from this post: - Structural typing, i.e. instead of "you eagerly write a schema for the whole universe", just limit to what you need (basically, encode only the same kinds of assumptions you would make in a dynamically-typed language). - It’s easy to discover the assumptions of the Haskell program [...] In the dynamically-typed program, we’d have to audit every code path — Left implici…

After giving it enough time and rereading, I feel I have a better understanding. The crux IMO is here:

> The above JavaScript code makes all the same assumptions our Haskell code does: it assumes event payloads are JSON objects with an event_type field, and it assumes signup payloads include data.user.name and data.user.email fields.

What the post points out is that these exact same assumptions, and the same behaviour of what to do with an unknown event type, can be encoded into the Haskell types and code. Similarly, in the case of “val = pickle.load(f)” in Python, the moment we try to do anything with “val” we'll inevitably make some assumptions about it, and those assumptions are its type. (For example, if all we assume that `val` has a `.foo()` which returns a string, then that type can be expressed in a static type system, even in Java.) And for proxying unknown data, just don't specify more in your type than you actually need.

So the post repeatedly emphasizes that static typing does not mean “classifying the world” or “pinning down the structure of every value in a system” (and conversely with dynamic typing we never can process truly unknown data: whatever we assume, that's the type). Overall, the key thesis (“static type systems are not fundamentally worse than dynamic type systems at processing data with an open or partially-known structure”) seems (to me) convincingly demonstrated, and it seems everyone who understands the argument can get on board with her conclusion:

> There are many patterns in dynamically-typed languages that are genuinely difficult to translate into a statically-typed context, and I think discussions of those patterns can be productive. The purpose of this blog post is to clarify why one particular discussion is not productive

----

However, I think the word “inherently” or “fundamentally” is doing a lot of work here, to the extent that if the words “are inherently” were to be replaced with “tend to be”, then a good argument could be made afresh. The post admits to much the same thing, around “These two styles facilitate very different flavors of programming”:

> many dynamically typed languages idiomatically reuse simple data structures like hashmaps to represent what in statically-typed languages are often represented by bespoke datatypes (usually defined as classes or structs). [...] A JavaScript or Clojure program may represent a record as a hashmap from string or symbol keys to values, written using object or hash literals and manipulated using ordinary functions from the standard library that manipulate keys and values in a generic way.

and points out that this has many advantages (“the practical, pragmatic advantage of a more structural approach to data modeling”) to the way things are done in Haskell, and even more so in “all mainstream, statically-typed OOP languages”. These advantages are coming to “modern statically-typed languages” (see list).

So does this defeat the point of the whole post, if ultimately the approach usually taken in dynamic languages has advantages over the approach usually taken in ("typical") statically-typed languages? No, it's an matter of clarity — it's worth it to be clear when the issue is structural-versus-nominal-typing, not static-versus-dynamic-typing. So all this:

> may give programmers from a dynamically-typed background who have historically found statically-typed languages much more frustrating to work with a better understanding of the real reason they feel that way

Post reply on HN