[HN Gopher] TypeScript is surprisingly ok for compilers
___________________________________________________________________
TypeScript is surprisingly ok for compilers
Author : Fudgel
Score : 264 points
Date : 2023-08-18 05:58 UTC (17 hours ago)
(HTM) web link (matklad.github.io)
(TXT) w3m dump (matklad.github.io)
| hardware2win wrote:
| Ive been writing compiler in C# and I dont see anything fancy
| here except union type
|
| Ive personally decided to avoid visitor pattern bloat and Im
| waiting for closed enum feature in order to have compile time
| exhaustive check
| AgentME wrote:
| I find myself really missing union types when I'm in a language
| that doesn't have either it or Rust-style enums. The usual
| alternatives are awkward: either one class representing the
| union type containing N nullable properties with the documented
| condition that only one property is ever expected to be non-
| null, a condition the type system isn't able to check or make
| use of, or multiple classes extending a common class, which
| feels really heavyweight. Both solutions require some
| duplication or clever composition if you want several different
| overlapping union types.
| andromaton wrote:
| Can I humbly add "Anders Hejlsberg" to the list of reasons why
| none of the commenters seem surprised?
| tomp wrote:
| As the post nicely demonstrates, TypeScript is definitely _not_
| OK for compilers (and not surprising at all!)
|
| It doesn't even have destructuring pattern matching!
|
| At this point, even Java is better [1].
|
| [1]
| https://github.com/tomprimozic/caya/blob/master/src/caya/Int...
| dom96 wrote:
| Pattern matching is definitely not a requirement for a language
| to be good for writing compilers in.
| [deleted]
| [deleted]
| vlakreeh wrote:
| With how powerful the type system is you can implement pattern
| matching via a library pretty convincingly,
| https://github.com/gvergnaud/ts-pattern is definitely the go-
| to. That being said pattern matching is hardly a requirement
| for being ok for implementing compilers.
| domlebo70 wrote:
| To OP: You could avoid the visitor by using an IIFE style switch
| using the run utility fn: export const run =
| <T>(f: () => T): T => f();
|
| Now you can go: const inferred_type = run(() =>
| { switch(blah) { ... } })
| mgreenw wrote:
| Exactly! I wrote a post about this pattern:
| https://maxgreenwald.me/blog/do-more-with-run
| domlebo70 wrote:
| Yeah this is where I heard about it :P I showed all my dev
| friends, and our minds were equally blown away by the
| obviousness of it.
| gavinray wrote:
| You don't even need to do this much, you can just invoke the
| function and let the return type be inferred.
| const inferred = (() => "Hello")() // Inferred as "string"
|
| If you really don't want to use an IIFE because you don't like
| the "()" at the end, you can just: const myFn =
| () => { switch(...) } const inferred = myFn()
| harha_ wrote:
| I'm sick of seeing overly complicated TypeScript code. Mainly
| overly complicated types.
| LeanderK wrote:
| compilers are really complicated pieces of software. There's a
| lot of complexity even in small routines. I would expect to
| encounter complicated types in there, they capture (while
| complex) intent and meaning.
|
| It's not the frontend glue-code kind of code. The inherently
| hard problem makes is fun for people who like to solve small
| puzzles.
| [deleted]
| newusertoday wrote:
| it isn't, i have codebase in golang that is much larger than
| typescript and it is pleasant to work with lsp and compiles
| smoothly. With typescript i have to turn off lsp and even after
| that it takes long time too compile. There is a reason why people
| are writing typescript compiler in rust.
| IshKebab wrote:
| He's talking about compilers for small languages. The
| Typescript LSP works fine on very big projects like VSCode so I
| think you'd need an enormous language like C++ or Rust before
| you'd run into those limits.
|
| But still, I think I'd rather use Rust. I'm pretty sure the
| code would be nicer (e.g. no need for the explicit tag field or
| for the visitor hack).
| tejinderss wrote:
| The author is no stranger to rust (he's creator of rust-
| analyser). The reason why he's pitching typescript here is
| due to its high level nature and doesnt have to deal with
| memory management, low level integer types etc.
| IshKebab wrote:
| I know. I'm just expressing my opinion that he hasn't
| really made me want to use Typescript over Rust for this
| case.
|
| I like Typescript and Deno. But I'd still rather use Rust
| here.
|
| I would love it someone made a RustScript though which
| would be basically Rust but with arbitrary precision
| integers, garbage collection etc. (but still value
| semantics).
| baq wrote:
| I see 'TS server has been restarted 5 times in 5 minutes'
| daily.
| iends wrote:
| I don't see this and I work on large enterprise websocket
| server with almost a million daily active users. In fact, I
| think we had 100% up time for the last 12 months except for
| a few AWS outages. Codebase is 10 years old and was
| CoffeeScript -> ES6 -> TypeScript.
| baq wrote:
| nono, I mean the LSP in vscode. prod deployment is
| unrelated.
| [deleted]
| andrewcobby wrote:
| As some how is writing a compilter in TS, I agree it's not too
| bad. I started with Deno like the author but ended up switching
| to Bun which, despite some rough edges, I'm enjoying more than
| Deno and it's _very_ quick! (My main niggle with Bun is the test
| reporting, but it's getting the job done)
|
| For standard parser generator frontend, Ohm-js[1] is quite
| pleasant. I wouldn't recommend anyone reviews the offical tsc
| compiler, it's huuuge - instead there's mini-typescript[2] (in
| particular the centi-typescript branch[3]) which does a nice job
| illustrating how tsc is working.
|
| [1] https://ohmjs.org/ [2] https://github.com/sandersn/mini-
| typescript/ [3] https://github.com/sandersn/mini-
| typescript/tree/centi-types...
|
| Looking forward to GC an DOM access in WASM.
| lmm wrote:
| Is that really suprising? Typescript is yet another language that
| has, kicking and screaming, picked up most of the ML featureset.
| I'd expect it to be, well, fine; the lack of real pattern
| matching is a pain, so it's going to be inferior to OCaml, but
| fine, no different from using C# or Swift or Dart or Kotlin or
| something of that ilk.
| madeofpalk wrote:
| Lack of pattern matching is annoying, but Typescript's tagged
| unions and type narrowing from if-statements are a good
| substitude and make for a very pleasent time traversing ASTs.
| C# has it's own way to express similar data structures (with
| subclasses and interfaces), but I find unions+narrowing much
| more natural in Typescript.
| DrScientist wrote:
| Dart pattern matching?
|
| https://medium.com/dartlang/dart-3-1-a-retrospective-on-func...
| IshKebab wrote:
| > picked up most of the ML featureset
|
| 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!
| bakkoting wrote:
| You can now! The iterator helpers proposal is stage 3 and
| shipping in Chrome. (new Set([0, 1,
| 2])).values().map(x => x + 1).toArray()
|
| You can also reduce, filter, find, etc.
| andyferris wrote:
| I think there's an iterator proposal out there for JS with
| generic map, filter, etc.
| jillesvangurp wrote:
| I've done a bit of typescript and kotlin-js. It always strikes
| me how close those two languages are. Yes there are lots of
| differences but they aren't that different and you can
| transition from one to the other pretty easily. I have my
| preferences (kotlin) but I can work with both.
|
| 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.
| jbreckmckye wrote:
| > IMHO typescript could just cut loose from its javascript
| compatibility. Why not compile it to wasm instead of
| transpiling it to javascript?
|
| 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.
| srhtftw wrote:
| I've been hoping to see alternative targets to JS for TS as
| well. WASM makes a lot of sense but TypeScriptToLua1 also
| looks interesting.
|
| 1: https://typescripttolua.github.io
| jitl wrote:
| Go has exceptions: you can use `panic` to throw/catch just
| fine. The community will bring out the torch and pitchforks
| because it's "not idiomatic" but if you're programming solo
| or with other pragmatists don't let it stop you.
| skybrian wrote:
| Does the community care if it's not in a public API? If
| it's caught within the same library it doesn't affect
| anyone else.
| madeofpalk wrote:
| Or just pass pointers because you need optionality, and
| get null pointer exceptions for free :)
| andrewcobby wrote:
| I'm actually working on a language like this. I quite liked
| ReScript/ReasonML, but having to manually write binding to
| use TypeScript or JS code is a drag. I'm making a
| functional language that looks and feels like TS and lets
| you import TS directly. Mostly just stripping imperative
| statements, removing class declaration and adding pattern
| matching and better data constructors (never liked the
| discriminated unions). WASM as a target is a bit further
| off.
| Kyro38 wrote:
| > IMHO typescript could just cut loose from its javascript
| compatibility. Why not compile it to wasm instead of
| transpiling it to javascript
|
| In browsers the Wasm runtime doesn't have access to the DOM
| APIs. So that's wishful thinking ATM.
| AgentME wrote:
| Typescript is defined to run exactly as the equivalent
| Javascript you get when all the type declarations are
| stripped out of the source. There's no benefit in compiling
| it to WASM unless you had a WASM JS engine (or JS->WASM
| converter) that executed the JS faster than the browser's
| built-in JS engine (or created WASM binaries that were
| smaller than compressed minified JS, which seems extra
| unlikely when you'd have to include a JS runtime). If that
| existed for general JS, browsers would just upgrade to use
| that internally on all JS.
|
| 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.
| hibbelig wrote:
| Your parent suggested to make changes to TS so that TS is
| no longer compatible with JS (in the sense you described).
| Once that happens, compiling to WASM instead of transpiling
| to JS is a very valid design choice.
| wiseowise wrote:
| The whole purpose of TS is better JS.
| [deleted]
| orra wrote:
| I'm afraid that's wishful thinking. JS compatibility is core
| to the TypeScript ethos, regardless of TypeScript's own
| popularity.
|
| Besides, enums (?) aside, and ignoring typechecking,
| compiling TS is really easy right now. Switching to WASM is a
| high ask.
| horsawlarway wrote:
| JS compatibility is core to Typescript's own popularity.
| [deleted]
| flohofwoe wrote:
| "Modern" Javascript essentially _is_ Typescript without the
| type hints (e.g. if you ignore the historical baggage, JS is
| actually a fairly decent language).
| usrusr wrote:
| Yeah, having just jumped into ts after a long js hiatus
| since back when The Good Parts was still surprising, it's
| quite awesome to see how much of what I assumed to be "ts
| stuff" is actually just postdeluvian js. Makes ts more
| attractive, not less. I do wonder however of it was
| possible to identify parts of that language superset that
| are _fully_ redundant (as in not even required for exotic
| edge cases) and let loose some draconian linter?
| jakubmazanec wrote:
| You already can kinda do that using ESLint and rules like
| https://eslint.org/docs/latest/rules/no-restricted-
| syntax, https://eslint.org/docs/latest/rules/no-
| restricted-propertie... or
| https://eslint.org/docs/latest/rules/no-restricted-
| imports.
| usrusr wrote:
| Sure, but that's still seems quite mix and match,
| anything goes, choose your own adventure. There are many
| positives about this approach, but as a learner a little
| more common ground between codebases would certainly be
| appreciated.
| junon wrote:
| I actually argue JS was a better language before all of the
| changes made, starting with const/let. The only thing I'd
| say makes sense are classes but the fact they aren't syntax
| sugar over prototypes was a mistake.
|
| 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.
| baq wrote:
| you can still write the old javascript. it'll feel very
| lonely, though.
| shrimpx wrote:
| > aren't syntax sugar
|
| They aren't syntactic sugar? Pretty sure `class Foo {
| blah() {} }` is equivalent to `function Foo() {};
| Foo.prototype.blah = function() {}`.
| junon wrote:
| They are not, no.
| shrimpx wrote:
| My example is incorrect because it doesn't cover all the
| edge cases but you can desugar es6 to es5, except in es5
| you can't subclass builtins.
| zdragnar wrote:
| There are a few very specific differences. In a subclass,
| 'this' doesn't exist until you call "super" for example,
| and the constructor will throw an error of invoked
| without the "new" keyword. [O]
|
| 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...
| shrimpx wrote:
| Thanks. It sounds like subclassing built-ins is where
| transpilers hit a hard wall. Other syntactic features of
| es6 can be transpiled.
| bornfreddy wrote:
| > the fact they aren't syntax sugar over prototypes was a
| mistake
|
| Totally different meaning.
| shrimpx wrote:
| What else would 'syntax sugar over prototypes' mean?
| cxr wrote:
| The result of `let x = Foo()` when Foo is defined as a
| function is whatever Foo's return value is. Trying `let y
| = Foo()` when Foo is defined as a class throws a
| TypeError.
| flohofwoe wrote:
| Coming into web development fairly recently (after 2013
| or so) from the statically typed hemisphere and without
| closely following the Javascript history from the start I
| naturally cannot agree ;)
|
| (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 ;)
| moonchrome wrote:
| That's some very high level view - in reality even tough those
| languages are in similar categories the experience would be
| vastly different :
|
| 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.
| crooked-v wrote:
| > no pattern matching/switch expressions
|
| 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.
| yunohn wrote:
| I'm not clear on the benefits of this proposal over just
| using anonymous functions. Some of the examples just seem
| contrived.
|
| And the React example makes no sense, you can use a ternary
| and it is even shorter.
|
| ``` return ( <nav> <Home /> {loggedIn ? <LogoutButton /> :
| <LoginButton /> } </nav> ) ```
| baq wrote:
| for me the point is to be able to return from inside
| multiple expressions from the correct scope i.e. the root
| function without throwing.
| doctormanhatten wrote:
| Having written quite a lot of Reason react (similar in
| syntax to the proposed React example) - while in this
| particular case because the example is so simple the
| ternary looks nicer, its also nice to just have a longer
| expression block in the middle of your JSX when you're
| doing more complex things.
| graftak wrote:
| What if you want a `switch` instead?
| graftak wrote:
| The proposal for pattern matching syntax seems more akin to
| what they're looking for.
|
| https://github.com/tc39/proposal-pattern-matching
| munificent wrote:
| Your understanding of Dart sounds a little outdated. We have
| a fully sound static type system and the language is pretty
| expressive, especially with the new pattern matching stuff in
| 3.0:
|
| https://medium.com/dartlang/dart-3-1-a-retrospective-on-
| func...
| pja wrote:
| If you're going to use C# for a compiler, why not go the
| whole hog & use F#?
| delta_p_delta_x wrote:
| I was going to mention F#. The parent and grandparent
| comments mention C# and ML, and the intersection of the two
| is... F#.
| moonchrome wrote:
| No argument from me, just saying lumping in all those
| languages together sounds reasonable in theory, in practice
| they are very far apart.
|
| 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.
| jezzamon wrote:
| Could you speak more to the dart, and specifically the "Java
| level of boilerplate and code generators"?
|
| 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?
| thebears5454 wrote:
| Naively, I would have guessed it being harder than C#
| baq wrote:
| The biggest expressivity pain point in practice for me is
| try/catch not being an expression, so I can't do
| const c = try { ... }
| not_alexb wrote:
| I use iife for stuff like this.
|
| const c = ( ()=> { try: {...}
| }
|
| )()
| smcl wrote:
| Would you not just move that `try { ... }` block into a
| separate function or method?
| austin-cheney wrote:
| I never ever use try/catch in my code. The only time its
| really necessary is to wrap JSON.parse on use of untrusted
| input. For everything else it just feels sloppy as through
| there is insufficient logic in place for handling primary
| conditions versus edge cases. Also, try/catch will never
| compile in the JIT.
| afavour wrote:
| > Also, try/catch will never compile in the JIT
|
| 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.
| madeofpalk wrote:
| And, JS engine's whims change frequently enough that
| yesterdays micro-optimisation is today's deopt.
|
| Much better to just write ideomatic code.
| austin-cheney wrote:
| That depends on how many changes it requires. If its just
| a matter of don't do these 3 things and your code
| suddenly becomes more predictable its like being slapped
| with a magic wand. Everybody wins. All you have to do to
| ensure 100% of your code compiles in a JIT is be
| predictable. Predictable code is, on its face, always
| less confusing.
|
| > 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/JavaScri
| pt_...
|
| 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.
| afavour wrote:
| > People guessing about performance is perhaps the most
| frequent anti-pattern in all programming
|
| 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.
| austin-cheney wrote:
| What you are advocating, the guessing about performance,
| is the premature optimization. Don't feel bad, most
| developers have no idea what that term really means. Here
| is the original essay where it comes from: http://web.arc
| hive.org/web/20130731202547/http://pplab.snu.a...
|
| 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.
| afavour wrote:
| > Don't feel bad, most developers have no idea
|
| 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.
| austin-cheney wrote:
| Yes, I work in a language where junior developers and
| people deeply unaware of the language advocate anti-
| patterns and bad practice as matters of law, often for
| personal defensive reasons. Its hard to not feel
| patronized. You are correct in that I do not know you,
| but I have been doing this work long enough to see when
| people hide behind abstractions they don't understand as
| necessary to mask their level of confidence and then
| argue from for best practices from that perspective.
|
| 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.
| afavour wrote:
| This is exactly what I'm talking about. You are assuming
| I am inexperienced and have no idea what I'm talking
| about because I have a different view to you.
|
| 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.
| macguillicuddy wrote:
| Perhaps the place I use it most is that try/catch is how
| you deal with rejected promises that you've `await`ed in an
| `async` function.
| simlevesque wrote:
| > Also, try/catch will never compile in the JIT
|
| That's false. It used to be this way with Crankshaft but
| since 2017 we have Turbofan which solves this.
| eyelidlessness wrote:
| > Also, try/catch will never compile in the JIT.
|
| 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.
| raxxorraxor wrote:
| For everything with the slightest bit of I/O it is quite
| practical if you have even the most trivial assumptions
| about its form.
|
| 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.
| austin-cheney wrote:
| This sounds like some hyperbolic framework nonsense.
| First of all I/O just means access to an API. I am going
| to presume you mean messages from a network.
|
| 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?
| thrashh wrote:
| Disagree.
|
| 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.
| austin-cheney wrote:
| You made the same mistake as the grandparent by assuming
| I/O means something more specific than it does. At any
| rate there isn't a good reason to bubble error objects
| out of the event handler and secondly even if you did you
| would have everything you need to fully describe the
| problem within the error object including the stack
| trace, event type, error message, and so forth.
|
| Its an equivalent breeze in JavaScript as well unless you
| are fumbling through abstraction layers (frameworks) that
| eliminate the information you need.
| thrashh wrote:
| Not at all. You don't have to describe anything when
| throwing an error because that's the point of exception
| inheritance. Second, the _whole_ point of exceptions is
| to bubble them out of the handler(!) because otherwise
| you would use return values (like in Go).
|
| 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.
| magicalist wrote:
| > _Also, try /catch will never compile in the JIT_
|
| 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 :)
| jve wrote:
| Yes! This just drives me crazy: 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 }
| scotty79 wrote:
| var something = (() => { try { return
| mayThrow() } catch(SomeException ex) {
| log.Info(ex); return null; }
| catch(Exception ex) { log.Fatal(ex);
| throw; } })();
|
| Close enough?
| baq wrote:
| not really; can't immediately return from the lambda
| caller without throwing, always have to either try/catch
| anyway or if/else the return value of the lambda. (also,
| it's rather ugly to read and inconvenient to write.)
| scotty79 wrote:
| > can't immediately return from the lambda caller without
| throwing
|
| 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.
| jve wrote:
| Another idea, introduce ??? that would invoke on
| exception :) 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/FluentResults
|
| But 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
| thdespou wrote:
| Zig can though: const std = @import("std");
| pub fn main() !void { const stdout =
| std.io.getStdOut().writer(); try stdout.print("Hello,
| {s}!\n", .{"world"}); }
| zkldi wrote:
| that's not using `try` as an expression. can you do `let
| foo = try <code> (plus some handling for diverging in the
| other case?)`
| flohofwoe wrote:
| Yes, 'try' can be used like an expression in Zig which
| resolves to the unwrapped success-result of an error-
| union, or in case of an error immediately passes the
| error-result up to the calling function.
| kaba0 wrote:
| It can and you in fact either have to mark that you
| return that error when you use try, or you have to
| provide a default value in a catch block:
| 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.
| flohofwoe wrote:
| > const foo = potentiallyErrorReturningFunc() catch { 0
| };
|
| 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)
| xscott wrote:
| What does a try expression like this do? Returns
| null/undefined on a throw?
|
| I suspect you could do something like:
| const c = attempt(() => ... );
|
| where attempt invokes the lambda, catches any exceptions, and
| does what you want
| lmm wrote:
| > What does a try expression like this do? Returns
| null/undefined on a throw?
|
| If 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.
| afandian wrote:
| Kotlin has this feature (`try` and `if` are expressions not
| statements), and a good model for return values.
| https://kotlinlang.org/docs/exceptions.html#exception-
| classe...
| lovasoa wrote:
| With async code, there is const c =
| myAsyncFun() .catch(e => ...)
| WorldMaker wrote:
| That leaves c as Promise which was not the type they were
| looking for. You'd still have to unwrap the promise and
| deal with the result or rejection. The easiest way to do
| that is to use await, and if you are using await you'd be
| better off using try {} catch {} around the async function
| than .catch().
| dejawu wrote:
| I'm quite fond of this library for pattern matching; it's a
| staple in all my new projects.
|
| https://github.com/gvergnaud/ts-pattern
| retrac wrote:
| Some crude snippets from a Pascal-C (I never could decide)
| self-educational compiler/toy I wrote a while ago (in Haskell):
| -- 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.
| xvilka wrote:
| This is why once WebAssembly more polished and more common,
| languages like JS and TS could become obsolete since proper,
| more powerful languages will suddenly become a valid choice for
| Web.
| thrashh wrote:
| What language would that be?
|
| 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.
| mbork_pl wrote:
| You mean, like C and its derivatives became obsolete once
| successors to PDP machines were built? ;-)
| andsoitis wrote:
| > This is why once WebAssembly more polished and more common,
| languages like JS and TS could become obsolete since proper,
| more powerful languages will suddenly become a valid choice
| for Web.
|
| 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.
| airstrike wrote:
| JavaScript's redeeming quality is that it is ubiquitous due
| to being used by browsers. 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
| madeofpalk wrote:
| Outside of the "everyone hates javascript" circle jerk, I
| really enjoy writing Typescript. I find its type system
| to be very natural and pleasent to use. It's my go-to for
| any generic task.
| marcosdumay wrote:
| > I don't think anybody credibly claims it to be a
| language we would want to use
|
| 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.
| shepherdjerred wrote:
| TypeScript is a pleasure to use. I have no desire to
| replace it with another language.
| andsoitis wrote:
| What follows is meant to be _descriptive_ rather arguing
| "my camp is right".
|
| > 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.
| flohofwoe wrote:
| Any non-toy WASM application running in browsers will almost
| certainly end up as a hybrid WASM/JS application, just
| because you can't call directly from WASM into browser APIs,
| and even if the JS shim is "hidden from view", sometimes it's
| either more convenient or even more performant to write more
| than just a thin API shim in Javascript, especially for
| asynchronous code or to avoid redundant copying of large data
| blobs in and out of the WASM heap.
| LudwigNagasena wrote:
| > just because you can't call directly from WASM into
| browser APIs
|
| Surely that will change someday.
| flohofwoe wrote:
| You are still limited by the fact that browser APIs are
| mainly designed for usage with Javascript, and those
| Javascript APIs usually don't map all that well to
| languages used with WASM (which typically use the "C ABI"
| for FFI). There is zero chance that each web API will get
| a second API which is compatible with "C calling
| conventions".
|
| There is a (now inactive) "interface-types" proposal,
| which has dissolved into "component-types", but this
| scope-creep definitely is already quite concerning:
|
| https://github.com/WebAssembly/interface-types
| pjmlp wrote:
| C# and Swift have pattern matching.
| troupo wrote:
| It's more limited than what you'd ideally want: pattern
| matching in function definitions. This makes walking ASTs a
| breeze.
|
| Edit: And inline pattern matching for values returned from
| expressions and function calls (similar to destructuring, but
| more powerful).
| pjmlp wrote:
| I would say what C#11 does is already quite good, likewise
| for Swift.
|
| 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.
| paddim8 wrote:
| I would think C# is easier for this due to having proper (and
| very convenient) pattern matching.
| zkldi wrote:
| C# doesn't have anything resembling proper pattern matching.
| C#s pattern matching is akin to a marginally improved switch
| or if/else-if chain. C#s pattern matches aren't exhaustive,
| and the compiler doesn't properly check them at all.[0]
|
| 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)), };
| hardware2win wrote:
| >compiler can't prove the above code is exhaustive because
| no proper sum types
|
| Thats because c#s enum is not "closed"
|
| https://github.com/dotnet/csharplang/issues/3179
| [deleted]
| chpatrick wrote:
| Type-refining if in TypeScript is good enough for pattern
| matching if you ask me.
| graftak wrote:
| The TypeScript type system meta programming actually does
| pattern matching very well, it's just the underlying JS
| runtimes that can't, there is a proposal for it though;
| https://github.com/tc39/proposal-pattern-matching.
| rustybolt wrote:
| I really don't know what the author means by "language-centric"
| vs "implementation-centric" and "production-ready"...
| matklad wrote:
| language-centric: the language you are compiling is flexible
| (you are also the language designer), you want to build the
| best possible language
|
| implementation-centric: the language you are compiling is
| mostly fixed and/or defined by an external entity. You want to
| build the fastest compiler for this language, which emits the
| fastest code.
| zelphirkalt wrote:
| But to what end? For a compiler, there is no need for all the
| overhead of npm (usually), ts, tsconfig, package.json bundler and
| bundler configuration, to get something usable, unless one really
| wants a JS thing at the end to run in the browser. I imagine even
| some webassembly tool chains may be shorter.
| [deleted]
| meheleventyone wrote:
| The article answers this question. The author is using deno
| which is a single binary that uses TypeScript without all of
| that.
| [deleted]
| dna_polymerase wrote:
| It sure is. For anyone looking into Compilers and just starting
| out, I recommend this book: https://keleshev.com/compiling-to-
| assembly-from-scratch/
|
| The author uses a TypeScript subset to write a compiler to 32bit
| ARM assembly and explains that it almost looks like Pseudocode,
| so it is very accessible. A sentiment I can get behind, despite
| avoiding it in any case possible.
| Bognar wrote:
| I would also _highly_ recommend Crafting Interpreters:
| https://craftinginterpreters.com/
|
| The book is split into two parts. The first implements a
| language interpreter in Java, and the second implements the
| same language by building a compiler to byte-code and also
| implements a byte-code VM in C. Every line of code in the
| implementation is referenced in the book.
| jabits wrote:
| Thanks for the ref. Looks very interesting!
| jjice wrote:
| Anecdotally, I love the reference to the classic "Dragon Book"
| that this cover makes.
| gavinray wrote:
| This looks great -- thank you for sharing!
| metalforever wrote:
| It's just not the right tool for the job .
| rtpg wrote:
| TS's type system is fun but a part of me always wonders how much
| faster TS's compiler would be if it was written in a compiled
| language (assuming "good implementation", which is a big
| assumption!)
| smcl wrote:
| I haven't used it in a couple of years (tbh I may have to
| remove "full stack" and "Angular" from my CV...) but I don't
| recall TS compilation being particularly slow. Are people not
| happy with how quick it is, or do you have a particularly
| big/complex application you're working with?
| jitl wrote:
| Notion is more than 10k typescript files and we view
| typecheck slowness and memory pressure as an existential
| threat to our codebase. Right now our typecheck needs ~12GB
| of heap size but the memory needs have accelerated recently.
| agloe_dreams wrote:
| NX likely would do magic for you all but I imagine it is
| far too late to do anything about that.
|
| We had a very large Angular 10 project that we replaced
| with an NX Angular project late last year. We went from ~6
| minute prod compile to around 20 seconds, recompile is
| lightning fast because it only builds your affected
| library. That is all without using the remote library cache
| feature where you can save compiled libraries to the cloud
| such that a user only builds the libraries they are
| changing ever.
| cobbzilla wrote:
| Can you not split the 10k files into modules for
| incremental/parallel compilation?
| supriyo-biswas wrote:
| There's https://swc.rs/, a Rust implementation (albeit without
| type checking at this time).
| mcluck wrote:
| Not really a fair comparison. Stripping out types is
| trivially fast regardless of the implementation language. The
| _vast_ majority of the time taken in tsc is because of the
| type checking
| mst wrote:
| See the sibling comment
| https://news.ycombinator.com/item?id=37173313 for a type
| checker by the same author.
| merlindru wrote:
| Wonder no more: https://github.com/dudykr/stc
|
| Written in Rust by the (lead?) dev of SWC
|
| ---
|
| SWC (speedy web compiler) compiles TS to JS
|
| STC (speedy type checker) checks TS types
| madarco wrote:
| PS: swc and esbuild aren't good example, because most of the
| speed improvements comes from the fact that they are just
| stripping TS-specific syntaxes to generate JS code.
|
| Also tsc is slow, sure, but only for the first run. Enabling
| `incremental` flag or using watch mode with `--transpile-only`
| usually brings compile time under 100ms, Making it practically
| indistinguishable from SWC or ESBuild.
| conaclos wrote:
| Performance of the TypeScript compiler has greatly improved in
| the last versions. The future isolated declaration mode
| promises great improvements: until 75% of reduction in
| compilation time! [0]
|
| [0]
| https://github.com/microsoft/TypeScript/pull/53463#issuecomm...
| barbariangrunge wrote:
| The reason we need to buy new hardware every few years, and
| fill landfills with our old stuff, is because of ideas like
| "let's use JavaScript to write a compiler!"
| agloe_dreams wrote:
| No lie, the M1 was a game changer for large Typescript
| projects.
| foldr wrote:
| Probably not _that_ much faster. V8 is very good at optimizing
| JS code. Typescript is slow mostly because of the complexity of
| its type system (which is required to make it backward
| compatible with existing JS code).
| nikanj wrote:
| Probably quite a bit faster if you do not have to instantiate
| a new V8 for every single file, considering how many files
| your average npm project has. I'm not certain if any
| contemporary build systems reuse the compiler process for
| multiple files
| foldr wrote:
| Why would it have to instantiate a new V8 for every file?
| You can just run 'tsc' in your project dir to compile all
| Typescript files.
| harrygeez wrote:
| I think people often underestimate how much the
| TypeScript team does to make TypeScript fast and
| efficient. They have some of the best people in the field
| working on the type checker
| foldr wrote:
| Indeed. I haven't checked the implementation, but I
| suspect that "don't spin up a separate process for every
| file" is an idea that has probably already occurred to
| them :D
| alexvitkov wrote:
| Yes, because it's one of the slowest compilers in the
| world, and its job is to compile from JavaScript to
| JavaScript. There are other languages with complex type
| systems, where the compilers have to do _a lot of_ other
| additional work on top of typechecking, and are still way
| faster than TS.
|
| And TypeScript is always slow, not only when you do abuse
| its type system and require it to do complex inference -
| I mostly use it for "this is a number" and "this is a
| object with the following 4 fields" and it's still by far
| the slowest component in my builds usually
| agloe_dreams wrote:
| The type system is fun right up until you are using it to its
| full ability in generics..then you look at the 5 lines of 'type
| logic' you spent the last day debugging and ask yourself how
| you got here.
| svachalek wrote:
| I've definitely seen it suck people into a black hole. It
| works best when you think of it as a form of documentation
| with a benefit of enforcement, rather than trying to use the
| type system to prevent every conceivable bug. Massive
| unreadable types don't help programmers write better code.
| cxr wrote:
| > part of me always wonders how much faster TS's compiler would
| be if it was written in a compiled language
|
| TypeScript's compiler _is_ written in a compiled language.
|
| (I think you are using "compiled" here as a euphemism--one that
| doesn't help anyone.)
| harrygeez wrote:
| There's an answer from TypeScript team for your question :)
| https://twitter.com/drosenwasser/status/1260723846534979584
|
| Basically,
|
| > Let's say TypeScript takes over 20 seconds to type-check a
| medium-sized program. That's not usually because it's JS, it's
| often because of types that cause a combinatorial explosion.
|
| Also
|
| > A different runtime can afford _a lot_ (it sounds like
| parallelism and start-up time in this case) but I haven 't seen
| a CPU-bound benchmark that supports the idea of a 20x all-up
| speed-up.
| rtpg wrote:
| I mean I get what Daniel's saying, but I kinda doubt there
| wouldn't be _some_ speedup. And hey, what if type checking
| for small programs went from half a second to 100 ms? That
| would be nice!
| foldr wrote:
| That's a five times speedup. I wouldn't expect a typical
| 'compiled' language to run five times faster than
| JavaScript on the kind of code you find in a compiler.
| sdflhasjd wrote:
| Both swc and esbuild claim to be much faster typescript
| compilers in part because they're written natively, but are
| actually just "cheating" and not doing any type-checking at
| all.
|
| That's not to say they're useless though, they're good for
| fast hot-reload as you can have type-checking running in
| parallel.
| madeofpalk wrote:
| it's easy to write a typed language compiler if you don't
| need to actually check types :)
| gr__or wrote:
| Very much apropos:
|
| Going between Rust and TS it is painfully obvious how much sth
| like tagged enums are missing, which can also be seen in this
| post.
|
| I know of this [1] proposal for ADT enums which looks like it has
| stalled. Anyone know of other efforts?
|
| [1] https://github.com/Jack-Works/proposal-enum/discussions/19
| dcre wrote:
| I find discriminated unions work ok for similar use cases.
| auggierose wrote:
| Not that surprising:
| https://news.ycombinator.com/item?id=37039443
|
| Of course, that one has been downvoted to -4 (as of now).
| catsarebetter wrote:
| That is surprising, I would've thought that TS would have more
| overhead because of the interfaces adding extra weight. Makes me
| wonder what else TS could apply to. Language parsing, maybe?
| recursive wrote:
| At runtime, interfaces have zero weight. They get completely
| compiled out.
| frozenport wrote:
| Gut reaction is to use LLVM for everything...
| [deleted]
| matt_kantor wrote:
| What does that have to do with the compiler's implementation
| language? A written-in-TypeScript compiler can emit LLVM
| bytecode just like a compiler written in any other language.
| paxys wrote:
| The result shouldn't be all that surprising considering the
| TypeScript compiler is written in...TypeScript. So TypeScript as
| a ML is already battle tested and is in heavy production use
| every day.
| grumblingdev wrote:
| TypeScript is an incredible language in general.
|
| The fact that Functions are Objects that can have
| properties/methods is supremely undervalued.
|
| Are there other languages that do this so nicely? It's the
| perfect blend of OO and functional.
|
| Programming is mostly about gradually figuring out the right
| design I find. JS/TS let's me evolve things naturally without big
| rewrites. function foo() {} function
| bar() {} function baz() {} const commands = [foo,
| bar, baz] // Run commands commands.forEach(x =>
| x()) foo.help = 'This does something' //
| Describe commands commands.forEach(x =>
| console.log(x.help)) // Add some state via closure.
| const config = {} function foo() { config.blah }
| // Add some state using partials. function _foo(config) {
| config.blah } const foo = foo.bind(null, config)
|
| I can flexibly do what I need, without ever having to define a
| class like `Command`, which I probably don't even know what it
| should be yet.
|
| This premature naming of things in OO creates so many dramas.
|
| It avoids things like `VideoCompressor#compress()`,
| `VideoSplitter#split()`. You don't have to think: what state
| belongs in which class...you just call the function and pass it
| what it needs.
| akkad33 wrote:
| This is a Javascript feature, not really a typescript thing.
| You can do the same thing with python
| hazebooth wrote:
| I could be wrong, but can't Common Lisp do this?
| thrashh wrote:
| TypeScript is really nice but you're really describing a
| JavaScript feature to be honest.
|
| Here's some other languages that support objects as functions
| (although not necessarily functions as objects):
| https://en.wikipedia.org/wiki/Function_object
| [deleted]
| _ZeD_ wrote:
| >>> Are there other languages that do this so nicely? It's the
| perfect blend of OO and functional.
|
| Python, where everything is an object.
| flylikeabanana wrote:
| FP in python is painful without tail call elimination and the
| higher-order function syntax is so clunky
| nightpool wrote:
| JS also doesn't have TCE, but for Python even just the
| lambda limitations are surprisingly annoying. I can't tell
| you how many times i've been frustrated because it's nearly
| impossible to put a print statement into a python lambda
| fullsailor wrote:
| JS technically does have TCE (specified in ES6[1]), but
| only the JavaScriptCore runtime used by Safari/WebKit
| implements it[2].
|
| [1]: https://webkit.org/blog/6240/ecmascript-6-proper-
| tail-calls-... [2]: https://kangax.github.io/compat-
| table/es6/#test-proper_tail_...
| throwaway287391 wrote:
| As a Python enjoyer, why do we want to shove so much into
| lambdas rather than just doing an inline function `def`?
|
| Is it the fact that you have to give it a name? If so I'd
| say just using some generic name like
| `{f,fn,func(tion),callback,etc}` is fine (at least as
| much as an anonymous function is), and to me would
| usually be more readable than an inline definition of a
| multi-statement lambda would be.
|
| Or maybe it's the fact that lambdas are allowed in the
| first place, so people are going to use them, and then
| when you want to debug a lambda you'll probably have to
| go to the trouble of changing it to a function? That is a
| fair complaint if so.
|
| In any case I can see how it could be annoying if you're
| more used to a language full of complex inline lambdas.
| sixo wrote:
| That's a mismatch between Python's choice of lambda
| syntax and the use of whitespace instead of curly braces.
| c-hendricks wrote:
| In Python, can typings define a property added to a function?
|
| Example, I have some Redux helpers that are functions, but
| those functions also define a `.actionType` property.
| TypeScript handles that.
|
| edit: accidentally wrote "object" instead of "function"
| nerdponx wrote:
| The typical approach to this in Python is to define a
| callable class instead. If you define the __call__() method
| on a class, instances of the class will be callable.
| class IntAdder: def __init__(self, x: int)
| -> None: self.x = x def
| __call__(self, y: int) -> int: return self.x
| + y def __str__(self) -> str:
| return f"({self.x} + ??)" add1 = IntAdder(1)
| assert add1(3) == 4 print(add1) # (1 + ??)
|
| As mentioned in the sibling comment, you can define a
| Protocol which is analogous to an interface, so if you had
| a protocol like this: from typing import
| Protocol, TypeVar T = TypeVar("T")
| class ValueUpdater(Protocol[T]): def
| __call__(self, y: T) -> T: ...
|
| Then IntAdder would be considered a subclass of
| ValueUpdater[int] by the type checker. Demo: https://mypy-
| play.net/?mypy=1.5.1&python=3.11&flags=strict&g...
| dataangel wrote:
| I believe you could have a Protocol that requires the
| property and that the object is Callable.
| c-hendricks wrote:
| Ah, that's not really that different than in TypeScript
| then. There you have to define a callable interface, and
| can add whatever properties you want
| interface SomethingCallableWithAnExtraProperty {
| // This means you can call it like a function.
| (args: Whatever): SomethingReturned // And
| since it's an interface, you can still do interface-y
| things anotherProperty: string }
| thefaux wrote:
| This blend of functional and oo programming was pioneered by
| scala.
| hocuspocus wrote:
| Scala also supports structural typing, even though it's a bit
| clunky and against its nature.
|
| And advanced typelevel shenanigans you see in TS usually have
| their counterpart in Scala, especially in Scala 3's meta-
| programming features.
| Zambyte wrote:
| Lisp of course supported both decades earlier though :)
| thefaux wrote:
| Sure. Everything is reducible to lisp. But even though lisp
| had the same capabilities decades earlier, scala is the
| language that brought them to the mainstream (which may
| well because of the historical accident that twitter's
| backend was rewritten in scala). I don't love scala but it
| has been hugely impactful.
| koito17 wrote:
| The meaning of "mainstream" changes throughout the years.
| Remember that Common Lisp started as quite literally the
| XKCD joke of "14 competing standards", except it actually
| worked -- in the sense that this 15th competing standard
| killed all the other ones and gained widespread adoption
| in the Lisp community. Tons of vendors threw money and
| effort at the situation, and it all started as an ARPA
| manager's idea. Of course, we live in different times
| now... But Zetalisp is one fairly popular Lisp I can
| think of that had funcallable objects. Whether the idea
| was appreciated or preferred over alternatives is a
| separate thing entirely, of course. I would assume most
| people would use a simple let-over-lambda to achieve the
| same effect as funcallable objects.
| andyjohnson0 wrote:
| > Are there other languages that do this so nicely? It's the
| perfect blend of OO and functional.
|
| In .net methods, properties, member variables, classes, etc.
| can have attached attributes that are objects. Attributes can
| be interrogated at runtime using reflection.
| [MyAttrib(foo=42, bar="baz")] class MyClass {
| }
| feoren wrote:
| I really dislike attributes in C#. Their use is a big code
| smell for me. They _look_ like C#, but they 're not actually
| -- they're a part of the type system that has been disguised.
| The fact that their parameters must be compile-time constants
| helps perpetuate a harmful "primitive-centric" viewpoint that
| is antithetical to many good design principals.
|
| Their only reason to exist is to support a use case of a 1:1
| relationship between classes and functional units (or methods
| and functional units, or parameters and functional units,
| etc.), which is almost always an 80% solution that makes the
| other 20% extremely hard and/or impossible. I would go so far
| as to say that every single use of an attribute is your own
| code begging you for a better design. Unfortunately you're
| sometimes stuck with them, but that's only because you're
| sometimes stuck with a framework that itself is begging its
| creators for a better design. I wonder if Attributes were
| never added to the language, if developers would have just
| made those better choices to begin with. From my vantage
| point, they were a clear mistake in a language that was
| otherwise very well designed.
|
| They certainly are nothing like the ability to attach methods
| to functions. A better example of that kind of convenience in
| C# is extension methods, which you can certainly define on
| types like Func<T>. I _love_ extension methods and miss them
| in every language that doesn 't have them!
| sisypheanblithe wrote:
| >>> Are there other languages that do this so nicely? It's the
| perfect blend of OO and functional.
|
| Everything is an object in Ruby as well
| CharlieDigital wrote:
| The fact that Functions are Objects that can have
| properties/methods is supremely undervalued.
| Are there other languages that do this so nicely? It's the
| perfect blend of OO and functional.
|
| Yes. C#. The equivalent are `Func` and `Action` types
| representing functions with a return and without a return. In
| fact, the JavaScript lambda expression looks awfully familiar
| to C#.
|
| One of the snippets below is C# and the other is TypeScript:
| var echo = (string message) => Console.Write($"You said:
| {message}"); var echo = (message: string) =>
| console.log(`You said: ${message}`);
|
| The same signature in C# and TypeScript: var
| Apply = (Func<string, string> fn, string input) => fn(input);
| var result = Apply(input => ..., "some_string");
| var apply = (fn: (input: string) => string, input: string) =>
| fn(input) var result = apply(input => ..., input);
|
| The C# can version can also be written like:
| var Apply = (Func<string, string> fn, string input) =>
| fn(input); var lowercase = (string input) =>
| input.ToLowerInvariant(); var result = Apply(lowercase,
| "HELLO, WORLD");
|
| For the curious, I have a small repo that shows just how
| similar JavaScript, TypeScript, and C# are:
| https://github.com/CharlieDigital/js-ts-csharp
|
| Screen grab of the same logic in JS/TS/C# showing the
| congruency: https://github.com/CharlieDigital/js-ts-
| csharp/blob/main/js-...
| nightpool wrote:
| this doesn't really address OP's point, where in JS you can
| do: const foo = () => doSomething;
| foo.help = "this is a description of the function";
| const commands = [foo]; // print help
| commands.forEach(c => console.log(c.name, c.help || "No help
| is available for this function");
|
| Presumably this isn't possible in C# because it's statically
| typed, so the object returned by "() => doSomething" can't be
| converted into one that supports adding more properties?
| CharlieDigital wrote:
| It can.
|
| .NET/C# has a `dynamic` type (aka `ExpandoObject`). That
| would be one way to do it (but would require casting to
| invoke). It's not exactly the same since you'd assign the
| `Func`/`Action` to a property of the `dynamic`. `dynamic`
| is generally avoided due to how easy it is to get into a
| pickle with it and also performance issues.
|
| An alternate in this case is probably to return a tuple
| which I think is just as good/better.
|
| Example: var log = (object message) =>
| Console.WriteLine(message); var foo = () =>
| log("Hello, World"); var fn1 = (foo, "This is the
| help text"); var commands = new[] { fn1 };
| commands.ToList().ForEach(c => { var (fn, help) =
| c; log(fn.Method.Name); log(help ?? "No
| help is available for this function"); });
|
| https://dotnetfiddle.net/szxb9F
|
| The tuple can also take named properties like this:
| var log = (object message) => Console.WriteLine(message);
| var foo = () => log("Hello, World"); var
| commands = new (Action fn, string? help)[] {
| (foo, "This is the help text"), (foo, null)
| }; commands.ToList().ForEach(c => {
| log(c.fn.Method.Name); log(c.help ?? "No help is
| available for this function"); });
|
| https://dotnetfiddle.net/oPK1d8
|
| Alternatively, use anonymous types: var
| log = (object message) => Console.WriteLine(message);
| var foo1 = () => log("Hello, World"); var foo2 = ()
| => log("Hello, Neighbor"); var bar = new[]
| { new { doSomething = foo1,
| help = "This is foo1's help text" },
| new { doSomething = foo2, help =
| "This is foo2's help text" }, };
| bar.ToList().ForEach(b => { var (fn, help) =
| (b.doSomething, b.help); fn();
| log(help); });
|
| https://dotnetfiddle.net/KyBtgZ
|
| The next release of C# (12) will include named tuple types:
| https://learn.microsoft.com/en-us/dotnet/csharp/language-
| ref...
|
| Very much looking forward to this since it gives you a lot
| of the same power of the JavaScript map/TS `Record`.
| > ...because it's statically typed
|
| While this is true, the `dynamic`/`ExpandoObject` is an
| oddity and lets you do weird things like multiple dispatch
| on .NET (https://charliedigital.com/2009/05/28/visitor-
| pattern-in-c-4...). But C# has a bunch of compiler tricks
| with regards to anonymous types that can mimic JS objects
| to some extent. The tuple type is probably a better choice
| in most cases, however.
| notnmeyer wrote:
| give me something with ts's type system and without exceptions
| and try/catch. i know go's error handing gets shit, but i
| really like the explicitness of it.
|
| oh and burn npm to the ground please.
| pull_my_finger wrote:
| > Are there other languages that do this so nicely? It's the
| perfect blend of OO and functional.
|
| In Lua, you have regular, non-object functions, but you can
| also create a callable table using metamethods, and keep
| whatever data you have with it. Add to that the colon syntax
| sugar (implicit self vs explicit self) on table methods and I
| think you have a beautiful "opt-in" OO story without the
| unintuitive `this` business from JS.
| diegof79 wrote:
| If you take out all the "script" legacy (type coercion which
| was common for scripting languages when JS came out, the
| initial lack of a module system which led to all kinds of
| hacks, the scope of var declaration, etc), JavaScript and its
| prototype based approach is really good.
|
| In fact I wish that constructor functions, and the class
| keyword never existed.
|
| You can do the same with Object.create and a function closure,
| isn't more verbose and it fits better with the mixed
| functional/oop approach of the language.
| paulddraper wrote:
| > Are there other languages that do this so nicely? It's the
| perfect blend of OO and functional.
|
| Python compressor = lambda x: x / 2
| compressor(5) helpful_compressor = lambda x: x / 2
| helpful_compressor.help = "compressor"
| helpful_compressor(5) helpful_compressor.help
|
| Scala val compressor = (x: double) => x / 2
| compressor(5) object helpfulCompressor { def
| apply(x: double) = x / 2 val help = "compressor"
| } helpfulCompressor(5) helpfulCompressor.help
|
| Ruby also has .call...but I can't remember it well enough
| asddubs wrote:
| I don't like that at all. it all seems to be too informally
| specified and requires divine knowledge to actually know how
| you're supposed to use the magic function properties.
| dgb23 wrote:
| In Go, you can attach methods to functions (and any type
| really, even "unboxed" primitives).
|
| One of the canonical examples would be a net/http: A handler
| can both be a struct (or any other type really) that implements
| a serve method, or it can be a handler function that calls
| itself to satisfy the interface.
|
| In Clojure you would achieve the thing you describe by
| attaching metadata on your function var. It being a Lisp, it
| also has macros so you can pretty much do anything.
|
| Speaking of macros:
|
| Clojure also implements CSP channels with macros in core/async,
| inspired by Go.
|
| Channels are a very powerful construct that you might like a
| lot. With channels you can completely avoid the function
| coloring problem (callbacks, promises, async/await). Perhaps
| most importantly they decouple execution from communication.
|
| So going back to your example, your commands could be producers
| that send their results on channels. They don't need to know
| what consumes w/e they make, nor do they need to hold on to it,
| mutate something, or call something directly.
|
| Good analogies would be buses on motherboards, routers and
| switches in networks, conveyor belts, message queues in
| distributed systems and so on.
| karmakaze wrote:
| The same is achieved with Java's use of single-method-
| interfaces. It doesn't matter what the method is called, it can
| be used in function contexts without referencing the specific
| method name.
|
| I'm not sure I'd like my functions to have properties (which
| gives them state and can alter what calling it does with the
| same arguments). A big benefit of FP is getting away from OO
| states. Perhaps the problems I work on aren't complex enough to
| benefit from them, or I simply make objects from classes.
| rezonant wrote:
| To be clear, since "can be used in function contexts without
| referencing the specific method name" might be ambiguous to
| some- in Java you still call the "function" as if it was a
| class implementing the interface. IE: `interface Foo { String
| bar(); }` is still called as foo.bar().
|
| It's just that there's now syntactic sugar for creating
| anonymous classes: `Foo foo = () -> "baz"` that automatically
| expands to an anonymous class that conforms to `Foo`. The
| compiler automatically assigns the method name for you.
| TOGoS wrote:
| The properties don't have to be 'state' per se; they can also
| be used to store metadata about the function. e.g. you could
| have a function with 'domain' and 'range' on it, or an
| 'integrate' method.
| karmakaze wrote:
| If the result of `integrate(...)` depend on `domain` and/or
| `range`, what would you call that other than 'state'? Or if
| `integrate(...)` doesn't reference `domain`/`range` what's
| the use of it being there?
|
| Or as I'm interpreting this, is sort of like documentation
| or information that could be used at runtime for code
| generation. Basically a shorthand for composing the
| function with its metadata. The nice thing about separation
| is that you know that when calling the function, there's no
| possible way for it to reference the metadata that is
| associated with it because it's composed externally.
| grumblingdev wrote:
| Interesting, didn't know about single-method-interfaces.
|
| There are lots of singletons in OO. I find it useful to add
| static metadata (not state) to them, without having to
| escalate them to a class. I guess Java has decorators for the
| same purpose. I'd really like decorators for functions in
| TypeScript though.
| tomp wrote:
| This looks like plain JavaScript.
|
| What does TypeScript add in this scenario?
|
| Does the example above even pass typecheck?
| usea wrote:
| Yep. It's javascript, not typescript. Moreover, I think the
| post is only trying to convey that they like dynamic typing.
| boredumb wrote:
| every day we stray further from God
___________________________________________________________________
(page generated 2023-08-18 23:02 UTC)