简体   繁体   English

如何缩小从 fp-ts R.lookup 推断的选项类型

[英]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.而对于很多基本类型(比如stringfp-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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM