22 ms·
From Rust to TypeScript
- auggierose 6y agoStopped reading after the exception / result stuff. You can have both, both are useful, learn yourself some Swift maybe.
- deleted 6y ago[deleted]
- k__ 6y agoI think monads are especially useful when the whole eco-system is build around it. Using promises with callback APIs gets ugly quickly. Using results with exception APIs also isnt much fun. Rust shines here because it's the default way.
- lioeters 6y agoI can see the author's point, that a throw behaves awfully like a GOTO. It can add complexity and unpredictability to the code path, especially dealing with external modules. On the other hand, there are situations in which throw/catch provides a simpler, even elegant way to handle errors. So I'd agree with you that both are useful. The author makes a good point though, and I will consider it next time whether returning errors may be more suitable, to be clear and explicit/verbose, without requiring the GOTO-like control flow.
- libria 6y agoAnother place throws are inconvenient is chaining/streams. Pseudocode: var goodStuff = listOfStuff.map(x => transform(x)) .filter(t => typeof(t.result) != Error) .map(y => handleGoodData(y)) If `transform()` threw exceptions, this whole thing aborts. But if it can return an `Error` type, we can continue processing the good ones and log the bad ones (here I ignore them for simplicity).
- XVincentX 6y agoI love this. It reminds me a lot of the work I did in Prism to port what I learned using Haskell in TypeScript: https://dev.to/vncz/forewords-and-domain-model-1p13 https://dev.to/vncz/forewords-and-domain-model-1p13 Reading the article, I do have some suggestion: 1. Never use isLeft or isRight; they break the composability of the monad instance — use `fold` instead, as late as possible 2. Do not use Enum in TypeScript — they have a runtime representation in JavaScript that's likely messing up a lot of things. Use string unions to accomplish the same result
- no_wizard 6y agoI really wish the TS team would have used frozen null prototype objects for enums instead. Feels like it would have been a perfect fit. With that said I am curious why you think one shouldn’t use enums. I know it’s a “factory” IIFE like namespaces at runtime but otherwise I don’t understand why it’s undesirable?
- XVincentX 6y agoMy main issue with Enums in Typescript: 1. Because they have a runtime representation, I cannot do `import type` — and I am importing the entire file (which might have a side effect). While it's still not justified (ideally importing should not do anything) the reality in JavaScript is different. 2. I can swap the enum value with a number or a string and it would still work, even if invalid. See https://www.typescriptlang.org/play?#code/KYOwrgtgBAggxgFwJYHsRQN4CgpQKIByMAQgDJ5QC8UADADQ5QAiAkgMonlVQCMWAvliwATYHAA2AQwBOwKADMwIRKnTCUbBGHnyAFAHcAFpIQAuWCrQBKcwCMUKccEkgA3EPWbte+MjQA6Qk48K1coAHpwqAB5AGsRDS0dXV9Vf1YOMhCwyJiAaQSvZJ4aUIioghQEKFlJcXEATyg4B3EhUEgoTWkkEABzVLRMRiCs7gAiPBBJWydxhlwM4ImmJABnGbmBIA https://www.typescriptlang.org/play?#code/KYOwrgtgBAggxgFwJY... for an example 3. On the other hand, I cannot use strings to create an Enum (https://www.typescriptlang.org/play?#code/KYOwrgtgBAygLgJwJYgOYEEDGckHsRQDeAUFFAKIBy6AQgDLlQC8UAROSAIYBGANsKwA0pKABEAkjFoNmbUUgDOPfq2IBfYsUz4FcKJwBcsRCgzY8BFuy58BUAPT2olXHs69euAO7AAJkA https://www.typescriptlang.org/play?#code/KYOwrgtgBAygLgJwJY...) — this is the exact opposite of n. 2 4. Duplicates are possible: https://www.typescriptlang.org/play?#code/KYOwrgtgBAIghgTwPIDMDqxgGsoG8BQUUAymCACaIA0hUAsgPYXW0AqYwAzpQjURuRBcefKKwAWYAE7dEUALxQATKIBiUgJYjaxOABdpPfAF8gA https://www.typescriptlang.org/play?#code/KYOwrgtgBAIghgTwPI...
- flowerlad 6y agoThis article is unconvincing. From the article: Compared to the first snippet, this one is harder to debug. It seems so because 1.) You don't know that callMe will throw an error or not. 2.) If it throws an exception you will not know what is the type of the exception. The more modular your code is, the harder it is to debug exceptions. This is only a problem with unchecked exceptions as seen in TypeScript and C#. Checked exceptions are the solution. The problem with error code return is tediousness: int foo() { int result = bar(); if (result == ERROR_CODE) return result; int result2 = baz(); if (result2 == ERROR_CODE) return result2; int result3 = bazz(); if (result3 == ERROR_CODE) return result3; } Compare to the same code written in a language that supports checked exceptions: int foo() throws SomeException { int result = bar(); int result2 = baz(); int result3 = bazz(); } In the first version the logic is interspersed with error handling, which makes the logic hard to see. How do people using Go and other languages that don't have exceptions solve this reduced readability problem?
- steveklabnik 6y agoIn Rust, your code would look like this: fn foo() -> Result<i32> { let result = bar()?; let result2 = baz()?; let result3 = bazz()?; } (This wouldn't compile because you don't actually return anything in the success case, but I just kept the code the same. Additionally, I am assuming you are using some sort of crate that helps with error type mapping, because that's realistic, otherwise, it would be Result<i32, Error> where Error would be some sort of type mapping to SomeException's type.)
- oleganza 6y agoRust has an operator for "re-throwing" error: question mark. fn foo() -> Result<int, SomeError> { let result = bar()?; let result2 = baz()?; let result3 = bazz()?; Ok(result + result2 + result3) } It's still 100% more explicit about return types and errors, but has the same lightweight feel as in JS/Java/etc.
- flowerlad 6y agoIn your example if bar() returned an error your program still needlessly calls baz() correct? This is why exceptions are better.
- taosx 6y agoI resonated with the post as I'm in a similar position but I'm really sad that typescript chose just to be a thin layer on top of javascript. The sentiment mostly comes from having the JS ecosystem mostly being untyped and having to interact with it. That being said I tried io-ts but found it undocumented, missing examples and hard to write. For future libraries/projects I'm looking to try again ReasonML, tried in the past but had to write too many bindings.
- valand 6y agoAuthor here. I used io-ts when the documentation is still on its root README.md. Apparently it has been moved to https://github.com/gcanti/io-ts/blob/master/index.md https://github.com/gcanti/io-ts/blob/master/index.md
- desert_boi 6y agoI've been rolling io-ts into an app I inherited and have been porting to TypeScript. If you know a little Haskell/Scala type FP, the docs are plenty. I think the lib could definitely have a shallower learning curve for folks coming from Go though (what my back end devs write mostly).
- chrismorgan 6y agoThe first example code is rather off-putting, because it has multiple errors. The code would not throw an exception, because it produces compiler errors so you’ll never run it. (OK, so it still generates JavaScript that you could run, but I’m assuming you’re using TypeScript properly and won’t do so.) • `return number + 1;` should be `return someNumber + 1;`. If you ignore errors and run the JavaScript, this means that it’ll throw a ReferenceError on this line. • `bar(someNumber) + bar;` is adding a number (well, it should be a number, but you’ve omitted the return types, so the previous error will mean it infers `any` instead of `number`) to a function. This is a type error. • `baz()` is called with zero arguments instead of the one it requires. > When baz is called it will throw an uncaught exception. By reading the code you know that foo throws an Error. Debugging this snippet is a piece of cake, right? As mentioned, this code doesn’t compile, but if you ignore that and run it anyway, then the uncaught exception is a ReferenceError, not the error you see on a throw line. I… don’t think this is what was intended. This also demonstrates what I consider a bit of a weakness of the exception system in such languages: various programming errors that a compiler should catch come through as runtime errors instead. (I’d generally prefer to use exceptions to indicate exclusively programmer errors, and basically never use a try/catch block, but too much is designed around treating exceptions as normal parts of control flow to be able to do this in most cases. I like the Result/panic split in Rust, where panic always means programmer error, and Result is for probably-exceptional cases, but where they’re still generally expected and part of the design rather than programmer error.) If you fixed only the first error, you’d get a value like "NaNfunction bar(someNumber) {\n return foo(someNumber) + 1;\n}" out of the baz() call. Fun times.
- robocat 6y agoAuthor has now fixed the example: https://news.ycombinator.com/item?id=24454455 https://news.ycombinator.com/item?id=24454455
- valand 6y agoAuthor here. Caught red-handed not running it on playground. Thanks for the correction. > I’d generally prefer to use exceptions to indicate exclusively programmer errors The idea that panic/exception means programmer errors worth to be noted. It think they should be intended for errors that is intended to be unrecoverable. Still, catching errors on runtime is not fun
- unnouinceput 6y agoYou can write bad code in any language, Rust included. This article is cherry-picking.
- bitxbit 6y agoI think it’s best to stay language agnostic especially in 2020. I try to think data-in data-out and use the best tools available for clean execution.
- azangru 6y ago> and use the best tools available How do you know which tools are the best?
- capableweb 6y agoUnderstand a lot of programming languages, become proficient in a few and use your understanding when trying to come up with solutions for your problems. At least play around in other technical stacks then what you normally use, so you get a broader understanding.
- matt_kantor 6y ago0. Define what problem you are trying to solve, make sure you understand the domain. 1. Come up with a set of options by doing research, talking to people who have solved similar problems before, etc. 2. Rank them by some criteria (e.g. availability of libraries, documentation, raw performance, hireability, support, quality of tooling, etc). 3. Do some prototyping in your problem domain using the top 2-3 (more if you aren't confident in your ranking) and pick whichever you like best. 4. Always be willing to change your mind down the road. Avoid the sunk cost fallacy and try not to lock yourself in too much until you're confident in your choice.
- nur0n 6y ago"Language agnosticism" is an ideal I have never been able to attain. Sure, I can hack together a solution on the first week of learning a language. But, am I able to write elegant, idiomatic, efficient code during the first few months? No way. A language is not just the basic control flow operations, but includes package management, performance gotchas, correctness gotchas, standard library, library ecosystem, data representation, etc. Then there are details and edge cases that you just don't hit without months or years of hard work with a language. My experience tells me to take choose a reasonably robust language, then stick to that language until you have an overwhelmingly compelling reason to switch.
- sdwvit 6y agoWell exceptions are generally a sign that code smells. Instead of throwing an exception, just return the default value.
- Smaug123 6y agoHonestly that sounds substantially worse in pretty much every way in nearly every situation. If an assumption of your program has broken, you almost always want to do something about it rather than simply silently producing garbage. Even in a less expressive language like C you'll still often use e.g. a negative return code to indicate failure.
- josalhor 6y agoThis is worse in every possible way. If the default value was part of the domain of the function it would be impossible to distinguish an error from a legit answer without breaking encapsulation. If it wasn't, then you're handling errors like in C.. ugh
- leptons 6y ago>code smells This is one of the worst "ideas" ever in programming. It allows any programmer to shit on any other programmer's work in practically any arbitrary way they feel like. Your idea of returning default is considered by others here to be a "code smell" - if you want to call it that. Looks like using that term just doesn't give your idea any benefit the way you thought it would.
- otabdeveloper4 6y ago> 1 / 0 just returns 0, #yolo No thanks.
- sdwvit 6y agoReturns infinity like it should
- choward 6y agoHow do I know if it errored or not? The default value is ambiguous. It either means that it's the correct result or that the function errored. Sure, the program might not crash right then and it will compile, but it's guaranteed to lead to confusion for both users and programmers who have to debug it.
- aerovistae 6y agoThe first code example he uses immediately threw me for a loop. Why is he adding the return value of a function call to a function itself? bar() + bar???? function bar(someNumber: number) { return foo(someNumber) + 1; } function baz(someNumber: number) { return bar(someNumber) + bar; } baz(); Moreover, baz is called with its only argument being undefined-- so the exception throwing code won't even throw, because it isn't 0. And _moreover_, this is typescript, so this would have thrown a type error since a required numeric argument is omitted. This has so many problems. This is not a promising start to this article's correctness or thought-outtedness. Yet here it is at the top of hacker news...
- throwaway35911 6y agoSounds like you posted this comment after only reading that example at the beginning. Did you read the rest of the article? What were your thoughts at the end of it?
- aerovistae 6y agoCorrect. There's about 19 billion opinionated articles on the internet. If I start one off and it seems immediately wrong, I'm not going to take the time to read the entire essay. You have to have some kind of filtering heuristic for content.
- throwaway35911 6y agoYou'll take the time to leave a comment, though. And engage in follow-up discussion, apparently. It's not a long read and it has some useful content (that I mostly agree with) despite having a bunch of minor mistakes. I noticed them too, but instead of loudly dismissing the entire thing on a public forum my first instinct was to look for a repo so I could send a pull request (sadly I didn't find one).
- valand 6y agoAuthor here. Thanks for the correction. You caught me red-handed for not testing these codes in playground.
- tym0 6y agoYou don't actually need the tag on Either. This is how I defined my Result type. The shape is enough to discriminate them. type Result<S, E> = Success<S> | ResultError<E>; type Success<S> = { value: S }; type ResultError<E> = { error: E };
- valand 6y agoAuthor here. Did this too. I found some quirks (rather irrelevant) while compiling using TypeScript 3.6-ish. ``` const { value, error } = result; if (error) return doSomething(); if (value) return doSomethingElse(); ``` ^ You can't do this because value or error might be not an attribute of result Nowadays I will just `npm install fp-ts` and use them.
- tym0 6y agoYou're right that you can't destructure until you've established which side of the union you got, does the tag help with that? I like a Result type over an Either because it feels semantically more meaningful. I work with people with a wide range of background and a Result type is self explanatory, they can just read the code by themselves without knowing and understanding the convention of which side of the branch the error goes, etc...
- valand 6y agoI agree with you on the Result type being semantically more meaningful and I agree there are a lot of conventions to be remembered coming from functional programming that gets meta. Thanks for the insight
- ape4 6y agoDoes anyone else hate foo(), bar() and baz() as example names. They are meaningless - except communicating that its a programmer selected name. Even func1(), func2() would be better.
- friedman23 6y agoI dont think it matters
- someHnUser 6y agoI'm ok with it but I think it does matter a bit. here's a small example: "Did baz call bar or did bar call baz ?" "Did func2 call func3 or did 3 call 2?" Again not a big deal but if I'm trying to show a snippet of code then I would try to make that snippet as simple to follow to get my point across. I think it's a sensible rule. In this case, could it have helped if instead of ,hypothetically, keeping this information in your head "foo called bar which baz" you could rely on meaningful names to help the reader ? Yes. Yes I think it would be beneficial (matters a bit). Whether that's specifically "func2"..3 or very verbose names "funThrowsExceptionOn0()", I think it would help. You can argue if that was necessary here for this example but I think it'd be hard to argue against meaningful names.
- fortydegrees 6y agoYep.
- jpxw 6y agof(), g() and h() are decent alternatives. Of course, the real solution is to come up with an reasonable example scenario with meaningful names.
- hu3 6y agoa, b, c seems more intuitive
- hn_throwaway_99 6y agoI'm very unconvinced considering the section on avoiding exceptions doesn't discuss async code at all. I mean, a returned Promise from an async function is very similar to the author's "Result" object where you can even pass a success handler and a rejected handler right in the single "then" method. That said, I greatly prefer instead to await on an async function and handle any rejections in a catch block.
- choward 6y agoExceptions aren't typed though so you either have to decode it in some way or treat every exception the same.
- gregopet 6y agoSure, people say they want to have to check for _every_ possible error, but then Java gets endless heat for having checked exceptions.
- abraxas 6y agoYes, this schizophrenic attitude drives me bonkers. Immature JS kids at my company love to make noises about Java’s verbosity yet spend hours debugging and troubleshooting most mundane errors because seemingly nothing is ever enforced by the JS interpreter. My theory is that nowadays Java is unpopular mostly because it is their parents’ programming language. That’s reason enough for these twenty somethings to despise it without even knowing the first thing about it.
- ragnese 6y agoHold on, though. There is plenty to dislike about Java. Everything can be null, interfaces have to be implemented at definition, broken array type variance, no const/mutable at the language level, no value types, etc...
- abraxas 6y agoNo question there is a lot to dislike about java but there are also things it got mostly right or better than competing languages of the day. Checked exceptions is in my opinion one of those.
- ragnese 6y agoI agree with that. I was heavily involved on the other thread someone linked to about Java's checked exceptions. Until that thread, I thought I was the only one who didn't accept that they were a mistake.
- emmanueloga_ 6y agoDon't write code like this... trying to fake sum types in an object oriented language ends up being a horrible, hard to maintain mess, similar to those examples on the original post. Typescript's "discriminated unions" [1] make for incredibly inelegant looking code for anything but the most basic sum types, the ergonomics are nothing like the experience of using Haskell or OCaml. I love sum types but in an OOP the equivalent implementation is the visitor pattern (if the problem calls for it). I was once starry eyed about "making invalid states irrepresentable". I even managed to introduce a code generator to bolt sum types into a production Dart app. My colleagues loved it (not) :-) Thing is, programs have this annoying tendency of being incorrect no matter what technique you use :-), there's no silver bullet! If a pattern is natural for a given language, it is better to use that pattern than to force it to be something it isn't. 1: https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-exhaustiveness-checking https://www.typescriptlang.org/docs/handbook/unions-and-inte...
- moomin 6y agoThe joke is, OO language have sum types, albeit incredibly broken ones, because any set of classes that inherits from the same base has the base as a sum type. You can surface this by implementing the visitor pattern.
- rmrfrmrf 6y agoIMO the `Either` construct should be avoided in JS because inevitably you'll either need to wrap everything in `try ... catch` anyway or you'll be pulling your hair out trying to figure out how to get them to work in an async/event context, in which case you'll end up re-inventing the Promise api (perhaps wrapped in an extra thunk to make it more fp-friendly or whatever). A more practical approach is to work along the grain of JS while getting inspiration from fp. A common snippet would be: function maybeUrl(str) { try { return new URL(str); } catch (err) { return null; } } This is much more straight-forward and interoperable. Dealing with something like Left<InvalidUrlError> where you've created an entire error subclass and wrapping function for an unrecoverable failure that will be discarded is way overkill. The unreliablility of thrown values in JS is a valid concern, but instead of trying to wrap all my code in a container construct, I simply wrap/convert the untrusted errors themselves if I need to use properties on an error reliably.
- rattray 6y agoTotally agree. In cases where the error type matters, I like to return rather than throw the error. type Success = string type Result = Success | BadInputError | NotFoundError | IncalculableError const foo = (input: I): Result => { //... }
- judah 6y agoTangent: This article made me think deeper about programming languages in general and I found it fascinating. So I went to subscribe to his blog in my feed reader, but alas, no RSS feed. :-(
- rattray 6y agoReminds me of a pattern I've wished would take off in TS, but hasn't (yet): const result = await fetchData().catch(e => e) if (_.isError(result)) { return "some failure" } return (await result.json()).foo TS is smart enough to know whether result will be of type Error or the Response type. This pattern should replace most Either cases when the left is an error state of some kind. Also should replace exceptions and promise rejections. You can also analogously replace a Maybe type with nullability, simply `type MaybeFoo = Foo | null`.
- tym0 6y agoI agree with Maybe not being that useful in TS but the point of an Either/Result type is that the caller is gonna handle it each branch differently. In your example the caller has no way to tell the difference between an error and the succesful return value.
- rattray 6y agoPerhaps I was unclear, the implicit typing is so: const result: FetchResult | Error = ... if (_.isError(result)) { // Here TS knows that result is of type Error return } // Here TS knows that result is of type FetchResult On my phone, apologies for shorthand In any case, with this pattern TS can easily tell the difference between success and error (or left and right or whatever) as long as the error / left case is a class of a different type than the success / right case. Tldr, where relevant, classes are cleaner than tagged disjoint unions in TS. Errors are one situation where this is relevant.
- mcintyre1994 6y agoI’ve seen and used the Maybe version of this a few times, I really like this Typescript feature. Using it for errors is clever :)
- tha0x3 6y agoThis is what happens when "coders" barely fumble through JavaScript, can barely hack a front-end together, and then self-promote themselves to "full-stack" developers without having a lick of clue on system basics, principles of computer science, application programming, basic fundamentals of programming languages and semantics, etc. You know, the stuff people go to school for.
- dang 6y agoWould you please stop creating accounts to break HN's guidelines with?
- jakelazaroff 6y agoThe gatekeeping isn’t necessary. Some people pick up programming because they have an idea they want to implement, rather than because they want a rigorous formal education in computer science.
- layoutIfNeeded 6y agoI pity the poor engineers inheriting this guy’s “clever” code a few years from now.
- valand 6y agoAuthor here. Input and further discussion would be appreciated.
- aussieguy1234 6y agoWith JS and Typescript, I find myself going back to logic error handling in the catch block, because there is only one Error type. You can't have multiple types of exceptions. So you have to set the error.type attribute and then do logic.
- MobiusHorizons 6y agoIt is worth noting that you can throw anything you want. It doesnt' have to be new Error(). There are definitely some drawbacks to this, such as if the errors thrown by your dependencies are of type Error, but the upside is that as long as you are consistent, you get a much richer set of types available. IMO this also works primarily because errors are very rarely thrown by native APIs, which instead encode failure into the return type. For this reason, you can typically have a reasonable level of control over what code can reasonably be expected to throw.
- tantaman 6y agoPlease please please don't perpetuate the use of either or maybe types :( They make software very hard to maintain and end up breaking anyone who depends on a library that uses them. Rich Hickey (author of Clojure) has a great discussion of Either and Maybe here: https://www.youtube.com/watch?v=YR5WdGrpoug https://www.youtube.com/watch?v=YR5WdGrpoug that dives into their unsoundness.
- darksaints 6y agoThe author of a dynamically typed language thinks strongly typed constructs are bad? You don't say...
- mattnewton 6y agoRick’s argument seems to be that we should have better tools in the language type system. If your language doesn’t give you those tools, these are still very useful types. In addition, the library maintenance issues he raises seem trivially solvable with languages that allow function polymorphism or have refactoring tools to me.
- simooooo 6y agoI hate when people use foo and bar. And then call them from within each other. Just make it easy to understand plz
- nsonha 6y agoWhy do you need Either when you have union? All the left types subclass Error so you can easily separate that from the successful value as well. Seems rather a dogmatic choice.