5 ms·
Stringly Typed
- GauntletWizard 2y agoAn API that uses JSON isn't "Stringly Typed". An API that lacks any validation on the JSON you pass to it is. Under their definition, nearly everything is stringly typed if if passes a system boundry, because serialization transforms everything into a string - Sometimes a byte string, sure, but you end up with transport-neutral single object whose interpretation is understood by metadata, and that's a good thing, because you don't need to waste time interpreting it at every layer it passes through. The modern advice to "Use a serialization library" is actually encoding several hard pieces of learning into one. There was a time when save files for most games were just memory dumps of large sections of memory. You dumped raw C-objects, including pointers to other objects, directly. You ended up with a tangled mess of references, but it was simple to write code for, cheap to write to disk, cheap to read from disk, and easy to break. Basically every update to a game broke all of the save files, because the most minor of tweaks could change the object layout generated by the compiler. The first change was to put magic strings at the beginning to inform the version - So at least you displayed an error message rather that executing some unexpected part of the save file as code. This lesson was hard learned as we entered the networked age, where you couldn't trust the incoming messages weren't malicious - And you certainly couldn't trust, with all of the terribly-behaving middleware, that they were well-formed. Writing serialization/deserialization code is not hard, but it's annoyingly rote, and you would need it for dozens upon dozens of classes. So instead we switched to standardized libraries for serialization and deserialization. Java and Python both had serialization libraries where whole objects could be serialized - Along with everything they referenced. This lead to massive security holes, because it was easy for them to take a huge chunk of working memory with them, because circular references to root objects allowed them to grab parts of other operations, or even application secrets. Python was worse, as the pickle library allowed serializing whole bytecode; Meaning every load was an arbitrary code execution. Modern serialization libraries have come to a compromise. They serialize data only in primitives. You have to rebuild the tangled web of cross object references yourself. This often sucks, but it's far better than the alternatives we've found. GraphQL is popular for precisely this reason. You can avoid most of the serialization and deserialization steps and query what you want directly, allowing you to access deeply-linked properties of deeply linked objects without the expensive round trips and security barriers being checked and rechecked; But the expressiveness comes at a distinct cost in terms of getting those barriers on the server side really right, because the default allow permissions make it easy to leak.
- bqmjjx0kac 2y agoNot trying to be mean, but there's not much content here. It's a definition of the term "stringly typed" (from another blog) followed by the idea of using appropriate types.
- zahlman 2y agoI guess the author is "one of today's 10,000", as they say. Wiktionary attests the term from 2019 but I'm sure I've been hearing it much longer than that.
- smnrchrds 2y agoThe post is a true web-log. Someone logged something they learned and put it on the web.
- gskye 2y agoxkcd has a relevant take on this: https://xkcd.com/1053/ https://xkcd.com/1053/ TLDR, we should totally be celebrating learning in public
- nothrabannosir 2y agoThat's not just a take, that's the origin of the phrase OP used :) In a beautiful meta moment, you are one of today's 10k about the origin of 10k :D
- codydkdc 2y agoI'm witnessing...something!
- matsemann 2y agoI first heard of it from Jeff Atwood in 2012, loads of fun concepts here I reference often. Favorite must be "shrug report" https://blog.codinghorror.com/new-programming-jargon/ https://blog.codinghorror.com/new-programming-jargon/
- 2y ago
- valorzard 2y agoI know JSON is the standard now, but are there “better” serialization formats out there? Especially since JSON doesn’t know what an integer is in the spec
- Analemma_ 2y agoI'm having a vision of XML reading your comment and going "well well well, look who's decided to come crawling back".
- zahlman 2y agoI can't imagine why. XML is still fundamentally, well, a markup language, not a serialization format designed as such. But the "extensible" part isn't so accurate - attributes aren't extensible. GP complains that JSON doesn't know what an integer is (as distinct from a generic number), but at least it does know more than just strings. And needing to repeat a tag name when closing it just adds useless complexity.
- wpm 2y agoIt’s not anymore useless than a closing } or ], except since it has the tag name in it, so when I’m reading a highly nested object I’m not stuck in my text editor looking at a bunch of }’s at random indentation levels I have to scroll all the way back up to regain any context for. Tags are text which is visual structure I can choose to read, or choose to gloss over and use as bulk to shape the data in my head.
- deleted 2y ago[deleted]
- bluGill 2y agoDefine better. As the other poster said, you could use XML which is more powerful, but as a result is a lot more complex. For most tasks I'd prefer JSON because while it is lacking, all the real world parsers I've seen are much easier to work with and I rarely need more complexity. If someone did a JSON++ (I have no doubt many people have but I'm not aware of them!) that added things like integers, without the complexity of XML that might be even better. In the real world if something should be an integer it isn't hard to check that and error out - you need to support parse errors in any data format anyway. Protobuf is sometimes better for data serialization. It isn't human readable, but you rarely need that and saving data bytes is often useful even today. Protobuf does have your integer type that you are missing, but it has other limitations might or might not apply to you. (I don't use protobuf enough myself to know what they are. Sqlite has more than once suggested that their database file is a great serialization format. You get a lot of power here and for complex things a database is often easier to work with than an xml file. There are various no sql databases as well that sometimes can work for this. I've handwritten my own serialization format in the past. The only hard part is designing enough the ability to add whatever the future needs are (note that I've never had to read my serialization on a different CPU family, things like little vs big endian I'm told can be a pain) There might be something else I didn't cover... Everything has pros and cons.
- lock1 2y agoUnfortunately you can't escape stringly-typed (and other mess) in language with structural type system.
- Ciantic 2y agoWhen Anders Hejlsberg did a lot of those talks to sell TypeScript, he described a lot of JavaScript as "stringly typed", which is very obvious with all the addEventListener("click", ...) and so on depending on certain strings. The term itself is not compliment, if you describe someone else's API as such, it's not well taken.
- nine_k 2y agoJS is also "stringly typed" in a sense that you can access object's properties by just string names, foo.bar and foo["bar"] is the same thing. It lead to a really nice Typescript feature, where you can declare something like type FooProp = "bar" | "baz", and the typechecker is smart enough to only allow these literal strings where you use values of FooProp type (e.g. when accessing properties by name, like above). This collapsed the whole crowd of strings, enums, and symbolic constants to just strings, without any loss of type safety, which I find a cognitive win.
- turnsout 2y agoYes, once you start noticing the (mis)use of strings, it's everywhere. I set my IDE to make strings a bright orange color so they're very noticeable in the code. With that said, you can overdo it. For example, if you're constructing a URL in an internal method that will never be seen by the caller, it's okay to just use a bare "https" without turning it into an enum like Scheme.HTTPSecure.
- titzer 2y ago> JavaScript is a weakly typed language without much type safety This is a little inaccurate. JavaScript is dynamically typed. Values carry strong, unforgeable type information (tags), though tags for numbers are extremely cheap and optimized away whenever possible. It is not possible that JavaScript "forgets" that 1.0 is a number and allows a program to use it as a "pointer".
- andai 2y agoI think this is referring to JS's unfortunate habit of doing nonsensical things with types unless the user takes special precautions. For years I thought I needed explicit static typing, until I tried Python, which is also dynamically typed, and found that I had none of the problems I had in JS. This is because Python is strongly typed. Indeed Python has the opposite problem of being a bit too pedantic with the type conversions. I thought it was interesting that C#, which is also strongly typed, lets you do string + number. (IIRC it has something to do with how they both descend from Object...) Worth mentioning that I do think static typing is a very good idea in any nontrivial program, and I wish more languages forced the programmer to be explicit here — TypeScript and even Rust have both bitten me in the ass with the type system making incorrect assumptions instead of just asking me (i.e. forcing me to actually specify the program and eliminate guesswork).
- chowells 2y ago> IIRC it has something to do with how they both descend from Object... It's far less interesting than that. The truth is that the symbol + means a wide variety of different things in the language, and it uses compile-time type information to select exactly which of those things it means. In the case where either operand is a string, it means string concatenation, with string conversion for a non-string operand if one exists. That specific conversion part is the only thing that cares that stuff descends from Object. The conversion algorithm is "call ToString() and let dynamic dispatch sort it out." (For completeness, the other meanings are various forms of addition, but adding two doubles is a different machine instruction than adding two floats. It really does still need to use type information to figure out which of those operations it is.)
- cartoffal 2y agoThe idea of "type safety over the network" is a fiction. When it comes down to it, what is being sent over the network is 1s and 0s. At some point, some layer (probably multiple layers) are going to have to interpret that sequence of 1s and 0s into a particular shape. There are two main ways of doing that: - Know what format the data is in, but not the content (self-describing formats) - in which case the data you end up with might be of an arbitrary shape, and casting/interpreting the data is left to the application - Know what format the data is in, AND know the content (non-self-describing formats) - in which case the runtime will parse out the data for you from the raw bits and you get to use it in its structured form. The fact that this conversion happens doesn't depend on the language; JS is no more unsafe than any other language in this regard, and JSON is no better or worse of a data serialisation format for it. The boundary already exists, and someone has to handle what happens when the data is the wrong shape. Where that boundary ends up influences the shape of the application but what is preferable will depend on the application and developer.
- rappatic 2y ago> The idea of "type safety over the network" is a fiction > When it comes down to it, what is being sent over the network is 1s and 0s ? When it comes down to it, all of computing is 1s and 0s. This is not some feature that's particular to the wire.
- lblume 2y agoThe difference being that a client can be malicious, while e.g. a local file is assumed to behave with the same intent as another. Programs that run on one computer can always be statically verified, while the task is harder for server-client applications — the client could always be an untrusted impersonator!
- JadeNB 2y ago> The difference being that a client can be malicious, while e.g. a local file is assumed to behave with the same intent as another. I'm not sure what it means to assume something about the behavior of a file, presumably thought of as a static piece of data, but I'd certainly disagree that a modern computing system is entitled to assume that all local apps behave with the same intent as one another (except to the extent that it assumes that all local apps behave maliciously).
- TeaVMFan 2y agoThis is one of my favorite things about the Flavour framework: strongly-typed web service calls: https://frequal.com/Flavour/book.html#org44d6b49 https://frequal.com/Flavour/book.html#org44d6b49 Your single-page app code calls your backend API using strong types. Your code is clean and the framework handles marshaling and unmarshalling JSON.
- beders 2y agoif you have control over all the consumers of your API, run whatever makes you fast and keeps you safe. If you want others to play with your API: JSON here we come.
- dleeftink 2y agoOp might like Structurae's Binary Protocol, type safe from door-to-door[0]. There are lot more interesting use-cases there! [0]: https://github.com/zandaqo/structurae https://github.com/zandaqo/structurae
- t-writescode 2y agoI have such an addiction to types that the first thing I do to anything as it comes in and out of systems I own (even across systems I own) is put it back into types and error all the things if it’s invalid. I assumed this was the regular action because it seems so much safer to me.
- somat 2y agoOpenbsd went with a stringly typed system for their pledge api. I think it was for simplicity of use. But i find it a very strange interface from a bunch of C programmers. https://man.openbsd.org/pledge https://man.openbsd.org/pledge update: Found the commit message Move to next tame() API. The flags are now passed as a very simple string, which results in tame() code placements being much more recognizeable. tame() can be moved to unistd.h and does not need cpp symbols to turn the bits on and off. The resulting API is a bit unexpected, but simplifies the mapping to enabling bits in the kernel substantially.
- strongly-typed 2y agomy nemesis
- teo_zero 2y agoAn example of "stringly typed" in C is when you have to pass "r+" to fopen().
- zzo38computer 2y agoYes, that is another example, and I had thought using numbers would make more sense (and you can use enum or #define to give names to those numbers). It is not only the fopen function in C that does that; I had seen similar things in other C libraries as well, as well as in other programming languages.
- zzo38computer 2y agoI think that JSON is overused (DER is better), but even without JSON, string data is also overused, in cases where numbers or other types would do better. (Unicode string types are also overused, but if a different type other than strings is better anyways, then Unicode is not the main issue here anyways.) > If you're using a strongly typed language like TypeScript, receiving the user object as any or unknown type is unfortunate. You'll lose all the type safety and you can only regain it with manual type checking. This is not specific to "stringly typed" stuff or to JSON, but is just the case when you transfer data that may use multiple types. In strongly typed programming languages, your program can parse it as the data that it expects and use an error handler when it is not what it is expected. (If you do expect that it may have any type, then you might be able to pass the unparsed value if appropriate; for example, I have a ASN1_Value structure in some of my C programs for this purpose.)
- inejge 2y ago> I think that JSON is overused (DER is better) When I was younger and more enthusiastic about some aspects of network communications, I set out to understand ASN.1 by reading the specifications. That was when ISO and ITU-T were even more stingy with access to their standards/recommendations, so it wasn't easy to get them as someone with no connection to standards bodies, and also without a few hundred CHF to spare. Reading those specs is an art in itself, but one gets a hang of it after a while. It went pretty well until this part of X.208: The resulting type and value of an instance of use of the new value notation is determined by the value (and the type of the value) finally assigned to the distinguished local reference identified by the keyword VALUE, according to the processing of the macrodefinition for the new type notation followed by that for the new value notation. That's where I burst out laughing and finally deeply understood why the ISO networking stack crashed and burned despite having some solid ideas. All this is to point out that yes, DER is not bad at all, but the whole infrastructure it rests on is simply too alien to people outside of telecom space and those who have to deal with it by necessity because of its use in various security protocols.
- deleted 2y ago[deleted]
- deleted 2y ago[deleted]
- cognomano 2y agoAs usual, insight from 2013: https://wiki.c2.com/?StringlyTyped https://wiki.c2.com/?StringlyTyped
- xg15 2y ago> What if we considered API endpoints to be remote function calls? What if we considered API endpoints to be part of the frontend applications instead of remote interfaces? I fear every 5-10 years, a new generation of devs discovers those questions, are awestruck at what they believe to be fundamental insights - and then go on to reinvent RMI. I would argue like this: "Stringly typed" systems inside a single process are an anti pattern. It's nonsensical for a process to write a constant string into memory, only to verify, character by character, that it's in fact that same constant string in later parts of the program. There are almost certainty other organization schemes that would make the program more type safe, more safe from bugs, less memory intensive and more performant. (Even in those situations, "string typing" can be acceptable technical debt if switching to such an alternate system would be very costly or work-intensive. You can still follow the "one good deed every day" pattern and try to reduce the string typing step-by-step whenever you're working on those parts of the code anyway.) BUT: "String typing" on network boundaries (or even just general IO boundaries) is something completely different: There, your underlying data model are strings (of bytes, not of ASCII chars/Unicode chars/whatever else) and every attempt to model a more "truthful" representation on top of it is essentially a fiction. This fiction is often very useful, but I'd always keep in mind that it is an abstraction, and provide an "escape hatch" that lets you easily map back to the wire format representation. Because that is what's relevant when you have to debug issues or investigate security vulnerabilities. Also: Never ever try to abstract away a network or IO boundary. Many have tried and it usually ends in tears.