Typescript 将IO映射到fp ts中任意一个的数组
有人能帮我弄清楚如何在fp ts中实现这一点吗 const$=cheerio.load'some text'; 常量测试=$'table tr'。获取 .maprow=>$row.find'a' .maplink=>link.attr'data-test'?link.attr'data-test':null .filterv=>v!=无效的 我也可以用task来完成所有的工作,但我不知道如何将它与IO混合,或者我根本不应该使用IO 这就是我到目前为止的想法: 常量选择器r=a:ChereIOStatic:ChereIOSelector=>s:any,c?:any,r?:any=>as,c,r; const getElementText=text:string=>{ 回流管 IO.ofcheerio.load, IO.apIO.oftext, IO.mapselector, IO.mapx=>x‘表tr’, //我不知道在这里该做什么 ; } 更新: 我必须提到并澄清,对我来说,最具挑战性的部分是如何将输入从IO更改为一个或多个数组,然后过滤或忽略左边的,并继续执行任务或任务 TypeScript错误为类型“或”不可分配给类型“IO”Typescript 将IO映射到fp ts中任意一个的数组,typescript,fp-ts,Typescript,Fp Ts,有人能帮我弄清楚如何在fp ts中实现这一点吗 const$=cheerio.load'some text'; 常量测试=$'table tr'。获取 .maprow=>$row.find'a' .maplink=>link.attr'data-test'?link.attr'data-test':null .filterv=>v!=无效的 我也可以用task来完成所有的工作,但我不知道如何将它与IO混合,或者我根本不应该使用IO 这就是我到目前为止的想法: 常量选择器r=a:ChereIOSt
如果您想正确地执行此操作,那么您需要将所有非确定性非纯函数调用包装在IO或IO中,这取决于它们是否能够失败 首先让我们定义哪些函数调用是纯函数调用,哪些不是纯函数调用。我发现最容易想到的是这样的——如果函数总是为相同的输入提供相同的输出,并且不会引起任何可观察到的副作用,那么它是纯的 相同的输出并不意味着引用相等,它意味着结构/行为相等。因此,如果您的函数返回另一个函数,那么这个返回的函数可能不是同一个函数对象,但它的行为必须与原始函数相同,才能被视为纯函数 因此,在这些术语中,以下是正确的: cherio.负载是纯的 美元是纯的 .得到不是纯粹的 .发现不是纯粹的 .attr不是纯的 地图是纯的 .过滤器是纯的 现在,让我们为所有非纯函数调用创建包装器:
const getIO = selection => IO.of(selection.get())
const findIO = (...args) => selection => IO.of(selection.find(...args))
const attrIO = (...args) => element => IO.of(element.attr(...args))
需要注意的是,这里我们在元素数组的包装版本中应用非纯函数.attr或attrIO。如果我们只是将属性映射到数组上,我们就得到了数组,但它不是非常有用,我们需要IO
因此,如果您有一个数组行,并且希望对其应用attrIO,则可以这样做:
import { array } from 'fp-ts/lib/Array';
import { io } from 'fp-ts/lib/IO';
const rows: Array<...> = ...;
// normal map
const mapped: Array<IO<...>> = rows.map(attrIO('data-test'));
// same result as above `mapped`, but in fp-ts way instead of native array map
const mappedFpTs: Array<IO<...>> = array.map(rows, attrIO('data-test'));
// now applying traverse instead of map to "flip" the `IO` with `Array` in the type signature
const result: IO<Array<...>> = array.traverse(io)(rows, attrIO('data-test'));
现在,getTests将返回原始代码中测试变量中相同元素的IO
免责声明:我没有通过编译器运行代码,它可能有一些打字错误或错误。您可能还需要做出一些努力,使其全部为强类型
编辑:
若您想保留有关错误的信息(在这种情况下,其中一个a元素上缺少数据测试属性),那个么您有几个选项可以这样做。当前getTests返回一个IO。要在此处匹配错误信息,您可以执行以下操作:
IO—返回数组的IO,其中每个元素都是错误或值。要使用它,您仍然需要在以后进行过滤以消除错误。这是最灵活的解决方案,因为您不会丢失任何信息,但感觉也没用,因为在本例中,这两种方法都与string | null几乎相同。
如果您想正确地执行此操作,那么您需要将所有非确定性非纯函数调用包装在IO或IO中,这取决于它们是否能够失败 首先让我们定义哪些函数调用是纯函数调用,哪些不是纯函数调用。我发现最容易想到的是这样的——如果函数总是为相同的输入提供相同的输出,并且不会引起任何可观察到的副作用,那么它是纯的 相同的输出并不意味着引用相等,它意味着结构/行为相等。因此,如果您的函数返回另一个函数,那么这个返回的函数可能不是同一个函数对象,但它的行为必须与原始函数相同,才能被视为纯函数 因此,在这些术语中,以下是正确的: cherio.负载是纯的 美元是纯的 .得到不是纯粹的 .发现不是纯粹的 .attr不是纯的 地图是纯的 .过滤器是纯的 现在,让我们为所有非纯函数调用创建包装器:
const getIO = selection => IO.of(selection.get())
const findIO = (...args) => selection => IO.of(selection.find(...args))
const attrIO = (...args) => element => IO.of(element.attr(...args))
需要注意的是,这里我们在元素数组的包装版本中应用非纯函数.attr或attrIO。如果我们只是将属性映射到数组上,我们就得到了数组,但它不是非常有用,我们需要IO
因此,如果您有一个数组行,并且希望对其应用attrIO,则可以这样做:
import { array } from 'fp-ts/lib/Array';
import { io } from 'fp-ts/lib/IO';
const rows: Array<...> = ...;
// normal map
const mapped: Array<IO<...>> = rows.map(attrIO('data-test'));
// same result as above `mapped`, but in fp-ts way instead of native array map
const mappedFpTs: Array<IO<...>> = array.map(rows, attrIO('data-test'));
// now applying traverse instead of map to "flip" the `IO` with `Array` in the type signature
const result: IO<Array<...>> = array.traverse(io)(rows, attrIO('data-test'));
现在,getTests将返回原始代码中测试变量中相同元素的IO
免责声明:我没有通过编译器运行代码,它可能有一些打字错误或错误。您可能还需要做出一些努力,使其全部为强类型
编辑:
若您想保留有关错误的信息(在这种情况下,其中一个a元素上缺少数据测试属性),那个么您有几个选项可以这样做。当前getTests返回一个IO。要在此处匹配错误信息,您可以执行以下操作:
IO—返回数组的IO,其中每个元素都是错误或值。要使用它,您仍然需要在以后进行过滤以摆脱它
错误。这是最灵活的解决方案,因为您不会丢失任何信息,但感觉也没用,因为在本例中,这两种方法都与string | null几乎相同。
你不能用一个选择器吗?类似于$'table tr a[data test]'的东西您不能用一个选择器来完成吗?类似于$'table tr a[data test]'的东西非常好,谢谢你的回答。还有一件事我很好奇,那就是ioother,实际上我想我应该用它来跳过代码中的.filter部分。你能改进你的答案以涵盖这一部分吗?我想如果我们修改属性以返回IOI,两者都可以完成这项工作,我只是不知道如何将打字改回IOI更新了问题的这一部分。请看一看。谢谢这太有趣了!非常感谢您花时间解释多重方法。非常好,谢谢您的回答。还有一件事我很好奇,那就是ioother,实际上我想我应该用它来跳过代码中的.filter部分。你能改进你的答案以涵盖这一部分吗?我想如果我们修改属性以返回IOI,两者都可以完成这项工作,我只是不知道如何将打字改回IOI更新了问题的这一部分。请看一看。谢谢这太有趣了!非常感谢您花时间解释多重方法。
import * as Either from 'fp-ts/lib/Either';
const attrIO = (...args) => element: IO<Either<Error, string>> => IO.of(Either.fromNullable(new Error("not found"))(element.attr(...args) ? element.attr(...args): null));
const getTests = (text: string): IO<Either<Error, string>[]> => {
const $ = cheerio.load(text);
return pipe(
$('table tr'),
getIO,
IO.chain(rows => array.traverse(io)(rows, flow($, findIO('a')))),
IO.chain(links => array.traverse(io)(links, attrIO('data-test')))
);
}
import * as Either from 'fp-ts/lib/Either';
import * as IOEither from 'fp-ts/lib/IOEither';
const { ioEither } = IOEither;
const attrIOEither = (...args) => element: IOEither<Error, string> => IOEither.fromEither(Either.fromNullable(new Error("not found"))(element.attr(...args) ? element.attr(...args): null));
const getTests = (text: string): IOEither<Error, string[]> => {
const $ = cheerio.load(text);
return pipe(
$('table tr'),
getIO,
IO.chain(rows => array.traverse(io)(rows, flow($, findIO('a')))),
IOEither.rightIO, // "lift" IO to IOEither context
IOEither.chain(links => array.traverse(ioEither)(links, attrIOEither('data-test')))
);
}
import { Monoid } from 'fp-ts/lib/Monoid';
type Result = { errors: Error[], values: string[] };
const resultMonoid: Monoid<Result> = {
empty: {
errors: [],
values: []
},
concat(a, b) {
return {
errors: [].concat(a.errors, b.errors),
values: [].concat(a.values, b.values)
};
}
};
const attrIO = (...args) => element: IO<Result> => {
const value = element.attr(...args);
if (value) {
return {
errors: [],
values: [value]
};
} else {
return {
errors: [new Error('not found')],
values: []
};
};
const getTests = (text: string): IO<Result> => {
const $ = cheerio.load(text);
return pipe(
$('table tr'),
getIO,
IO.chain(rows => array.traverse(io)(rows, flow($, findIO('a')))),
IO.chain(links => array.traverse(io)(links, attrIO('data-test'))),
IO.map(results => array.foldMap(resultMonoid)(results, x => x))
);
}