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. 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.
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. 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!
A local file can be "newly local" and have recently been saved from the network or via a usb drive, etc.
And assuming a file is going to behave with good intent, or even the same intent as another file of the same format, is bad. It's how we get jpeg/png/etc parsing errors. Its how we end up with PDFs that are also valid executables, and 1000 more issues.
> 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).
What does that have to do with type safety though? If anything, type safety improves whichever piece of the puzzle you do have control over by reducing the likelihood of you accepting malformed data.
In many statically-typed languages, types do not exist at runtime - they are erased once the program is known to be internally consistent. What is left is not type safety, it is parsing and validation of unstructured binary blobs (or arbitrary strings, depending on the protocol) into structured data. Structure and types are not the same thing, and in many languages they barely even overlap.
Any input data has the same problem. Type safety exists after validation, and its guarantees hold only if your original validation was upheld.
Files, database, user input, network protocols, etc. I don’t know why the network would be any way special. You parse/validate unstructured binary blobs into structured data, and what’s left is type safety. It’s not in the runtime only because, if the compiler has done its job correctly, it is typesafe by construction.
In other words, how many times are you going to check your data structure is correct, before you start assuming it’s correct? Once — at parsing and validation — after that, you’re working with structured data, and your types are just recording the fact
> I don’t know why the network would be any way special.
The network isn't special. This applies locally too. But the article we are commenting on (at least, _I_ was commenting on) is about the network, and it uses the phrase "type safety over the network" in particular.
> You parse/validate unstructured binary blobs into structured data, and what’s left _is type safety_.
That is in fact exactly what I said. The point is that at some point you started with unstructured binary blobs. As soon as data leaves the application, it is no longer "safe", and it is unsafe until it is ingested and (re)validated. So my point can be freely generalised to "type safety beyond the application boundary is a fiction". And the application boundary will always exist, whether you are working with a strongly or weakly, statically or dynamically typed language.
While it is all 1 and 0, what those bits mean can easially be encoded. When you say what those bits mean in detail (which we need to anyway - what code page is that string), we can then assign what is valid, and in turn we can reject messages that while they are only 1/0 are still wrong. Also by assigning meaning we can get closer to what we want. the string "12345" and the number 1234 can both mean the same thing, but we can put one into 2 bytes if we want, while the string is at least 5 bytes. Not to mention a number is easier to parse, turning a string to a number is not always trivial (depending of course on which code page is active)
I agree with you. I think that except for memory cost, having everything in strings just moves the logic of validation of unknown or malformed strings from deserializer to validator method. But it must happen. And it will break nevertheless when new possible value occur.
You've just implemented POST and GET enum? Here's the PATCH. You have all http codes in your enum? And what about teapot? You had STARTING, PENDING, and FINISHED in your ``state`` allowed values? Our business analyst wants FAILED. Etc. Etc.
My point was more that the layers below the application will also have to parse the data into a particular format - in the case of networked applications, into TCP/IP packets, then anything particular to the message protocol, before hitting the application. And then the application will, at runtime (regardless of whether you are using a type safe language or not) have to parse and validate the shape of the data before it can be used.
Not to agree, but this is the same for files on disk or data in a database, where the malicious or misbehaving peer might just be an older version of the same program
Turing completeness and P completeness are completely different things. There is no sense in which P-completeness is a "more specific" version of Turing-completeness.
> The $ operator is nothing but syntactic sugar that allows you to write bar $ foo data instead of having to write bar (foo data). That’s it.
Actually, it's even simpler than that: the $ operator is nothing but a function that applies its left argument to its right one! The full definition is
f $ x = f x
(plus a directive that sets its precedence and association)
This kind of thing is emblematic of how little Haskellers care about readability and discoverability. I'm sure it's great for people that are already expert Haskellers but it adds yet another thing to learn (that's really hard to search for!) and the benefit is... you can skip a couple of brackets. Awesome.
Skipping brackets is incredibly useful for readability. Basically every language that relies on brackets has settled on a style convention that makes the redundant with indentation.
Admittedly, the big exception to this is function application. But Haskell is much more function oriented, so requiring brackets on function application is much more burdensome than in most languages.
As to searchability, this should be covered in whatever learn Haskell material you are using. And if it isn't, then you can literally just search for it in the Haskell search engine [0].
I agree with this sentiment. The one thing I liked about Clojure was the simplicity of the syntax, as long as you kept away from writing macros.
In general, after so many decades programming, I've come to dislike languages with optional periods (a.foo) and optional parenthesis for function calls - little gain to a confusion of precedence, what's a field vs method.
Seems that the whole DSL craze of 15 years ago was a mistake after all.
Having said all that, I think haskell is awesome, in the original sense of the word. I became a better programmer after working with it for a bit.
Haskell has it's issues, but this really ain't it. $ is idiomatic and used all over the place and is greatly more readable than stacking up brackets. The discoverability is also great because, like most things in Haskell, you can literally just input it into hoogle: https://hoogle.haskell.org/?hoogle=%24 and the first hit is, of course the definition of it. Which includes a full explanation what it does.
Because (as far as I know) that's not the case for most other languages. It's an interesting (and certainly not necessarily "wrong") way to program when you aren't sure about how to write something. Python has something similar, that I will occasionally utilize for this purpose, but it doesn't show documentation (as far as I know) or anything like that while it sounds like gchi has functionality kind of like CLI tools' "--help"?
With C#, intellisense takes the role of gchi and does pop up say the valid methods and properties of a class, and iirc includes documentation text.
So it's less about that haskell has "coding help" built in and more about how that's presented to and interacted by the developer.
To be clear, the core "issue" is whether developers use or like to use that functionality, and to that end I say I never use it for Python (obviously- since I didn't know of its existence). More succinctly, it seems like other languages have "more popular" alternatives.
Totally serious. I'm not claiming it's the best, but it's how I use the tools. There's a few reasons:
1. It's just how I learned. Googled my way through Python, then learned Haskell much later via a book which explicitly recommends using the REPL to get info about functions.
2. Types. I'm typically only looking for type information. A quick `:t` or `:i` is usually all I need in Haskell. I know Python half has them now, but I'm not
3. I'm certainly weird here, but I turn autocomplete and other pop-ups off. I find it distracting, and prefer the more intentional action of typing stuff into a REPL. Heck, I even do it in the VSCode terminal.
In summary: IDK, weird I guess. I should probably use it more in Python.
What do you mean by discoverability? What is the bar? Like is `:=` not discoverable in python. Or are ternaries not discoverable in Javascript? I'd say these are equally mysterious to a new learner. Just because $ is unfamiliar, doesn't mean it's worse than any other language construct.
Also, literally the first hit when you google “what does $ do in Haskell” is an explanation. So it’s in the docs, easily googlable, and one of the first things all learning material explains. How is that not discoverable?
No, it really doesn't. It values language ergonomics higher than learnability. That doesn't mean learnability is valued at 0. It has nothing to do with discoverability.
It wasn't easier, but it was simpler. Fewer moving parts.
The reason we moved away from it was that after a point, the amount of legwork to implement relatively simple behaviours became astronomical. Whether or not the current state of matters is "better", who's to say. But things are certainly much easier now.
Besides, you still can use those old modes if you want to :)
> Understanding the map signature in Haskell is more difficult than any C construct.
This is obviously false. The map type signature is significantly easier to understand than pointers, referencing and dereferencing.
I am an educator in computer science - the former takes about 30-60 seconds to grok (even in Haskell, though it translates to most languages, and even the fully generalised fmap), but it is a rare student that fully understands the latter within a full term of teaching.
Are the students who failed the pointer class the same ones in the fmap class?
I didn’t say “using map” I said understanding the type signature. For example, after introducing map can you write its type signature? That’s abstract reasoning.
Pointers are a problem in Haskell too. They exist in any random access memory system.
Whether pointers exist is irrelevant. What matters is if they're exposed to the programmer. And even then it mostly only matters if they're mutable or if you have to free them manually.
Sure, IORef is a thing, but it's hardly comparable to the prevalence of pointers in C. I use pointers constantly. I don't think I've ever used an IORef.
If you have an array and an index, you have all the complexity of pointers. The only difference is that Haskell will bounds check every array access, which is also a debug option for pointer deref.
Hard to believe that “learners ... get confused over mutability” more than functional programming when millions of middle-schoolers grokked the idea of “mutability” in the form of variables in Basic, while I (and at a guess, at least thousands of other experienced programmers) have no fucking idea about pretty much all the stuff in most of the tens or hundreds of articles and discussions like this that we've seen over the years. Just plain stating that “mutability is more difficult” without a shred of evidence ain't gonna fly.
That’s an unfair comparison because these are two unrelated concepts. In many languages, pointers are abstracted away anyway. Something more analogous would be map vs a range loop.
And I'd say the average React or Java developer these days understands both pretty well. It's the default way to render a list of things in React. Java streams are also adopted quite well in my experience.
I wouldn't say one is more difficult than the other.
IMO `map` is a really bad example for the point that OP is trying to make, since it's almost everywhere these days.
FlatMap might be a better example, but people call `.then` on Promises all the time.
I think it might just be familiarity at this point. Generally, programming has sort of become more `small f` functional. I'd call purely functional languages like Haskell Capital F Functional, which are still quite obscure.
> Just imagine Vampire Survivors without sound or effects.
I can't help but feel that this completely undermines your point - Vampire Survivors is bashed together using rudimentary knockoffs of sprites from games from the 1990s, in an engine which barely supports the idea of particles let alone proper visual effects.* It is the gameplay that carries Vampire Survivors, not the aesthetic.
Game feel is of course essential to producing a good game all-round, but a competent game designer can and will tell the difference between a good game design and a bad one, way before polish and juice are layered on top.
*I don't say this as a criticism - Vampire Survivors is fantastic - but the idea that it's propped up by its look is just daft.
The point of the OP (which I agree with) with is that the gameplay and the aesthetic are not orthogonal to one another. Even with Vampire Survivors which is not strictly beautiful the aesthetics are a big part of the gameplay. Largely the visuals and audio need to do several things:
- Make the game legible. A good example would be to reduce the whole game to boxes, you still need to differentiate things, so you might want to color them. Aesthetics in support of gameplay to make the game understandable.
- Add 'game feel'. This is where audio is especially important as you tend to notice the lack of good audio rather than its presence. But also 'juice', animations and what not all layer in.
- Support the fantasy. The name Vampire Survivors carries expectations that boxes do not match.
If you've ever done a lot of playtesting with your target audience one thing you'll find is that missing these elements gives you much worse feedback. Most notably legibility because it's so integral to being able to play a game but the others as well.
Game designers to an extent can get past this but it's still an attempt at extrapolation which is necessarily less concrete. Also if you're new to making games then you're going to make it harder to judge your own work.
The good thing about Vampire Survivors as an example is it shows that you don't need to do much but enough.
> Here are some monoids: string-append over strings, addition over integers, state transition over machine states, compose over unary functions.
Correction: function composition is not a monoid over unary functions, only over families of endofunctions (functions of type `a -> a` for some `a`). You can't compose a function of type `a -> b` with a function of type `a -> b` for example, when `a` /= `b`, because one gives back a value of type `b` which cannot be passed into a function expecting a value of type `a`.
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.