TypeScript - powerful type system but shit underlying stdlib and language (no pattern matching/switch expressions)
Dart - worse than TS because the object model is closed - so no dynamic freedom, but the type system and expressions are weaker then the rest. Also 0 meta programming facilities - Java level of boilerplate and code generators
C# - closest to ML featureset out of the mentioned, but unlike TS doesn't have sum types which will make a lot of things more tedious.
You'll probably have similar overlap between C# and F# implementations as you would with say TypeScript - they are just that different in practice IMO.
They're still waiting on the do expression proposal for that (https://github.com/tc39/proposal-do-expressions), which has been in the bikeshedding stage for the past five years.
And the React example makes no sense, you can use a ternary and it is even shorter.
``` return ( <nav> <Home /> {loggedIn ? <LogoutButton /> : <LoginButton /> } </nav> ) ```
I've used it a bit but I haven't found it very boilerplatey in general, so I'm interested in learning what contexts you run into that.
I'm assuming you're using it with flutter?
https://medium.com/dartlang/dart-3-1-a-retrospective-on-func...
-- a parser function that matches on { ... } returning ...
-- can be combined with other parsers
braces = (symbol "{") `between` (symbol "}")
...
-- a statement is a controlFlow or a declaration or an assignment
statement = controlFlow <|> declaration <|> assignment
-- a block is either 1 or more statements surrounded by { ... }
-- or a single statement.
block = (braces $ many statement) <|> statement
...
-- An expression is a number, or a variable, or a binary operation
-- derive functionality to print and test equality of these values.
data Expr =
Num Int
| Var Variable
| DualOp Op Expr Expr
...
deriving (Show, Eq)
...
foldConstants (DualOp Add (Num a) (Num b)) = Num (a + b)
...
Parser combinators (parser functions that take other parser functions and return more complex combined parser functions) with enough syntactic sugar can express BNF-like grammars directly as code. And ML-style list and pattern matching operations are very expressive for operating on the intermediate representation.It really hasn't. In this very post he had to use a visitor to work around the fact that switch isn't an expression.
JavaScript's support for iterators is also weirdly shit. It has `.map()` but that only works on arrays. You can't map an iterator!
(new Set([0, 1, 2])).values().map(x => x + 1).toArray()
You can also reduce, filter, find, etc.IMHO typescript could just cut loose from its javascript compatibility. Why not compile it to wasm instead of transpiling it to javascript? Kotlin is in the process of adding a wasm compiler and they already have a js transpiler. The same code results in a lot smaller binaries and loads a lot faster in wasm form. Browser Javascript is not a great compilation target. And who cares what the minified crap that loads in the browser looks like? The only reason for backwards compatibility is allowing javascript projects to easily transition to typescript. But that's increasingly less relevant now that a lot of projects start out being typescript from day 1.
Of course, Assembly script is already a thing. But why not make that a bit more official? It wouldn't surprise me to see people doing a lot of web development in languages like that in a few years.
Besides, enums (?) aside, and ignoring typechecking, compiling TS is really easy right now. Switching to WASM is a high ask.
People wanted a different language, they should have gotten more scripting languages in the browser. Not changing JavaScript so much that it's no longer JavaScript.
(things like the scoping rules for 'var' is just bizarre, same with the concept of 'prototypes')
Looking at typical "old-school" Javascript code gives me the creeps the same way as looking at K&R C code ;)
They aren't syntactic sugar? Pretty sure `class Foo { blah() {} }` is equivalent to `function Foo() {}; Foo.prototype.blah = function() {}`.
Totally different meaning.
These differences let you extend built-in things that simply can't be done with old prototype constructor syntax. [1]
[O] I should probably be using a more recent reference like MDN, but the 2ality series always sticks out in my mind whenever "just syntax sugar" comes up: https://2ality.com/2015/02/es6-classes-final.html#safety-che...
[1] https://2ality.com/2015/02/es6-classes-final.html#subclassin...
This page lists features from es6 (and newer versions linked at the top) along with compliance to the spec. First column is the current browser, second is babel+corejs polyfills.
Overall, babel gets about 70% of the way there.
In browsers the Wasm runtime doesn't have access to the DOM APIs. So that's wishful thinking ATM.
My fantasy for the past year has been, if I could magically program anything, bringing a compiler and spec wholesale into the world out of the void, I would create a new language (call it WebScript as a placeholder) that
- featured an ML style type system, ADTs, a type syntax nearly indistinguishable from TypeScript
- whose actual core language essentially resembled Kotlin or "Go with exceptions"
- compiled to WASM or JS as a compatibility bridge
Nothing radical. Nothing revolutionary. Just these things would be an immediate plus.
But maybe AssemblyScript is a good enough step towards that.
Well, you could in theory get benefits by having optimizations based on the type declarations. This could be awkward to do when Typescript allows zero-runtime-cost interop with untyped JS anywhere and allows any value to be cast through the `any` type and then to any other type. If Typescript with these optimizations is still intended to execute in the same way as its untyped JS equivalent, then the optimizations all need to handle being opted out of when an unexpected value is received, in the same way optimizing runtime-type-tracking JS engines have to be able to abort out of their type-based optimizations. This optimization would be equivalent to pre-warming a JS engine's knowledge of runtime type information, which is an optimization that could be done for JS in general rather than by doing it just in the Typescript project through compiling to WASM.
const c = try { … } const c = myAsyncFun()
.catch(e => …) string something = null;
try {
something = mayThrow();
} catch (SomeException ex) {
log.Info(ex);
} catch (Exception ex) {
log.Fatal(ex);
throw;
}
if (something != null) {
...
}
Would be actually nice if it had F#'s try...with https://learn.microsoft.com/en-us/dotnet/fsharp/language-ref... and would allow us something like: var something = try {
mayThrow()
} with (SomeException ex) {
log.Info(ex);
} with (Exception ex) {
log.Fatal(ex);
throw;
}
Or some way to do pattern matching on exceptions for switch expression. var something = mayThrow() switch {
(SomeException ex) => {
log.Info(ex);
return null;
}
(Exception ex) => {
log.Fatal(ex);
throw;
}
var passThru => passThru
} var something = (() => {
try {
return mayThrow()
} catch(SomeException ex) {
log.Info(ex);
return null;
} catch(Exception ex) {
log.Fatal(ex);
throw;
}
})();
Close enough? var something = (mayThrow()
??? (SomeException ex) => { log.Info(ex); return null} )
??? (Exception ex) => { log.Fatal(ex); throw; };
Or maybe just start using Results as return type and get ValueOrDefault :) But when it comes to handling exceptions, I think it explodes there with ifs and processing IENumerables: https://github.com/altmann/FluentResultsBut then again, simpler Results wrapper may be used perhaps. But it is a different way of coding and takes some mental shift on how to think about errors and distinguish between error results and true exceptions.
Oh, csharplang already had discussion on the matters about try/catch single statements: https://github.com/dotnet/csharplang/issues/786
You want to immediately return in the middle of something that was supposed to simulate just evaluating an expression?
You can't immediately return in the middle of 2+2 as well because return is a statement and can't be a part of expression.
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello, {s}!\n", .{"world"});
} fn potentiallyErrorReturningFunc() !u64 {
...
}
const foo = potentiallyErrorReturningFunc() catch { 0 };
One notable difference to most other languages is it being a value only, basically a product type of return value XOR error. These error values get serialized into a number by the compiler that is unique, and on the type system level you deal with sets of these error values. Quite clever in case of a low-level system, in my opinion.Nitpick, but AFAIK this won't work and needs to be rewritten either to:
const foo = potentiallyErrorReturningFunc() catch 0;
...or to: const foo = potentiallyErrorReturningFunc() catch blk: { break :blk 0; }
(I wish there would be a more convenient way to return a value from a scope block, not necessarily with a "dangling expression" like Rust, but maybe at least getting rid of the label somehow)I suspect you could do something like:
const c = attempt(() => ... );
where attempt invokes the lambda, catches any exceptions, and does what you wantIf an exception is caught, the expression evaluates to what the catch block evaluates to. (Similar to having if/else be an expression). Of course if the exception isn't caught then it propagates so the expression doesn't take a value.
const c = (
()=> {
try: {...}
}
)()Sure, you can check for everything. It often is more effective, but not prettier or easier to read and of course you also will miss cases.
In that case a message from a network either sends or fails and in the case of sending try/catch does nothing for you but provide an error object, and you don't need try/catch to capture the error. In the case of receiving a network message the message comes in or it doesn't. If the message does come in and is malformed your application should be mature enough to handle that appropriately to the benefit of the user instead of crapping out some error messaging to the console. You don't need try/catch to do any of this. A simple if condition is more than sufficient.
> check for everything
You should check for everything. You only need to check for one thing and if you check for it you are already at 100% of everything. Did you get what you expected: Yes/No?
Usually you don't need to just know if there is an error or not -- sometimes you need to know what that error is. For example, an I/O error versus your image-is-too-small error -- you need to respond to the user differently. I've seen plenty of web apps that treat I/O errors and user input errors as a generic "an error happened" and that is completely wrong. Proper exception bubbling means none of that code that encounters the errors needs to necessarily know how to handle the error and your app always responds correctly.
That said, I don't use exceptions as much in JS because exception handling in JS is still very nascent. There's no base library of exceptions and telling different exceptions apart is not built-in to the language. In a language with more mature exception handling, it's a breeze.
Its an equivalent breeze in JavaScript as well unless you are fumbling through abstraction layers (frameworks) that eliminate the information you need.
Since you are saying that you have to describe the problem within the error object, it sounds like you must be you are parsing error messages and stack traces to figure out what the error is. That's not how exceptions are to be used and I think that's why you are not seeing why you would bubble errors.
In other languages, you don't have to do any of that nonsense because object identity is much stronger, but JavaScript uses prototypical inheritance so you can't really tell if an error is an ImageError or an IOError. Despite this issue, exceptions were added to the JavaScript language without resolving that problem. The reason why you have to worry about frameworks eliminating error information is because that problem wasn't resolved and so frameworks roll their own error handling and there is no standard.
Changing the way you program to fit what a JS compiler does or doesn't do is a fool's errand, IMO. The performance benefits are likely to be minimal, confusion for anyone else who has to touch the codebase is high.
Much better to just write ideomatic code.
> The performance benefits are likely to be minimal
This makes me cry, but not a cry of joy or ecstasy. People guessing about performance is perhaps the most frequent anti-pattern in all programming. Please read the following document, you can skip to the end but it may not make much sense if you do.
https://github.com/prettydiff/wisdom/blob/master/JavaScript_...
When developers make random performance assumptions, defensive assumptions are worse, it immediately identifies a lack of professional maturity. Its like small children playing games that expand their imagination, which is great for small children but less great for developers pretending to be adults.
I disagree, premature optimisation is.
Changing your style of programming to suit what todays JIT does but tomorrow’s may not, in anticipation of performance issues you have not yet encountered is a waste of everyone’s time. No doubt it is valuable in extremely performance sensitive code but that is the extreme minority, especially in the JavaScript world.
If I were involved in a project where performance concerns were high enough to require everyone know what the JIT is and is not doing my proposal would be to use a language other than JavaScript. If thinking this makes me a “small child” in your mind I’m fine with that.
Premature optimization is the extra effort required to alter work necessary to circumvent guessed optimization pitfalls. In the same breath Knuth advocates always targeting known performance advantages.
Some advice I know you’ll ignore: your tone in the comments here is deeply patronising. You know absolutely nothing about me and yet are entirely comfortable dismissing my perspective as wrong simply because yours must be correct. It’s not an interesting or rewarding way to converse, it makes me want to stop talking to you as soon as I can. Which I’ll be doing here. Have a good weekend.
My best possible free advise: only advocate to what you practice. If, for example, you have never written an application in JavaScript without a framework, such as Angular, then you are an Angular developer not a JavaScript developer. If you speak to something you have never practiced with a presence of authority people with 10, 15, or 20 years experience will be condescending. That is why imposter syndrome is a thing. Its better to get the unexpected honesty from some irrelevant guy online than get hired into a job and either get it in person or worse compensating for a manager with imposter syndrome.
For what it’s worth, I’ve spent years working with the innards of various JS engines, managed deployments of JavaScriptCore inside iOS apps (fun fact: no JIT) and using QuickJS in low resource environments (no JIT there either). There’s an interesting conversation to be had around optimising your code for JIT, given the different environments we’ve both worked in. But your ego won’t allow it to take place. A shame for all concerned but in my years of development I’ve met plenty like you and I’m well aware that I’m not about to change your mind so I’ll just leave it there.
If this is an actual concern (as in you have measured and try/catch is actually affecting something performance sensitive): you can most likely mitigate the impact by isolating the try/catch bodies in separate functions.
I haven’t verified this in every JIT, but I saw meaningful perf improvement in V8 for real world degenerate cases where the performance did actually matter and make a significant difference. But seriously, as always, measure before optimizing stuff like this! It might matter, but it seldom will for anything more than academic curiosity.
This was only ever true in V8, and hasn't been the case since 2017 with the optimizing compiler switch from Crankshaft to TurboFan.
Take a break before lecturing commenters on "guessing about performance is perhaps the most frequent anti-pattern in all programming" next time :)
That's false. It used to be this way with Crankshaft but since 2017 we have Turbofan which solves this.
https://medium.com/dartlang/dart-3-1-a-retrospective-on-func...
There are no proper sum types in C#, so 80% of the point isn't even there.
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
... int PatternMatch(Season season) =>
season switch {
Season.Spring => 1,
Season.Summer => 2,
Season.Autumn => 3,
Season.Winter => 4,
// compiler can't prove the above code is exhaustive because no proper sum types
// compiler needs nonsensical branch here that diverges
_ => throw new ArgumentException("Invalid enum value for command", nameof(command)),
};Thats because c#s enum is not "closed"
Surely that will change someday.
There is a (now inactive) "interface-types" proposal, which has dissolved into "component-types", but this scope-creep definitely is already quite concerning:
JavaScript and TypeScript are not "proper" languages?
Different languages are powerful in different use case dimensions (tradeoffs), and the typical client-side web programming has a very very strong UI angle, which many "more powerful" languages that I am going to guess you have in mind are less adept at.
> JavaScript's redeeming quality is that it is ubiquitous due to being used by browsers.
JavaScript has other properties that make it very adept at a wide range of web app usecases. Languages exist not in isolation, but have a dynamic environment w.r.t. domains they excel in, tooling, mindshare, programming in the small and the large, comlexity, etc.
> I don't think anybody credibly claims it to be a language we would want to use if we were to redesign web programming from scratch today
Dart has tried. Many languages transpile to JavaScript (and have for a long time).
Yet, JavaScript and TypeScript are some of the most popular languages on the planet.
If the web is still around in 20 years, it will be interesting to see whether another language has taken center stage. I'm not quite holding my breath, if only because even with transpilation being around for many many years, no other language has overtaken. I have a hard time seeing WASM changing that outcome in a meaningful way.
Ouch, you posted this on the internet.
Somebody, somewhere is certainly going to claim this. Some of the people with that claim are even going to have a rationale behind it.
And to be fair, the tooling is going to push a lot of opinion. Because as bad as the JS/TS tooling is, its UI component has been evolving steadily while every other tool stagnated. And by now it has quite probably surpassed the 90's RAD paradigm (I mean, I can't make up my mind).
IMO, the language is one of the main things holding those tools back, but most people just won't agree.
I've programmed in Java, Python, C, C#, TypeScript, VHDL, and some other languages and they all fucking suck in some way.
If it was up to me, I would pick the best parts of the languages/ecosystems and make a Frankenstein language.
And anyway in the end, it's never the language. It's the ecosystem. Just because you can run Java on .NET doesn't mean you should -- because almost no one else does it your way and it's not worth the trouble maintaining some weird approach very few people use. You're never going to get help from library authors when it doesn't work on your weird setup.
Edit: And inline pattern matching for values returned from expressions and function calls (similar to destructuring, but more powerful).
Perfection is the enemy from good.
Those I can use at work, ML derived languages not, even F# is an uphill battle in most shops.