如何使用不变性和函数式编程实践构造具有多种可能长度的数组?

How to construct an array with multiple possible lengths using immutability and functional programming practices?

我们正在将我们的命令式大脑转变为以功能为主的范式。这个功能给我带来了麻烦。我想构造一个包含两对或三对的数组,具体取决于条件(refreshToken 是否为 null)。我怎样才能使用 FP 范式干净地做到这一点?当然,对于命令式代码和突变,我只是有条件地 .push() 将额外的值放在看起来很干净的末尾。

这是 "local mutation is ok" FP 警告的示例吗?

(我们在 TypeScript 中使用 ReadonlyArray 来强制执行不可变性,这使得它更难看。)

const itemsToSet = [
    [JWT_KEY, jwt],
    [JWT_EXPIRES_KEY, tokenExpireDate.toString()],
    [REFRESH_TOKEN_KEY, refreshToken /*could be null*/]]
    .filter(item => item[1] != null) as ReadonlyArray<ReadonlyArray<string>>;

AsyncStorage.multiSet(itemsToSet.map(roArray => [...roArray]));

在 Haskell 中,我只创建一个包含三个元素的数组,然后根据条件按原样传递它或只传递两个元素的一部分。由于懒惰,除非实际需要,否则不会在第三个元素上花费任何计算工作。在 TypeScript 中,您可能会得到计算第三个元素的成本,即使不需要它,但这也许无关紧要。

或者,如果您不需要将结构作为实际数组(对于 String 元素,性能可能不是那么关键,并且 O (n) 如果长度限制为三个元素,直接访问成本不是问题),我会使用 单链表 反而。创建包含两个元素的列表,并根据条件添加第三个元素。这 不需要 任何变化:3 元素列表仅包含未更改的 2 元素列表作为子结构。

OP 中给出的 itemsToSet 有什么问题?它看起来很实用,但可能是因为我缺乏 TypeScript 知识。

在Haskell中没有null,但是如果我们对第二个元素使用Maybe,我认为itemsToSet可能是翻译成这样:

itemsToSet :: [(String, String)]
itemsToSet = foldr folder [] values
  where
    values = [
      (jwt_key, jwt),
      (jwt_expires_key, tokenExpireDate),
      (refresh_token_key, refreshToken)]
    folder (key, Just value) acc = (key, value) : acc
    folder _ acc = acc

这里jwttokenExpireDaterefreshToken都是Maybe String.

类型

itemsToSetvalues 上执行 右折叠 ,将 Maye String 元素与 Just 进行模式匹配,并且(隐式) Nothing。如果它是一个 Just 值,它会将 (key, value) 对连接到累加器 acc。如果不是,folder 只是 returns acc.

foldr 从右到左遍历 values 列表,在访问每个元素时构建累加器。初始累加器值是空列表 [].

在函数式编程中不需要 'local mutation'。通常,您可以通过 using recursion and introducing an accumulator value.

从 'local mutation' 重构为适当的功能样式

虽然 foldr 是内置函数,但您可以使用递归自行实现它。

根据描述,我不认为数组是最好的解决方案,因为您提前知道它们包含 2 个值或 3 个值,具体取决于某些条件.因此,我将问题建模如下:

type alias Pair = (String, String)

type TokenState 
  = WithoutRefresh (Pair, Pair)
  | WithRefresh (Pair, Pair, Pair)

itemsToTokenState: String -> Date -> Maybe String -> TokenState
itemsToTokenState jwtKey jwtExpiry maybeRefreshToken =
  case maybeRefreshToken of
    Some refreshToken ->
      WithRefresh (("JWT_KEY", jwtKey), ("JWT_EXPIRES_KEY", toString jwtExpiry), ("REFRESH_TOKEN_KEY", refreshToken))

    None ->
      WithoutRefresh (("JWT_KEY", jwtKey), ("JWT_EXPIRES_KEY", toString jwtExpiry))

通过这种方式,您可以更有效地利用类型系统,并且可以通过做一些比返回元组更符合人体工程学的事情来进一步改进。