反转链式地图?

Inverting chained maps?

函数式编程中一个非常常见的模式是将一系列对 map 的调用链接到列表中。一个人为的简单示例:

[1; 2; 3] |> List.map (fun x -> x + 1) |> List.map (fun x -> x * 2)

结果是:

[4; 6; 8]

现在很容易将其翻转并避免在每个阶段创建列表,如果类型永远不变:只需 map 取一个按顺序应用的函数列表:

let list_map_chained : 'a. ('a -> 'a) list -> 'a list -> 'a list =
  let rec apply x chain =
    begin match chain with
    | []          -> x
    | f :: chain' -> apply (x |> f) chain'
    end
  in
  fun chain xs ->
    List.map (fun x -> apply x chain) xs

我们可以这样使用:

[1; 2; 3] |> list_map_chained [ (fun x -> x + 1) ; (fun x -> 2 * x) ]

但是如果我们想对如下序列做同样的事情:

[1; 2; 3] |> List.map (fun x -> x + 1) 
          |> List.map (fun x -> x * 2)
          |> List.map (fun x -> float_of_int x /. 3.)

现在类型确实发生了变化,但是由于类型的异构性质,函数不能存储在期望(并且需要)同质类型的列表之类的任何东西中。显然,这在 Ruby:

这样类型不那么严格的编程语言中非常简单
class Array
  def inverted_map(*lambdas)
    self.map { |x| lambdas.inject(x) { |sum, l| l.call(sum) } }
  end
end

irb(main):032:0> [1,2,3].inverted_map(lambda { |x| x + 1 }, lambda { |x| x * 2 }, lambda { |x| x.to_f / 3})
=> [1.3333333333333333, 2.0, 2.6666666666666665]

我对 Ocaml 了解相当多,但我对不了解这一切持开放态度。有没有一种方法可以在 Ocaml 的类型系统中实现这一目标而不是比它的价值更麻烦?

通过组合函数

与其实际存储连续应用的函数序列,不如直接组合它们?

(* (0) ORIGINAL CODE: *)

let () =
  [1; 2; 3]
  |> List.map (fun x -> x + 1)
  |> List.map (fun x -> float x /. 3.)
  |> List.map string_of_float
  |> List.iter print_endline

(* (1) BY COMPOSING FUNCTIONS: *)

let (%) f g x = x |> f |> g

let () =
  [1; 2; 3]
  |> List.map (
      (fun x -> x + 1) %
      (fun x -> float x /. 3.) %
      string_of_float
    )
  |> List.iter print_endline

使用 GADT 存储异构的(链接的)函数列表

现在我认为没有任何理由这样做,但是如果您实际上想要您在问题中所说的内容,则可以使用 GADT 来实现。我认为这就是@glennsl 在提到“difflists”时在评论中提出的建议。

在下面的代码中,我们为组合类型为a -> c的函数链定义了一个新的归纳类型(a, c) fun_chain;换句话说,对于异构函数列表[f0; f1; …; fn],其类型如下:

f0 :     a -> b1
f1 :          b1 -> b2
…                      …
f{n-1} :                 b{n-1} -> bn
fn :                               bn -> c

由于 b1, …, bn 没有出现在最终类型中,它们是存在量化的,'b(::) 构造函数的类型中也是如此。

(* (2) WITH A GADT: *)

(* Syntax tip: we overload the list notation [x1 ; … ; xn],
 * but wrap our notation in a module to help disambiguation. *)
module Chain = struct

  type (_, _) fun_chain =
    |  []  : ('a, 'a) fun_chain
    | (::) : ('a -> 'b) * ('b, 'c) fun_chain -> ('a, 'c) fun_chain

  (* [reduce] reduces a chain to just one function by composing all
   * functions of the chain. *)
  let rec reduce : type a c. (a, c) fun_chain -> a -> c =
    fun chain ->
      begin match chain with
      | []          -> fun x -> x
      | f :: chain' -> f % reduce chain'
      end

  (* [map] is most easily implemented by pre-reducing the chain first,
   * but then it is just a more convoluted way of pre-composing functions,
   * as in the first suggestion. If you want a slightly different behavior,
   * you would reimplement [map] with a nested loop (once for iterating
   * on the list, the other for applying each function in turn). Not sure
   * whether that would improve performance, though. *)
  let map : type a c. (a, c) fun_chain -> a list -> c list =
    fun chain ->
      List.map (reduce chain)

end

let () =
  [1; 2; 3]
  |> Chain.map [
      (fun x -> x + 1) ;
      (fun x -> 2 * x) ;
      (fun x -> float x /. 3.) ;
      string_of_float
    ]
  |> List.iter print_endline

关于多态类型注释的注释

在上面的代码中,您可能会对 reducemap 定义中的类型注释感兴趣(对于 map 它实际上并不需要,但我喜欢确保我们有预期的类型)。

简而言之,: type a. … is a type annotation with explicit (forced) polymorphism. You can think of this as the universal quantifier: ∀ a, … or as a binder for a type-level argument. In the code above we make use of advanced features of the type system, namely, polymorphic recursion and branches with different types,这让类型推断无所适从。为了在这种情况下进行程序类型检查,我们需要像这样强制多态性。

有关更详细的解释,请参阅