-
-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
if let construction? #109
Comments
It appears that you're focused on this behavior of TypeScript can't implement Rust's control flow syntax — although I'd bet I'm not alone in wishing it could. Something that you can use instead is (Option<T>).map(fn: (val: T) => U) => Option<U> The callback is lazy and is only invoked for
map<U>(fn: (val: T) => U): OptSome<U> {
return some_constructor<U>(fn(val));
}
map<U>(_fn: (val: T) => U): OptNone<U> {
return none_constructor<U>();
} Is that what you're looking for? If not, can you explain in more detail what it is that you want from In case you're interested in more on types... You also said:
There's a subtle difference between using the import {isSome, type Option} from '@sniptt/monads';
declare let numOpt: Option<number>;
// Using the instance method:
if (numOpt.isSome()) {
// The compiler still doesn't know that it has a value. It's still just `Option<T>`:
numOpt;
//^? let numOpt: Option<number>
}
// Using the function:
if (isSome(numOpt)) {
// Now the compiler knows that it's `OptSome(T)`:
numOpt;
//^? let numOpt: OptSome<number>
} Because of the way that type guards work in TS (return type predicates can only be conveyed about parameter values), zero-argument methods can't be used as type guards for their owner objects. This is why the code above works the way it does.
If you examine these interface types in the source, you can see that the return types are truly sound (type-safe): export interface Option<T> {
// --- snip ---
unwrap(): T | never;
// --- snip ---
}
export interface OptSome<T> extends Option<T> {
// --- snip ---
unwrap(): T;
// --- snip ---
}
export interface OptNone<T> extends Option<T> {
// --- snip ---
unwrap(): never;
// --- snip ---
} The "problem" is in the way that // `never` is always absorbed in unions:
type U1 = string | never;
//^? type U1 = string
type U2 = any | never;
//^? type U2 = any
type U3 = unknown | never;
//^? type U3 = unknown
type U4 = void | never;
//^? type U4 = void
// `unknown` always dominates other types (except `any`):
type U5 = string | number | unknown | never;
//^? type U5 = unknown
type U6 = string | number | unknown | never | any;
//^? type U6 = any So when using the import {isNone, isSome, type Option} from '@sniptt/monads';
declare let numOpt: Option<number>;
if (numOpt.isSome()) {
numOpt;
//^? let numOpt: Option<number>
// The return type here is acutally `T | never`, but `never` is absorbed:
const unwrapped = numOpt.unwrap();
//^? const unwrapped: number
}
if (isSome(numOpt)) {
numOpt;
//^? let numOpt: OptSome<number>
// The return type here is acutally just `T`:
const unwrapped = numOpt.unwrap();
//^? const unwrapped: number
}
// Using `isNone`:
if (isNone(numOpt)) {
numOpt;
//^? let numOpt: OptNone<number>
// The return type here is soundly resolved:
const unwrapped = numOpt.unwrap();
//^? const unwrapped: never
} |
The The render() {
return (
{post.url.match({
some: url => <div>{url} HERE!</div>,
none: <></>,
})} // This statement actually does return `JSX.Element` I would love to be able to do: {post.url.let(url => <div>{url} HERE!</div>)} In rust, this would be like: if let Some(url) = post.url {
<div>{url} HERE!</div>
} |
Of course: that makes sense — it's quite challenging for values to be useful if they must always stay inside a block scope. But what if the unwrapped value doesn't exist (a
If
In that Rust code (assuming that all of the syntax were valid), the React element would still need to be assigned to a variable to be used outside the block, like this: let react_element = if let Some(url) = post.url {
<div>{url} HERE!</div>
}; But — in Rust — So, in the case that It's simple to fix this so that the program could compile: just supply a default let react_element = if let Some(url) = post.url {
<div>{url} HERE!</div>
} else {
<></>
}; The TypeScript equivalent of the example Rust block above would be to chain the following methods together:
Putting that together with your JSX example looks like this: import type {Option} from '@sniptt/monads';
declare let post: { url: Option<URL> };
const reactElement = post.url.map(url => (<div>{url.href} HERE!</div>)).unwrapOr(<></>);
//^? const reactElement: JSX.Element However, if you really want to use another type of default, that's certainly possible — TypeScript doesn't have the same requirement for a variable to be of a single type (that's exactly what unions are: one of multiple types). You can do something like this: import type {ReactElement} from 'react';
import type {Option} from '@sniptt/monads';
declare let post: { url: Option<URL> };
const reactElementOrUndefined = post.url
.map<ReactElement | undefined>(url => (<div>{url.href} HERE!</div>))
.unwrapOr(undefined); And, of course, you can create your own function abstraction to lazily execute and return the result of a callback function depending on the import type {Option} from '@sniptt/monads';
function matchOption <T, SomeResult, NoneResult>(
opt: Option<T>,
someFn: (val: T) => SomeResult,
noneFn: () => NoneResult,
): SomeResult | NoneResult;
function matchOption <T, SomeResult>(
opt: Option<T>,
someFn: (val: T) => SomeResult,
): SomeResult | undefined;
function matchOption <T, SomeResult, NoneResult>(
opt: Option<T>,
someFn: (val: T) => SomeResult,
noneFn?: () => NoneResult,
) {
return opt.isSome() ? someFn(opt.unwrap()) : noneFn?.();
}
// Use:
declare let strOpt: Option<string>;
const upper = (str: string): string => str.toUpperCase();
// Explicitly returning `undefined` in the None callback:
const uppercaseOrUndefined1 = matchOption(strOpt, upper, () => undefined);
//^? const uppercaseOrUndefined1: string | undefined
// Omitting the None callback always results in `undefined` as the return type
// in the case of a None variant:
const uppercaseOrUndefined2 = matchOption(strOpt, upper);
//^? const uppercaseOrUndefined2: string | undefined ...which is essentially an ever-so-slightly less verbose version of
The type inference is also a bit more flexible because of the generics in the overload signature. Is this syntactically more concise than |
I'm not sure if this would be possible in typescript.
In addition to
match
, rust also hasif let Some(X) = an_opt_var {
....This is really useful, since often the
None
case isn't used / ignored.It's possible to do
if (opt.isSome()) { let x = opt.unwrap()...
but that's less compiler-checked, and it requires an unsafe unwrap.I'd like this a lot since my lemmy-ui codebase has tons of these verbose
.match
whilst ignoring the none case, all over the code.The text was updated successfully, but these errors were encountered: