Hacker News new | ask | show | jobs
by nicoburns 1614 days ago
The real power of dynamic languages is being able to do:

    const foo = JSON.parse(arbitraryJsonString);
and not having to worry about the structure up front.
15 comments

When that JSON payload changes (intentionally or not), you will run into a mysterious problem in some unrelated area of your code. It will be significantly more expensive to fix than failing fast at the point of parsing.
But depending on the situation, that problem may never happen. I'm not a big fan of introducing complexity to guard against code screwups in the same codebase.
The hardest bugs are the rare bugs. Common and frequent problems are easy bugs to fix. These subtle or rare changes are those which should be feared. If you model only that which you support, finding the source of the problem becomes much easier.
But the thing is, don't you have to worry about structure? You have to unpack the elements from the JSON, so you will need to encode its structure explicitly, which includes type information. The only reason this would be useful is if you're just shuttling data to another API that expects a dict structure (that will then validate everything) and not JSON and you aren't really doing any real work yourself.
> The real power of dynamic languages is being able to do: const foo = JSON.parse(arbitraryJsonString); and not having to worry about the structure up front.

That's not power, that's a shotgun aimed at your crotch whose trigger is connected to a cosmic ray detector.

+NaN For comments that make me laugh out loud for duration T>2.0 seconds, I wish HN provided a way to transmute/sacrifice one's past karma points into additional +1 mod points.
Every static language that I know of also supports this -- you can parse into a sum type of `JsonAny` (or whatever), where `JsonAny` is one of `Null | Number | String | List[Any] | Dict[String, JsonAny]`.

The API then becomes a runtime fallible one, which is perfectly sound.

In C# I can just parse it to a Dictionary<string, object>, and I can do that without destroying the usability of the rest of the language.
What if the JSON represents a list, or an int?

Also, how do you then access nested objects, like data['key'][0]['attr'] in Python?

> What if the JSON represents a list, or an int?

Then you write one short operator (and I agree that some static languages make this more cumbersome than it should be) to say so, and either handle the case where it isn't, or explicitly declare yourself partial and not handling it.

> Also, how do you then access nested objects, like data['key'][0]['attr'] in Python?

With lenses, something like:

    data ^? (key "key") >>> (nth 0) >>> (key "attr")
If you do several unsafe operations in a row then this is cumbersome by design - you want to be clear which parts of your program are safe and which are unsafe, so that readers can understand and know where to review. But a good language should let you compose together several unsafe operations in a lightweight way and then execute them as a single unsafe operation, for cases like this where you want to work in the unsafe part of the language for a bit.
Sure there are solutions.

But my main point is that HideousKojima's "statically-typed" solution would result in a runtime type error if it was given unexpected input, just like a dynamically typed solution.

> But my main point is that HideousKojima's "statically-typed" solution would result in a runtime type error if it was given unexpected input, just like a dynamically typed solution.

I don't think HideousKojima ever called it a "statically-typed solution". Their point was that statically-typed languages still let you write unchecked code when you want to - and yes, of course such unchecked code can fail at runtime - but give you the option of having checking in the cases where you want it.

You can parse it to the dynamic type too. Everyone is happy...right?
Well in Python everything is an object, so that type definition holds true for Python as well :P
Rust:

    let foo: serde_json::Value = serde_json::from_str(arbitraryJsonString)?;
There, just as powerful [1]. But you know what's even more powerful? After you've done your dynamic checks, you can do this on the entire JSON tree, or on a subtree:

    let bar: MyStaticType = serde_json::from_value(foo)?;
and you get a fully parsed instance of a static type, with all the guarantees and performance benefits that entails.

[1] Value represents a JSON tree: https://docs.serde.rs/serde_json/enum.Value.html

There's something to this. I love featurefull type systems, but I've seen engineers try to parse JSON the "right" way in Scala, get frustrated, and blame the entire concept of statically typed languages. Elm manages to make this user friendly so perhaps it's "only" a matter of compiler messages and API design?
Structure is a virtue, not a vice. By doing this you're subverting your own interests.
Structure is a tool. Like any tool, it can be misused or overused.

For anything even remotely production-y I'll always prefer explicitly parsing JSON into a known structure, but there's a lot of value in in being able to do some exploratory scripting without those constraints.

Yes! Exploratory scripting is a categorically different thing than programming, though, I think.
not necessarily. there is a bottom up school of thought that encourages people to noodle around and construct primitives by playing in the domain, and then interactively composing those primitives into larger and larger systems.
Yeah, that's true. It's a judgment call for sure, but I've always found that angle on things to be self-subversive. The best programmers I know are all bottom-up learners, not top-down.
You can do that in static languages too by just parsing into a map
What's the type definition of the map out of interest?

  const Json = union(enum) {
      null,
      number: f64,
      bool: bool,
      string: []const u8,
      array: []const Json,
      object: HashMap([]const u8, Json),
  };

Usually it's a tagged union of the base JSON types which can easily be consumed by most statically typed languages or a variant of it.

EDIT: added "tagged"

In TS JSON is usually Record<string, unknown>.
Well unknown is not a type (by definition), so you have just stepped outside of a type system, which is very common in TS if I understand.
Unknown is a top type in the TS type system. It serves the very important role of "here you need to apply some pattern matching and validation" and then you can make sure that you can continue working in a type safe environment. TS has a lot of facilities that help you with this (from the usual narrowing things, typeof and instanceof guards, control flow analysis, and at the end of the list there are the big guns like this thing called type predicates which basically allows you to wrap these checks and "casts" in nice reusable functions).

There are also recursive types that help you model JSON, but knowing that it's an arbitrary deep nested map/list of maps/lists and number and bool and string mixed like a Bloody Mary cocktail doesn't really help :)

With NestJS it's very easy to add decorators/annotations to fields of a class, and the framework handles validation (throws HTTP 422 with nice descriptions of what failed) and then in your controller you can again work in a type safe environment.

https://www.typescriptlang.org/docs/handbook/release-notes/t...

https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...

Json = Map<string, Json>
In Nim you can even convert JSON to static types! https://nim-lang.org/docs/json.html#to%2CJsonNode%2Ctypedesc...

Now you get type checking on JSON at compile time :)

How powerful is this, really?

As soon as you try to do anything useful to foo it's not arbitrary anymore. You have to make some kind of an assumption on the underlying type, check for keys, nulls, maybe it's a number (the right number?), maybe it's a list. So now you have to scatter some boilerplate checks everywhere you touch a part of foo.

If you could parse it into a typed structure up front, you'd only have to deal with this in one spot, and have guarantees for everything else that follows.

Bonus: if your typed language has good support for records, you can even do this in a way that only provides structure to the parts you care about, and is robust to changes to any other parts of the json.

You can trivially do exactly the same thing in Haskell, so I think you’re suggesting that dynamic languages have no “real power”.
I thought the real power of dynamic languages was being able to do things like:

   eval('alert("hello, ' + userInput.name + '!")')
What's 'foo' and what you can do with it?
An arbitrary object? What else would arbitrary JSON parse into? Then you can access its properties, like with any JS object.
That’s not a capability unique to dynamic languages