[英]How to narrow the Option type inferred from fp-ts R.lookup
I am trying to lookup the value of a key with fp-ts
.我正在尝试使用
fp-ts
查找键的值。 The key might not be present.密钥可能不存在。
type Data = {
a?: number;
b?: string;
c?: { e?: string; w: number };
};
const e = R.lookup('b')({ a: 23, b: 'asdfasdf', c: { e: 'asdf', w: 23}} as Data)
I thought the type of e
inferred by Typescript would be Option<string>
but it is:我认为由 Typescript 推断的
e
类型是Option<string>
但它是:
O.Option<string | number | {
e?: string | undefined;
w: number;
}>
This looks like all the possible types in Data
.这看起来像
Data
中所有可能的类型。 Is this intended behaviour?这是预期的行为吗? And how shall I narrow my types to only the "potential" type of
b
, so that I can continue a pipeline from an Option<string>
.以及如何将我的类型缩小到仅
b
的“潜在”类型,以便我可以从Option<string>
继续管道。
I tried the approach below, which marks e
as Option<string>
but then flags the entire object {a: 23...
as "not assignable to parameter of type 'Record<string, string>'"我尝试了下面的方法,它将
e
标记为Option<string>
但随后将整个 object {a: 23...
标记为“不可分配给'Record<string, string>'类型的参数”
const getB = R.lookup("b")
const e = getB<string>({ a: 234, b: "asdfasdf", c: { e: "asdf", w: 23 } } as Data);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// Argument of type 'Data' is not assignable to parameter of type 'Record<string, string>'.
Property 'a' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
I think R.lookup
's types just misalign with what you're hoping for.我认为
R.lookup
的类型与您的期望不一致。 The problem is that R.lookup
does not keep track of the fact that you're expecting 'b'
to be a keyof Data
.问题是
R.lookup
没有跟踪您期望'b'
成为keyof Data
的事实。 It just knows that b
is a string
and the function it returns has the signature:它只知道
b
是一个string
,并且它返回的函数具有签名:
<A>(record: Record<string, A>) => Option<A>
So it looks at data as a Record
and the type for A becomes Data[keyof Data]
which results in the type you're seeing.因此,它将数据视为
Record
,而 A 的类型变为Data[keyof Data]
,这会导致您看到的类型。
I think this mainly just stems from wanting to use a Record
in a way it's not really intended to be used.我认为这主要源于想要以一种并非真正打算使用的方式使用
Record
。 A Record
has a uniform type for any key you hand into it.一个
Record
对你提交给它的任何键都有一个统一的类型。 In order to get a uniform type, the fp-ts
code has to infer that at each key there might be the union type of all the values in data.为了获得统一的类型,
fp-ts
代码必须推断在每个键处可能存在数据中所有值的联合类型。
If instead, you know that you have a Data
object, you can write code like:相反,如果您知道自己有一个
Data
对象,则可以编写如下代码:
import * as O from 'fp-ts/Option';
const data: Data = { a: 234, b: "asdfasdf", c: { e: "asdf", w: 23 } };
const maybeB: Option<string> = O.fromNullable(data.b);
If instead, you don't actually know that you'll have Data
because your object is actually unknown
, rather than casting it to Data
you could use a library (like io-ts
since you're already using fp-ts
) to first validate the shape of the data.相反,如果您实际上并不知道您将拥有
Data
,因为您的对象实际上是unknown
,而不是将其转换为Data
您可以首先使用库(例如io-ts
,因为您已经在使用fp-ts
)验证数据的形状。 For example例如
import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option';
import { flow } from 'fp-ts/function';
import * as t from 'io-ts';
const dataSchema = t.partial({
a: t.number,
b: t.string,
c: t.type({
e: t.union([t.string, t.undefined]),
w: t.number,
}),
});
// This will be equivalent to your data definition but can be inferred
// from the schema definition
type Data = t.TypeOf<typeof dataSchema>;
// The explicit type here is unnecessary but just to illustrate
const getB: (input: unknown) => O.Option<string> = flow(
dataSchema.decode, // -> Either<t.Errors, Data>
O.fromEither, // -> Option<Data>
O.chain((d) => O.fromNullable(d.b)) // Option<string>
);
console.log(getB(data)); // { _tag: 'Some', value: 'asdfasdf' }
As per other comments, possibly the ideal approach to this would be using io-ts
for monocle-ts
.根据其他评论,可能理想的方法是将
io-ts
用于monocle-ts
。 But seeing you seem interested in how fp-ts
could handle it itself - for just a single field - I offer you this.但是看到您似乎对
fp-ts
如何自己处理它感兴趣 - 仅针对单个字段 - 我为您提供这个。
The gist is, that once you have got yourself the optional, you simply need to use typescript type narrowing.要点是,一旦你有了可选的,你只需要使用 typescript 类型缩小。 This can be done with
fp-ts
various fromPredicate
with the overloaded form using a Refinement
.这可以通过使用
fromPredicate
fp-ts
Refinement
。 And for a lot of basic types (like string
) fp-ts
provide some out of the box - such as S.isString
below.而对于很多基本类型(比如
string
) fp-ts
提供了一些开箱即用的——比如下面的S.isString
。
import { pipe } from "fp-ts/function";
import * as O from "fp-ts/Option";
import * as R from "fp-ts/Record";
import * as S from "fp-ts/string";
type Data = {
a?: number;
b?: string;
c?: { e?: string; w: number };
};
const testData: Data = { a: 23, b: "asdfasdf", c: { e: "asdf", w: 23 } };
const b = pipe(
testData,
R.lookup("b"),
O.chain(
O.fromPredicate(S.isString)
)
);
But although that gets you by with the single field, anything more that that is going to require more and more boilerplate, so then you might as well use io-ts
.但是,尽管这可以让您使用单个字段,但任何其他内容都需要越来越多的样板文件,所以您不妨使用
io-ts
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.