【发布时间】:2019-12-18 04:47:45
【问题描述】:
Typescript 能做简单的函数组合吗?我写了以下 compose、map 和 filter 的基本实现来测试。
下面设置类型和功能,然后实现。
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'])
})
当我使用 curried 参数调用 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 函数的结果,而不是让它们通过,这似乎是不可能的。
再一次,下面的内容应该没必要复习了,这只是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 functional-programming typescript-typings typescript-generics