gpt4 book ai didi

typescript - Typescript能够执行简单的功能组合吗?

转载 作者:行者123 更新时间:2023-12-03 23:42:51 24 4
gpt4 key购买 nike

Typescript能够执行简单的功能组合吗?我写出了以下基本实现,以进行组合,映射和筛选以进行测试。

类型和功能在下面设置,然后在之后实现。

javascript似乎不错,但是使用compose时,打字稿显示出误报。具体来说,似乎可以理解第一个函数将要返回的内容,但随后它不会将该信息传递给第二个函数的输入。设置后在下面进一步解释。

import { curry } from './curry'

type f1<a, b> = (a: a) => b
type f2_2<a, b, c> = (a: a, b: b) => c
type f2_1<a, b, c> = (a: a) => (b: b) => c
type f2<a, b, c> = f2_2<a, b, c> & f2_1<a, b, c>
type p<a> = (a: a) => boolean

//====[compose]================================
type b3 = <a, b, c>(f: f1<b, c>, g: f1<a, b>, a: a) => c
type b2 = <a, b, c>(f: f1<b, c>, g: f1<a, b>) => f1<a, c>
type b1 = <a, b, c>(f: f1<b, c>) => f2<f1<a, b>, a, c>
type b = b1 & b2 & b3

// bluebird :: (b -> c) -> (a -> b) -> a -> c
const compose: b = curry((f, g, a) => f(g(a)))

//====[filter]=================================
type fil2 = <a>(p: p<a>, xs: a[]) => a[]
type fil1 = <a>(p: p<a>) => f1<a[], a[]>
type fil = fil1 & fil2

// filter :: (a -> Boolean) -> [a] -> [a]
const filter: fil = curry((p, xs) => {
const len = xs.length
const r = Array()

for (let [i, j] = [0, 0]; i < len; i++) {
const v = xs[i]

if (p(v)) {
r[j++] = v
}
}

return r
})

//====[mapArr]=================================
type m2 = <a, b>(f1: f1<a, b>, xs: a[]) => b[]
type m1 = <a, b>(f: f1<a, b>) => f1<a[], b[]>
type m = m2 & m1

// map :: (a -> b) -> [a] -> [b]
const mapArr: m = curry((fn, xs) => {
const len = xs.length
const r = Array(len)

for (let i = 0; i < len; i++) {
r[i] = fn(xs[i])
}

return r
})

//====[prop]===================================
type z2 = <o, k extends keyof o>(propName: k, source: o) => o[k]
type z1 = <o, k extends keyof o>(propName: k) => f1<o, o[k]>
type z = z2 & z1

// prop :: String -> a -> b
// prop :: Number -> a -> b
// prop :: Symbol -> a -> b
const prop: z = curry((propName, obj) => obj[propName])


当我将鼠标悬停在下面组成的过滤器函数上时,TS知道它将返回 data[];但是,如果我将鼠标悬停在 mappArr函数上,TS将显示输入为 unknown[],因此会为 id字段抛出错误的肯定。我究竟做错了什么?

//====[typescript test]===================================
interface data {
relationId: string
id: string
}

type isMine = p<data>
// isMine :: a -> Boolean
const isMine: isMine = x => x.relationId === '1'

type t = f1<data[], string[]>
const testFn: t = compose(
// @ts-ignore
mapArr(prop('id')),
//=> ^^^^^
// error TS2345: Argument of type '"id"' is not assignable to
// parameter of type 'never'.
filter(isMine)
)

//====[javascript test]================================
const r = testFn([
{ id: '3', relationId: '1' },
{ id: '5', relationId: '3' },
{ id: '8', relationId: '1' },
])

test('javascript is correct', () => {
expect(r).toEqual(['3', '8'])
})


当我调用带有咖喱参数的compose时,Typescript的误报会变得更糟。

const testFn: t = compose (mapArr(prop('id')))
(filter(isMine))
//=> ^^^^^^^^^^^^^^^
// error TS2322: Type 'unknown[]' is not assignable to type 'f1<data[], string[]>'.
// Type 'unknown[]' provides no match for the signature '(a: data[]): string[]'.


// ================================================ =====================

@shanonjackson想查看咖喱功能。我几乎肯定地说,它不可能成为任何问题的根源,因为上述每个函数的类型都应用于curry函数的结果,而不是让它们通过,这似乎是不可能的。

同样,没有必要检查以下内容,这只是 curry的标准实现:

function isFunction (fn) {
return typeof fn === 'function'
}

const CURRY_SYMB = '@@curried'

function applyCurry (fn, arg) {
if (!isFunction(fn)) {
return fn
}

return fn.length > 1
? fn.bind(null, arg)
: fn.call(null, arg)
}

export function curry (fn) {
if (fn[CURRY_SYMB]) {
return fn
}

function curried (...xs) {
const args = xs.length
? xs
: [undefined]

if (args.length < fn.length) {
return curry(Function.bind.apply(fn, [null].concat(args)))
}

const val =
args.length === fn.length
? fn.apply(null, args)
: args.reduce(applyCurry, fn)

if (isFunction(val)) {
return curry(val)
}

return val
}

Object.defineProperty(curried, CURRY_SYMB, {
enumerable: false,
writable: false,
value: true,
})

return curried
}

最佳答案

无论我希望有多少,TypeScript都不是Haskell(或在此处插入您喜欢的类型化语言)。它的类型系统有很多漏洞,并且不能保证其类型推断算法会产生声音结果。它是not meant to be provably correct(启用惯用JavaScript更为重要)。公平地说,Haskell没有子类型,面对子类型的类型推断是more difficult。无论如何,这里的简短答案是:


