Typescript 能够进行简单的函数组合吗?
Is Typescript capable of doing simple function composition?
Typescript 可以进行简单的函数组合吗?我写了下面的compose、map、filter的基本实现来测试。
下面设置类型和函数,然后再实现。
javascript 似乎没问题,但是当使用 compose
时打字稿显示误报。具体来说,它似乎了解第一个函数将要做什么 return,但是它不会将该信息传递给第二个函数的输入。设置后在下面进一步解释。
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 明白它会 return 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
}
是的,您甚至可以按照此处的示例在 3.5.1 中编写泛型函数;不想偷偷重新发明轮子就写在这里。
https://devblogs.microsoft.com/typescript/announcing-typescript-3-5/
不幸的是,如果没有看到正在导入的 curry 的定义,很难给出一个未知的原因,但是当涉及泛型函数时,在 typescript 3.5.1 中未知是泛型不能的标志被推断
无论我多么希望它不是这样,TypeScript 都不是 Haskell(或在此处插入您最喜欢的类型化语言)。它的类型系统有很多漏洞,它的类型推断算法不能保证产生良好的结果。是 not meant to be provably correct (enabling idiomatic JavaScript is more important). To be fair to TypeScript, Haskell doesn't have subtyping, and type inference in the face of subtyping is 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()
在 A
、B
和 C
中是通用的,它只在 B
和 [= 中是通用的25=] 和 return 是 A
中的泛型函数。我这样做是因为在 TypeScript 中,泛型函数类型参数的推断通常基于函数的传入参数类型,而不是函数的预期 return 类型。是的,有 contextual typing 可以从所需的输出类型推断输入类型,但它不如正常的 "forward-in-time" 推断可靠。
当您在 A
中调用泛型函数时,当其参数的 none 作为 A
的推理站点时,可能会发生什么? (例如,当它只有一个(x: B) => C
类型的参数时)编译器会为A
推断出unknown
(as of TS3.5),你以后会不高兴的。通过将通用参数规范推迟到 returned 函数被调用时,我们有更好的机会以您想要的方式推断 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
的规范推迟到调用 returned 函数时。这意味着我需要将通用约束从 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'.
糟糕,那里仍然有错误。这是一个不同的错误,但这里的问题是,通过将您的 return 值注释为 (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()
的参数类型,因为你打算如何调用它的 returned 函数。在这种情况下,无法通过移动通用类型来修复它。推理不能在这里发生。相反,我们可以退而求其次,在 compose()
函数调用中显式指定泛型类型参数:
const testFnCurried = compose<Data[], string[]>(mapArr(prop("id")))(
filter(isMine)
); // okay
有效。
无论如何,我希望能给您一些如何继续的想法,尽管可能会令人失望。每当我对 TypeScript 的不健全和有限的类型推断能力感到难过时,我都会提醒自己它所有的类型系统 neat features that make up for it, at least in my opinion.
总之,祝你好运!
如果您愿意模仿 Promise
-esque API,您可以编写一个函数,首先“提升”您的初始值,然后您可以链接后续函数调用then
.
您只需要手动柯里化函数或使用库。
我们可以看到 TypeScript 允许在附加到对象的函数上使用可能无限的类型。 (另外,我们基本上只是写了一个非常简单的monad...)
See this compile in the TypeScript playground
interface Composer<T> {
value: T,
then<R>(f: (x: T) => R): Composer<R>,
unwrap(): T,
}
function compose<T>(value: T): Composer<T> {
return {
value,
then<R>(f: (x: T) => R) {
const result = f(this.value);
return compose(result);
},
unwrap() {
return this.value;
},
};
}
//
// Example usage
const map = <T, R>(f: (x: T) => R) => (xs: T[]) => xs.map(f);
const filter = <T,>(f: (x: T) => boolean) => (xs: T[]) => xs.filter(f);
const backpack = [
{ name: 'apple', coins: 5 },
{ name: 'pear' , coins: 10 },
{ name: 'sword', coins: 45 },
];
const result =
compose(backpack)
.then(map(x => ({ ...x, coins: x.coins * 2 })))
.then(filter(x => x.coins < 25))
.unwrap();
console.log(result); // "[{ name: "apple", coins: 10 }, { "name": "pear", coins: 20 }]
Typescript 可以进行简单的函数组合吗?我写了下面的compose、map、filter的基本实现来测试。
下面设置类型和函数,然后再实现。
javascript 似乎没问题,但是当使用 compose
时打字稿显示误报。具体来说,它似乎了解第一个函数将要做什么 return,但是它不会将该信息传递给第二个函数的输入。设置后在下面进一步解释。
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 明白它会 return 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
}
是的,您甚至可以按照此处的示例在 3.5.1 中编写泛型函数;不想偷偷重新发明轮子就写在这里。
https://devblogs.microsoft.com/typescript/announcing-typescript-3-5/
不幸的是,如果没有看到正在导入的 curry 的定义,很难给出一个未知的原因,但是当涉及泛型函数时,在 typescript 3.5.1 中未知是泛型不能的标志被推断
无论我多么希望它不是这样,TypeScript 都不是 Haskell(或在此处插入您最喜欢的类型化语言)。它的类型系统有很多漏洞,它的类型推断算法不能保证产生良好的结果。是 not meant to be provably correct (enabling idiomatic JavaScript is more important). To be fair to TypeScript, Haskell doesn't have subtyping, and type inference in the face of subtyping is 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()
在 A
、B
和 C
中是通用的,它只在 B
和 [= 中是通用的25=] 和 return 是 A
中的泛型函数。我这样做是因为在 TypeScript 中,泛型函数类型参数的推断通常基于函数的传入参数类型,而不是函数的预期 return 类型。是的,有 contextual typing 可以从所需的输出类型推断输入类型,但它不如正常的 "forward-in-time" 推断可靠。
当您在 A
中调用泛型函数时,当其参数的 none 作为 A
的推理站点时,可能会发生什么? (例如,当它只有一个(x: B) => C
类型的参数时)编译器会为A
推断出unknown
(as of TS3.5),你以后会不高兴的。通过将通用参数规范推迟到 returned 函数被调用时,我们有更好的机会以您想要的方式推断 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
的规范推迟到调用 returned 函数时。这意味着我需要将通用约束从 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'.
糟糕,那里仍然有错误。这是一个不同的错误,但这里的问题是,通过将您的 return 值注释为 (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()
的参数类型,因为你打算如何调用它的 returned 函数。在这种情况下,无法通过移动通用类型来修复它。推理不能在这里发生。相反,我们可以退而求其次,在 compose()
函数调用中显式指定泛型类型参数:
const testFnCurried = compose<Data[], string[]>(mapArr(prop("id")))(
filter(isMine)
); // okay
有效。
无论如何,我希望能给您一些如何继续的想法,尽管可能会令人失望。每当我对 TypeScript 的不健全和有限的类型推断能力感到难过时,我都会提醒自己它所有的类型系统 neat features that make up for it, at least in my opinion.
总之,祝你好运!
如果您愿意模仿 Promise
-esque API,您可以编写一个函数,首先“提升”您的初始值,然后您可以链接后续函数调用then
.
您只需要手动柯里化函数或使用库。
我们可以看到 TypeScript 允许在附加到对象的函数上使用可能无限的类型。 (另外,我们基本上只是写了一个非常简单的monad...)
See this compile in the TypeScript playground
interface Composer<T> {
value: T,
then<R>(f: (x: T) => R): Composer<R>,
unwrap(): T,
}
function compose<T>(value: T): Composer<T> {
return {
value,
then<R>(f: (x: T) => R) {
const result = f(this.value);
return compose(result);
},
unwrap() {
return this.value;
},
};
}
//
// Example usage
const map = <T, R>(f: (x: T) => R) => (xs: T[]) => xs.map(f);
const filter = <T,>(f: (x: T) => boolean) => (xs: T[]) => xs.filter(f);
const backpack = [
{ name: 'apple', coins: 5 },
{ name: 'pear' , coins: 10 },
{ name: 'sword', coins: 45 },
];
const result =
compose(backpack)
.then(map(x => ({ ...x, coins: x.coins * 2 })))
.then(filter(x => x.coins < 25))
.unwrap();
console.log(result); // "[{ name: "apple", coins: 10 }, { "name": "pear", coins: 20 }]