【问题标题】:Dealing With Assertions in Functional Code处理功能代码中的断言
【发布时间】:2022-12-10 12:19:44
【问题描述】:

在进行函数式编程时,我经常遇到这样的情况:我知道一些语言的类型系统不知道的东西。考虑以下解析 UUID 并向用户显示嵌入字段的 TypeScript 示例。该程序首先使用 io-ts 验证它的输入,以确保输入遵循 UUID 规范。后来,在拆分输入后,程序无法验证拆分后的 UUID 是否包含五个部分,这给我留下了fp-tsOption。它从getOrElse 中抛出一个assert false 来摆脱Option。函数式编程是否有一些更惯用的方法来处理断言?向最终用户报告错误并没有帮助,因为这种情况是程序员基本假设的错误,而不是最终用户可以解决的问题。

#!/usr/bin/env ts-node

import { append, intersperse, map, prepend } from 'fp-ts/lib/Array';
import { isRight } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/function';
import { IO } from 'fp-ts/lib/IO';
import { fromPredicate, getOrElse } from 'fp-ts/lib/Option';
import { empty } from 'fp-ts/lib/string';
import * as t from 'io-ts';

type Tuple5<A, B, C, D, E> = [A, B, C, D, E];
const length = 5;
const fromArray = fromPredicate(
  <A>(as: Array<A>): as is Tuple5<A, A, A, A, A> => as.length === length,
);
const Tuple5_ = {
  length,
  fromArray,
};

const separator = '-';

const hex = (n: number): string => `[A-Fa-f0-9]{${n}}`;
const fields: Tuple5<number, number, number, number, number> = [8, 4, 4, 4, 12];
const regexp = pipe(
  fields,
  map(hex),
  intersperse(separator),
  prepend('^'),
  append('$'),
).join(empty);

export type Uuid = t.Branded<string, UuidBrand>;
export type UuidC = t.BrandC<t.StringC, UuidBrand>;
export const Uuid: UuidC = t.brand(
  t.string,
  (x): x is t.Branded<string, UuidBrand> => x.match(RegExp(regexp)) !== null,
  'Uuid',
);
export type UuidBrand = {
  readonly Uuid: unique symbol;
};

export type TimeLow = string;
export type TimeMid = string;
export type TimeHiAndVersion = string;
export type ClockSeq = string;
export type Node = string;

export type Groups = Tuple5<TimeLow, TimeMid, TimeHiAndVersion, ClockSeq, Node>;

export const groups = (uuid: Uuid): Groups =>
  pipe(
    uuid.split(separator),
    Tuple5_.fromArray,
    getOrElse((): Groups => {
      // eslint-disable-next-line
      throw new Error('Assert false! Uuid invalid despite validation.');
    }),
  );

const main: IO<void> = () => {
  const [_node, _script, input] = process.argv;
  const result = Uuid.decode(input);
  if (isRight(result)) {
    const uuid: Uuid = result.right;
    const [timeLow, timeMid, timeHiAndVersion, clockSeq, node] = groups(uuid);
    console.log({ timeLow, timeMid, timeHiAndVersion, clockSeq, node });
  } else {
    console.error('Invalid input!');
  }
};

main();

【问题讨论】:

  • “拆分输入后,程序无法验证拆分后的 UUID 是否包含五个部分”这是为什么?我不会试图去理解 TypeScript 的那堵墙(一种我只了解基本知识的语言),但是基于那句话,五元组不能完成这项工作吗?
  • 您可能会发现 Alexis King 的 Parse, don't validate 很有启发性。
  • 您可以编写自己的函数来拆分 UUID,该 UUID 具有您需要的内置假设。类似于 (uuid: Uuid) =&gt; Tuple5。如果出现问题,您可能会在函数中抛出错误,但您甚至不需要这样做,因为 Uuid 类型基本上可以保证您拥有正确的格式。虽然它会要求你使用我想象的类型断言

标签: typescript functional-programming assert fp-ts


【解决方案1】:

解析,不验证。

type UuidPart1 = string & { readonly UuidPart1: unique symbol }
type UuidPart2 = string & { readonly UuidPart2: unique symbol }
type UuidPart3 = string & { readonly UuidPart3: unique symbol }
type UuidPart4 = string & { readonly UuidPart4: unique symbol }
type UuidPart5 = string & { readonly UuidPart5: unique symbol }
type SplitUuid = [UuidPart1, UuidPart2, UuidPart3, UuidPart4, UuidPart5]

declare const parseUuid: (a: Uuid) => Option<SplitUuid>

declare const recombineUuid: (a: SplitUuid) => Uuid

前一个函数应该将Uuid 分成5 部分,然后确保这5 部分中的每一部分都符合Uuid 的5 部分的格式。如果它们都这样做,那么您将返回一个 Some 包装 SplitUuid 类型(这是一个 5 元组)。如果不是,则返回 None。

现在您编写任何需要拆分 Uuid 的代码,以采用 SplitUuid 而不是 Uuid。

如果不需要拆分,您可以无损地转换回 Uuid 并让函数采用 Uuid 参数。

现在你不需要验证。只需编写采用正确类型的代码,而无需进行任何运行时验证。

如果你确实有一些东西可以接受 Uuid 或 SplitUuid,那么你需要一个类型保护:

type AnyUuid = Uuid | SplitUuid
function isSplitUuid(a: AnyUuid): a is SplitUuid {
  return typeof a === 'object'
}

declare const logSplitUuid = (a: SplitUuid) => console.log('this is split!', a)

const example: (a: AnyUuid) => void = a => pipe(
  O.fromPredicate(isSplitUuid),
  O.getOrElse(() => parseUuid(a)),
  logSplitUuid
)

例如,该函数将采用拆分或未拆分的 Uuid,如果尚未拆分,则将其拆分,然后记录拆分。完全类型安全。

【讨论】:

    猜你喜欢
    • 2014-06-26
    • 1970-01-01
    • 1970-01-01
    • 2010-12-06
    • 1970-01-01
    • 1970-01-01
    • 2015-10-29
    • 2013-12-06
    • 1970-01-01
    相关资源
    最近更新 更多