不要试图依赖上下文类型(从函数输出中推断类型)
尝试依靠前向类型推断(从函数输入中推断类型)
当类型推断失败时,注释并指定类型。




由于TypeScript不是Haskell,所以我要做的第一件事就是使用TypeScript命名约定。另外,由于compose()和其余的实现都不是问题,因此我将仅declare这些功能并省略实现。

让我们来看看:

type Predicate<A> = (a: A) => boolean;

//====[compose]===============================
declare function compose<B, C>(
f: (x: B) => C
): (<A>(g: (a: A) => B) => (a: A) => C) & (<A>(g: (a: A) => B, a: A) => C); //altered
declare function compose<A, B, C>(f: (b: B) => C, g: (a: A) => B): (a: A) => C;
declare function compose<A, B, C>(f: (b: B) => C, g: (a: A) => B, a: A): C;

//====[filter]=================================
declare function filter<A>(p: Predicate<A>, xs: A[]): A[];
declare function filter<A>(p: Predicate<A>): (xs: A[]) => A[];

//====[mapArr]=================================
declare function mapArr<A, B>(f1: (a: A) => B): (xs: A[]) => B[];
declare function mapArr<A, B>(f1: (a: A) => B, xs: A[]): B[];

//====[prop]===================================
declare function prop<K extends keyof any>(
propName: K
): <O extends Record<K, any>>(source: O) => O[K]; // altered
declare function prop<K extends keyof O, O>(propName: K, source: O): O[K];




多数只是直接重写,但我想提请您注意我所做的两个实质性更改。 compose()的第一个重载签名已从更改为

declare function compose<A, B, C>(
f: (x: B) => C
): ((g: (a: A) => B) => (a: A) => C) & ((g: (a: A) => B, a: A) => C);




declare function compose<B, C>(
f: (x: B) => C
): (<A>(g: (a: A) => B) => (a: A) => C) & (<A>(g: (a: A) => B, a: A) => C);


也就是说,不是 compose()ABC中是通用的,而是仅在 BC中是通用的,并返回在 A中的通用函数。之所以这样做,是因为在TypeScript中,通常根据传递给函数的参数的类型(而不是函数的预期返回类型)进行泛型函数类型参数的推断。是的,有 contextual typing可以从所需的输出类型推断输入类型,但是它不如普通的“时间前向”推断可靠。

当在 A中调用一个泛型函数时,如果没有任何参数充当 A的推理站点,可能会发生什么? (例如,当它只有一个类型为 (x: B) => C的参数时)编译器将为 unknown推断 Aas of TS3.5),以后您会不满意。通过将通用参数规范推迟到调用返回的函数之前,我们有更好的机会推断 A您的意图。



同样,我将 prop()的第一个重载从

declare function prop<K extends keyof O, O>(
propName: K,
): (source: O) => O[K];




declare function prop<K extends keyof any>(
propName: K
): <O extends Record<K, any>>(source: O) => O[K];


这具有相同的问题...对类似 prop("id")的调用将导致将 K推断为 "id",并且可能将 O推断为 unknown,然后由于 "id"不是已知是 keyof unknown(即 never)的一部分,您将收到错误消息。这很可能是引起您所看到的错误的原因。

无论如何,我将 O的规范推迟到调用返回函数时使用。这意味着我需要将通用约束从 K extends keyof O转换为 O extends Record<K, any> ...说类似的话,但方向相反。



很好,如果我们尝试您的 compose()测试会怎样?

//====[typescript test]===================================
interface Data {
relationId: string;
id: string;
}
type isMine = Predicate<Data>;
const isMine: isMine = x => x.relationId === "1";

const testFnWithFaultyContextualTyping: (a: Data[]) => string[] = compose(
mapArr(prop("id")), // error!
filter(isMine)
);
// Argument of type '<O extends Record<"id", any>>(source: O) => O["id"]'
// is not assignable to parameter of type '(a: unknown) => any'.


糟糕,仍然存在错误。这是一个不同的错误,但是这里的问题是,通过将返回值注释为 (a: Data[]) => string[],它触发了一些上下文类型,这些类型在正确推断 prop("id")之前逐渐消失。在这种情况下,我的本能是尝试不依赖于上下文类型,而是查看常规类型推断是否起作用:

const testFn = compose(
mapArr(prop("id")),
filter(isMine)
); // okay
// const testFn: (a: Data[]) => string[]


这样行得通。时间前向推断的行为符合预期,并且 testFn是您期望的类型。



如果我们尝试您的咖喱版本:

const testFnCurriedWithFaultyContextualTyping = compose(mapArr(prop("id")))( // error!
filter(isMine)
);
// Argument of type '<O extends Record<"id", any>>(xs: O[]) => O["id"][]'
// is not assignable to parameter of type '(x: unknown) => any[]'.


是的,我们仍然会出现错误。尝试进行上下文类型输入又是一个问题……鉴于您打算如何调用其返回的函数,编译器实际上并不真正知道如何推断 compose()参数的类型。在这种情况下,无法通过移动通用类型来修复它。推断就不可能在这里发生。相反,我们可以依靠在 compose()函数调用中显式指定泛型类型参数:

const testFnCurried = compose<Data[], string[]>(mapArr(prop("id")))(
filter(isMine)
); // okay


这样可行。



无论如何,我希望能给您一些思路,尽管可能会令人失望。每当我为TypeScript的不健全和有限的类型推断功能感到难过时,我都会提醒自己有关其所有类型系统的 neat features,即 make up for it,至少是 in my opinion

无论如何,祝你好运!

Link to code

关于typescript - Typescript能够执行简单的功能组合吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57447070/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com