The borrow-checker in Rust is kind of silly tool to use in the context of a managed language (that does GC) like Javascript. I don't get it. It's like using a backhoe to plant a few geraniums. Am I just not getting this?
Rust also has many, many features that are not the borrow checker. Some people prefer Rust because of those features, in spite of the borrow checker.
https://without.boats/blog/notes-on-a-smaller-rust/ is also one of my favorite bits of writing on this topic.
I’d use Swift a lot more (which has some of the features mentioned in the article) if the resulting code wasn’t limited to Apple OSes (the Linux support is crap). Or perhaps Kotlin if it wasn’t limited to the JVM, etc.
Or, is Go bigger because the GC represents a ton of complexity?
Node is a backend JS implementation. ClojureScript is compile-to-JS and can run on the backend in Node or on the frontend in browsers.
Rich is certainly a brighter individual than me, but some of his points are either him missing the point or being intentionally misleading.
For example, he discusses how Either types aren't true sum types because Either<A, B> isn't the same type as Either<B, A>. So he disparages people who say that Rust/Scala/Whatever have sum types. He's missing the point because 1) All Either implementations I've seen have the ability to swap the arguments to match another Either with the types backwards, so it's a sum type in practice, and 2) Clojure has none of it, so why criticize the typed languages by saying their type systems aren't perfect when your language's type system isn't helpful at all? Throw the baby out with the bath water?
To your specific point (which is also one of Hickey's), yes, it does kind of stink that loosening a requirement forces consumers to update their code. However, that minor downside does not mean that Optional is "not an improvement". It's still a HUGE improvement over Java's absurd handling of null (IIRC, Clojure is the same as Java there).
Also, maybe changing something to optional isn't really "loosening" the requirements. It's just changing the requirement. If the parameter changed to optional, don't you want to be alerted to that? Why is it optional now? What will it do if I pass None/null? Maybe I actually would prefer that to the way I called the old version.
It just never struck me as offensive to have to change my code when I upgrade a dependency. I have trouble sympathizing with that mindset.
Edit: And what is the Clojure alternative? You can loosen requirements, but really, you never had enforceable requirements anyway. Is it apples to apples to talk about a typed language loosening its contract?
that's such a weird argument! did he also complain that tuples aren't true product types because (A, B) isn't the same as (B, A) ? why would they be the same, and not just isomorphic?
His real point, I believe, was that the `Either` implementations weren't as good as true sum types because of ergonomics. It's part of his philosophy/bias that type systems get in the way and therefore cause more harm than good.
I don't really grok his point most of the time. It just feels foreign to me to not want as strong a type system as possible. But a lot of really smart guys feel that way: Him, Alan Kay, etc. I suspect that they're able to track much more stuff in their heads at a time than I am.
https://gist.github.com/rust-play/b28257cd7c48d0f9e9b1893181...
The type wasn't the only thing that changed, the possible values have changed. You may be getting more values than you were previously. This means that some of your assumptions may be incorrect. Of course your code needs to change.
Forcing callers to refactor their calls to use a new Option<T> type as a parameter simply amounts to a change in the type signature, but since the function is more liberal than before, it cannot break your assumptions (at least not assumptions based on the function type signature).
(For what it's worth, I do find Kotlin's T? to be more elegant than the Haskell/Rust-style Option/Some type. But then again, Kotlin is not fully sound, so there's that. Dart's implementation of T? will be fully sound though, so there are definitely examples of languages going that route.)
You could write <T: Into<Option<i32>> if you wanted, and your callers wont change.
Frankly, using options as parameters is just not generally good design, so this issue doesn't really come up very often, in my experience. There are exceptions, but it's exceedingly rare.
That's true. Even in annotated Python, I want to ensure all parameters are set before calling my functions/methods. Saves a lot of complications.
Ever since https://github.com/rust-lang/rust/pull/34828 you can transform any `f` that takes a `T` into an `f` that takes an `Option<T>` without any of the call sites changing.
For instance, look at this playground https://play.rust-lang.org/?version=stable&mode=debug&editio...
Your function `get_string_raw` which just handles `i32` can be transformed into a function `get_string` which handles `Option<i32>` without the thing calling changing how it calls the function. And the new `get_string` can accept `Some(i32)` or `None` or just `i32`.
Of course, this is slightly broad for brevity: you can now pass in anything that can become an `Option<i32>` but you can just define a trait to restrict that if you wanted.
You can get that sort of covariant effort that you wanted.
fn(a: int)
to fn(a: Option<int>)
?Technically, yes, all callers would have to update, but practically, you'd just define
fn(a: int) { fn_2(Some(a)) }
to avoid the breakage. That is, you're essentially telling the compiler how to loosen the requirements. Ergonomically, this seems rather fine. Especially if this means you gain (some) protections from the much more problematic case of restricting the requirements.Can you share why is that bad? the compiler will tell you exactly where you need to make the changes.
How many times have you written a function and a version later said "Oh, wait. I guess I don't actually need that required Foo parameter! I used to, but now I don't!"
Regularly if you're doing refactoring of code. Otherwise code becomes unchangeable because it's too big of a burden once it's clear it needs to change.
> And does that truly outweigh the benefit of being able to define a precise contract on your APIs?
I would point you to the XML standards which allowed people to do exactly that, and instead JSON won.
Are we talking about a published library or your internal-only code? If the former, I sympathize with the argument that relaxing a requirement should not force consumers to change their code. If the latter, then I find it much harder to sympathize. You're already refactoring your code, what is a few more trivial syntactic changes? You could almost do it with `sed`.
> I would point you to the XML standards which allowed people to do exactly that, and instead JSON won.
You know- this is an interesting point. And I guess I'm consistent because I absolutely hate JSON. I've only had to work with XML APIs very few times, but every time, it was perfectly fine! I could test my output against a DTD spec automatically and see if I did it right. It was great. JSON has JSON Schema, but I haven't bumped into in the wild at all. So it seems like "we" have definitely chosen to reject precision for... easy to read, I guess?
And what you'll find is that the tighter the contract, the more miserable the change you have to make when it changes. It's one thing if it's in one code base, it's another if it affects 10,000 systems.
And CORBA (after looking it up) seems to include behavior (or allow it, anyway) in the messages. That's about much more than having a precise/tight contract on what you're sending. It's much more burdensome to ask someone to implement so much logic in order to communicate with you. I'm fine with the contracts only being about data messages.
SOAP is closer to what I'm talking to. Or even just regular REST with XML instead of JSON.
I'm asking genuinely, how would life be worse between a REST + XML and a REST + JSON implementation of some service? In either case, tightening a contract will cause clients to have to firm up their requests. In either case, loosening requirements (making a field optional, for example) would not require changes in clients, AFAIK.
The only difference that I see is that one can write JSON by hand. And that's fine for exploring an API via REPL, but you surely don't craft a bunch of `"{ \"foo\": 3 }"` in your code. You use libraries for both.
It just seems insane that we don't have basic stuff in JSON like "array of things that are the same shape".
The IDL (interface description language) for CORBA is a contract. It defines exactly what can or can't be done. It's effectively a DTD for a remote procedure call, including input and output data. (Yes it can do more than that, but realistically nobody ever used those features)
A WSDL for SOAP is similar. CORBA is basically a compressed "proprietary" bitstream. SOAP is XML at it's core with HTTP calls.
> I'm asking genuinely, how would life be worse between a REST + XML and a REST + JSON implementation of some service?
So REST+XML vs REST+JSON alone (no DTD/XSD/schema) would be very similar -- other than the typical XML vs JSON issues. (XML has two ways to encapsulate data -- inside tags as attributes and between tags. Also arrays in XML are just repeated tags. In JSON they are square brackets []).
But lets say you need to change the terms of that contract (new feature usually), will code changes be required on client systems?
* If you used a code generator in CORBA with IDL the answer is yes, there will be code changes required.
* If you used a WSDL and added a new HTTP endpoint, the answer was no. If you added a new field to an existing endpoint, the answer was yes. (See [2])
* If you used a DTD/XSD, the answer is usually yes, since new fields will fail DTD validation using an old DTD -- that is if you validate all your data upon receipt before you process it.
And this was fine for services that didn't change frequently or smallish deployments.
In large systems, schema version proliferation became a nightmare. Interop between systems became a pain of never ending schema updates and deployments, hoping that you weren't going to break client systems. And orchestrating deployments across systems were painful. Basically everything had to go down at once to update -- that's a problem for banks, say.
What's sad to me is that was well known back in 1975. [1] When SOAP was developed around 2000 they violated most aspects of this principle.
> but you surely don't craft a bunch of `"{ \"foo\": 3 }"` in your code. You use libraries for both.
In python, JSON+REST is:
resp = requests.post(url, data={"field":"value"})
What I find really appealing in REST+JSON is that validation just happens on the server side, and that's usually good enough. Sure there's swagger, but that's a doc to code against on the client side.I don't feel that schemas and the need for tight contracts are all bad. I think if your data is very complex, a schema becomes more necessary than not when documents are bigger than a 1MB, say. I also think it's fine if your schema changes rarely. And yeah, if you need a schema for tight validation, JSON kinda sucks.
But that's the question, do you really need tight validation, and therefore coupling, or is server-side validation good enough? And in most cases people tend to agree with that.
[0] https://en.wikipedia.org/wiki/Service-oriented_architecture
[1] The Practical Guide To Structured System Design (1st ed.), Page-Jones, Yourdon Press, (c) 1980, pp103, footnote at bottom of page.
I'm not sure I follow. DTD, as far as I know, allows both optional elements as well as attributes. If you add a feature, a client with the old version should continue to work correctly if you add optional elements. If they are NOT optional, then the client will fail regardless of whether you did XML+DTD or JSON, because your API needs that data and it simply wont be there.
What am I misunderstanding?
> What I find really appealing in REST+JSON is that validation just happens on the server side, and that's usually good enough. Sure there's swagger, but that's a doc to code against on the client side.
As a client, you don't have to validate your request before you send it. But it's nice (and probably preferable) that you can.
>In python, JSON+REST is:
> resp = requests.post(url, data={"field":"value"})
requests is not built-in to Python, right? So you are still using a library to JSONify your data. If you were to use urllib, then you'd have to take extra steps to put JSON in the body: https://stackoverflow.com/questions/3290522/urllib2-and-json
What's more, you still are not crafting the JSON yourself if you call json.dumps on a dictionary.
But, yes, crafting a dictionary with no typing or anything is still many fewer keystrokes than crafting an XML doc would be, even with an ergonomic library. But again, how much are you doing what you typed in your real code? That looks more like something I'd do at the REPL.
Sure but, that begs the question, How is that better than JSON exactly? Maybe strong typing? And why isn't just sending a 400 Bad Request enough if the server fails validation?
I mean you could say well, "I know the data is valid before I sent it". But you still don't know if it works until you do some integration testing against the server -- something you'd have to do with JSON, anyway. XML is only about syntax, not semantics.
From what I've seen, XSD's tend to promote the use of complex structures, nested, repeating, special attributes and elements. And if you give a dev a feature, s/he will use it. "Sure, boss, we can keep 10 versions of 10 different messages for our API in one XSD" But should you?
JSON seems to do the opposite, it forces people to think in terms of data in terms of smaller chunks say. Yes you can make large JSON API's that hold tons of nested structures, but they get unwieldly quickly. And most devs would just break that up in different API's, since it's easier to test a few smaller messages than one large message.
> As a client, you don't have to validate your request before you send it. But it's nice (and probably preferable) that you can.
If you unit test your code, good unit tests serve as validation -- something you should be doing anyway. If you fail validation on your send, you have a bug anyway -- it's just you didn't get a 400 Bad Request message from the server. But to the user/dev, it's still a bug on the client side.
> requests is not built-in to Python, right?
Yes. But there's a lot of stuff not in the standard library that should be. The point is normal day to day code can be just a one-liner using native python data types.
> What's more, you still are not crafting the JSON yourself if you call json.dumps on a dictionary.
Sure, maybe a technicality here. If I type this, is it python or JSON?
{ "field": [ 1, 2, 3 ]}
Well, the answer is that both will parse it. json.dumps() just converts it to a string. No offense here, but I see it as a distinction without a difference.Typescript as well :-)
I suspect a lot people are drawn to Rust more because of its type system and great tooling. I'd be perfectly happy with a version of Rust that swapped out the borrow checker for a GC, but such a language doesn't exist today, and Rust does exist.
Rust is the only mainstream-ish language to really use them, though.
You can have dtors in non-affine types (types that implement the Copy trait) in Rust as well. I'm really only talking about C++ style destructors. Those don't require linear or affine types. But, I agree, that in a managed language, having a linear type is one way to get predictable destructors to run.
Strangely, Swift has deinit{} for its class types (ref-counted), but not for its struct types (value types).
That is such a silly meme. It's silly because it suggests that Rust is a one-trick pony, or that it only focuses on memory safety, which isn't true in the least.
Besides, the borrow checker has at least one other major upside: Show me another mainstream language in which safe and efficient shared-memory concurrency and parallelism is as easy as in Rust.
Speaking of which,
https://tinygo.org/webassembly/webassembly/
https://www.assemblyscript.org/runtime.html
http://dpldocs.info/this-week-in-d/Blog.Posted_2020_08_10.ht...
Yes, Typed Clojure addresses this problem somehow, as does Typescript.
But Elm, Rust, and the language formerly known as BuckleScript address the problem in a nicer and better-integrated way, to my mind.
>due to some misguided agenda that it’s safer (code is data!!!)
What does that even mean? Rust is a lot safer than C or C++, although of course here we're talking about effectively replacing JS so it's a different discussion.
I would personally love to write front-end code in Rust, not because it's necessarily the most appropriate language for such a task but because I'm familiar with it and can reuse what I already know. Basically the same justification people have used to bring JS outside of the browser.
Turing machines are not safe, code is data and it has no way of telling the difference. For instance, if I were to compile valid rust code, then open the binary in a hex editor and start changing the it rust can / will do nothing to stop it and so long as the code is valid it will run.
In Clojurescript interacting with the JS ecosystem is painful cause of its reliance on the closure compiler.
In Clojure, is almost the same, most Java libs are over-engineered and horrible to use but you need to reach for them because Clojure lacks an ecosystem.
So while Clojure is a better/nicer language than JS, the tradeoffs are not worth it if you want only one language for your webapp (SPA and server).
What "tons complexity" are you talking about? Clojure is a much simpler language (in the decomplected sense) than JS. Less syntax, better build tools, uniform stdlib, no webpack/babel nonsense. This sentence makes no sense.
> interacting with the JS ecosystem is painful cause of its reliance on the closure compiler.
Again, what? With tools like shadow-cljs, requiring JS libs and using them in you project is trivial (just require and import like you would any cljs library).
> In Clojure, is almost the same, most Java libs are over-engineered and horrible to use but you need to reach for them because Clojure lacks an ecosystem.
I've been writing Clojure for 10 years and rarely have I had to reach for Java. This is absolute rubbish.
You seem to hold strong opinions about a language you barely understand.
Edit: reading you comment history, you seem to have an axe to grind with Clojure.
I think Rich had to make a decision, writing a completely new LISP from scratch or leverage an ecosystem and create a LISP on the top.
>> In Clojure, is almost the same, most Java libs are over-engineered and horrible to use but you need to reach for them because Clojure lacks an ecosystem.
The biggest problem with Java libs the mixed quality. Many big data project have this problem, if you peek under the hood you gonna be amazed about abstractions leaking into different bits and pieces of the system. My favourite example how ORC imports Hadoop FS
https://github.com/apache/orc/blob/master/java/core/src/java...
There is one more problem with Clojure that I find annoying, the actual Java interop. I was running into issues with this many times and some Jave libs are almost unusable without a thin wrapper written in pure Java.
Other than that, I think Clojure is still one of the best options out there.
I usually avoid languages that keep reinventing the wheel of established libraries on the platform, "'cause it isn't idiomatic".
1. Clojure doesn't have as good interop as Kotlin.
2. For some heavy interop projects, it's simpler to write the thing Java.
And on the other direction is just as fun, try to call Kotlin co-routines from Java without having to write wrappers for it, or Kotlin code without putting @Jvm... annotations everywhere.
So for web development, do you use PHP?
Because neither Python, nor Ruby nor even JavaScript itself was designed for web development.
16 bit home computers could do this better 10 years before JS.
The original use-case at birth of a language matters less, as programming languages are living things that evolve and mature, sometimes morphing into very different things than they were at the beginning.
Likewise Rust's current lifespan has almost entirely been dedicated to taking on C/C++ and their use-cases in systems programming and server applications. That's all that really matters here.
Javascript was designed for web development of the 90s. It evolved along with web development.
Ada/SPARK, but it won't get a standing ovation from current generation of devs adopting Rust.
Luckily it gets some headlines exposure via NVidia and Genode OS adoption.
Anyway, it's hard to blame people for looking elsewhere when the best Ada compilers were proprietary for so long.
As for compilers being expensive, while true, I learned Ada via books a couple of years before being able to put my hands on a compiler.
Then again, maybe that is no longer fashionable way of learning.
Perhaps there are some rare scenarios where you need to eek out every little bit of performance... But in normal circumstances, typescript offers a fantastic combination of familiarity, expressiveness and ... if not type-safety, then at least some degree of type-sanity.
I do quite like TS, but you can never really get away from the fact that you’re still limited by many historical JavaScript gotchas, and the type system is nowhere near as powerful as Rust’s.
Edit: That being said, as a business decision, TS will often be a better pick because it’s so much easier to find talent, and the learning curve is much smaller.
https://www.typescriptlang.org/play?#code/C4TwDgpgBAygrgWygX...
So the real question imo should be which is better considering both frontend and backend. I suppose it’s also a very project-specific question. Maybe a heavy frontend project = ts, while heavy backend = rust.
Typescript gets better all the time, I didn't the last year or so run into anything I couldn't do in Typescript (although using it all the time, and Scala too)
If you have a Rust project that needs frontend code it's nice to be able to use the same language everywhere.
Advantages: You can use F# on the front and backend. Pretty good js interop with a lot of opportunities to shoot yourself in the foot.
Disadvantages: You will probably need to use the Elm docs/tutorials to learn about Elmish.
I'd like to hear more about this failed experiment. Reflex is alive and active and perhaps is the best tool out there for fullstack applications.
Reflex is distilled mutation and week reference black-magic, and the safe interface it provides relies heavily on higher kinded types. Even if someone goes though all the trouble to implement it or something like it (And that would be cool!), I don't think the resulting algorithms can be packaged up in nice abstractions.
Rust is a great language, but it just shouldn't bother competing where GC will do. It's just no point trying to win a race with that handicap.
------
I do believe in "one language for all tasks", actually. It's just that one language will have split ecosystems as it will support many idioms that don't interoperate well. Put another way, let's start with "multiple languages, perfect type-safe FFI", and then go for endosymbiosis.
I personally would be happy with many different languages that are focused on front end development, like how we already have many that are focused on back end development.
Reason is more expressive and on the web low level memory management isn't a concern.
A lot of people seem to be suggesting Rust as a GC language replacement instead of a C/C++/asm replacement, which I've never really got. Is the mutation/lifetime/pointer management really worth it for this kind of thing?
I am hopeful we can see the web ecosystem evolve into being capable of 'Single Language Web Applications' like Rust seems to be capable of, and C# as well. Of course there's Flutter, but it seems to just pain on a canvas instead of reusing the DOM.
I really fell in love with Elm early on, back when it was an experimental language for functional reactive programming that just happened to compile to JavaScript. It was an outgrowth of failed experiments in FRP from the Haskell world. I thought it got so many things right -- and it totally did. But then, just as soon as it started gaining real traction, development on Elm went silent and became siloed, staggeringly slow, locked-down, and unresponsive to users. I understand why this happened and I don't even hold it against the Elm team, but it certainly stunted the language's growth and adoption.
Rust has a much more expressive type system than Elm. The Rust world is much more open, responsive, and caring about user concerns. Rust isn't afraid to offer unsafe escape hatches even if they're not pretty or elegant. With Rust you have the added advantage of being able to use the language for the entire stack, both front- and back-end. That's especially compelling because Rust is on its way to being one of the strongest languages for back-end development due to its combination of type safety, expressiveness, and performance.