如何迭代具有相同类型的记录类型中的所有元素(记录类型的纯函数迭代器)

How to iterate over all elements in a record type with the same type (purely functional iterators for record types)

有没有一种很好的方法来迭代、折叠或循环记录中具有相同类型的所有元素?例如,在下面的 OCaml 代码中

type foo = {
    a : int;
    b : float;
    c : int;
    d : float
}

let foo = {
    a = 1;
    b = 2.0;
    c = 3;
    d = 4.0
}

我想分别遍历所有整数或浮点数。我知道 Fieldslib,但它似乎没有做我想做的事。例如,使用 Fieldslib,我可以编写代码:

open Fieldslib
type bar = {
    w : int;
    x : float;
    y : int;
    z : float
} with fields

let bar = {
    w = 1;
    x = 2.0;
    y = 3;
    z = 4.0
}

let print_int bar x = Printf.printf "%d\n" (Fieldslib.Field.get x bar)

let print_float bar x = Printf.printf "%f\n" (Fieldslib.Field.get x bar);;

Fields_of_bar.iter ~w:(print_int bar) 
                   ~x:(print_float bar)
                   ~y:(print_int bar)
                   ~z:(print_float bar)

但这迫使我们遍历所有元素,而不仅仅是单独的整数或浮点数。它还要求分别指定要在每个元素上调用的函数。真的,我只想说对结构进行迭代、映射或折叠某些函数。

如果异构记录类型很难做到这一点,那么所有元素都具有单一类型的记录类型会更容易吗?在这种情况下,我们可以定义一个镜头来投射出所有的整数、浮点数等。

从某种意义上说,感觉我想要某种与记录类型一起工作的纯函数迭代器,如果存在这种技术,我不确定它叫什么。


编辑 1

实际上,这可能比我想象的要简单得多。 Fieldslib 已经给出了第一个 class 个元素。只要我们列出所有具有相同类型的列表,我们就可以了。换句话说:

open Fieldslib
type bar = {
    w : int;
    x : float;
    y : int;
    z : float
} with fields

let ints=[w;y]
let floats=[x;z]

let bar = {
    w = 1;
    x = 2.0;
    y = 3;
    z = 4.0
};;

let print_ints bar = List.iter (fun l -> Printf.printf "%d\n" (l bar)) ints
let print_floats bar = List.iter (fun l -> Printf.printf "%f\n" (l bar)) floats

更一般地说,具有相同类型的镜头列表应该使所有这一切成为可能,因为我们可以只应用列表中的 map、fold 和 iter 函数。


编辑 2

如果有人想要 运行 @j-abrahamson 的代码,这里有一些轻微的修改,以便它 运行 在我的机器上

{-# LANGUAGE RankNTypes #-}

import Control.Applicative
import Control.Monad.Identity

data Foo = Foo {
    a :: Int,
    b :: Float,
    c :: Int,
    d :: Float
} deriving Show

type Traversal s a = forall f . Applicative f => (a -> f a) -> (s -> f s)

intsOfFoo :: Traversal Foo Int
intsOfFoo inj foo = build <$> inj (a foo) <*> inj (c foo) where
  build a c = foo { a = a, c = c }

mapOf :: Traversal s a -> (a -> a) -> (s -> s)
mapOf trav f = runIdentity . trav (Identity . f)

foo0 = Foo { a = 1, b = 1, c = 1, d = 1 }

foo1 = mapOf intsOfFoo (+1) foo0

我还添加了 Haskell 标志,以防对其他人有所帮助。

实际上,这可能比我想象的要简单得多。 Fieldslib 已经给出了第一个 class 个元素。只要我们列出所有具有相同类型的列表,我们就可以了。换句话说:

open Fieldslib
type bar = {
    w : int;
    x : float;
    y : int;
    z : float
} with fields

let ints=[w;y]
let floats=[x;z]

let bar = {
    w = 1;
    x = 2.0;
    y = 3;
    z = 4.0
};;

let print_ints bar = List.iter (fun l -> Printf.printf "%d\n" (l bar)) ints
let print_floats bar = List.iter (fun l -> Printf.printf "%f\n" (l bar)) floats

更一般地说,具有相同类型的镜头列表应该使所有这一切成为可能,因为我们可以只应用列表中的映射、折叠和迭代函数。

作为半不相关的旁注,在 Haskell 中,这可以由 van Laarhoven Traversal

完成
data Foo = Foo {
    a :: Int,
    b :: Float,
    c :: Int,
    d :: Float
}

type Traversal s a = forall f . Applicative f => (a -> f a) -> (s -> f s)

intsOfFoo :: Traversal Foo Int
intsOfFoo inj foo = build <$> inj (a foo) <*> inj (b foo) where
  build a b = foo { a = a, b = b }

这个值intsOfFoo一般遍历Fooinj函数接触每个int并将这些"touches"的所有效果粉碎在一起重新-build 结果 Foo.

mapOf :: Traversal s a -> (a -> a) -> (s -> s)
mapOf trav f = getIdentity . trav (Identity . f)

> let foo0 = Foo { a = 1, b = 1, c = 1, d = 1 }
> mapOf intsOfFoo (+1) foo0
Foo { a = 2, b = 1.0, c = 2, d = 1.0 }

不确定如何在 Fieldslib 中完成此操作,但这是 OCaml 中的丑陋草稿。

module type Applicative = sig
  type 'a t
  val map : ('a -> 'b) -> ('a t -> 'b t)
  val pure : 'a -> 'a t
  val ap : ('a -> 'b) t -> ('a t -> 'b t)
end

module type Traversal = functor (F : Applicative) ->
sig
  type s
  type a
  val it : (a -> a F.t) -> (s -> s F.t)
end

module Identity = struct
  type 'a t = 'a
  let map f = f
  let pure x = x
  let ap f = f
end

module Traversals (T : Traversal) = struct
  module Ti = T(Identity)
  include Ti
  let mapOf : (a -> a) -> (s -> s) = fun f s -> it f s
end

type foo = {
  a : int;
  b : float;
  c : int;
  d : float;
}

module TraverseFooInts (F : Applicative) = struct
  let (<$>) = F.map
  let (<*>) = F.ap

  type s = foo
  type a = int

  let it inj foo =
    let build a c = {foo with a = a; c = c} in
    build <$> inj foo.a <*> inj foo.c
end

module Z = Traversals(TraverseFooInts)

最后,

# Z.mapOf;;
- : (int -> int) -> foo -> foo = <fun>
# Z.mapOf (fun x -> x + 1) { a = 1; b = 1.; c = 1; d = 1. };;
- : foo = {a = 2; b = 1.; c = 2; d = 1.}