1

我有这种记录类型。

   type cell = { alive : bool ; column : int ; row : int }
   ;;

现在我创建了一个这样的单元

    #require "containers";;
   let makegrid = CCList.init 2 ( fun i -> (CCList.init 2 (fun j -> { alive = true; column = j;row = i })) );;

我根据网格中的单元格数量使用lablgtk绘制了一个方形网格。

    let drawgrid area (backing:GDraw.pixmap ref) grid =
    let rec loop1 limit m y =
     match m with
   | m when m < limit ->
  (let rec loop x n =
    match n with
    | n when n < limit ->
      let x = x + 20 in
      let width, height = 20,20 in
      displayrectangle area backing x y width height;
      (*Printf.printf "%3d %3d\n" x y;*)
      loop x   (n + 1)
    | n when n >= limit -> loop1  (List.length grid) (m + 1) (y + 20)
  in loop 0  0)
 (* when m >= limit *)  
 | m when m >= limit ->  ()
in loop1 (List.length grid) 0 0

;;

所以最终的代码是这样的。

 let makegameoflifegrid = CCList.init 7 ( fun i -> (CCList.init 7 (fun j -> { alive = false; column = j;row = i })) ) in
 let drawing_area = GMisc.drawing_area ~width:200 ~height:200 ~packing:aspect_frame#add () in
 drawing_area#event#connect#expose ~callback:(expose drawing_area backing);
 drawing_area#event#connect#configure ~callback:(configure window backing);
 drawing_area#event#add [`EXPOSURE];
 window#show ();
 drawgrid drawing_area backing makegameoflifegrid;
 GMain.Main.main ()
 ;;
 let _ = main ()
 ;;

我想知道如何将单元格类型与其具有 x,y 坐标的 GUI 表示相关联。这基本上是一个生命游戏,如果我必须根据单元格是否活着来制作一个单元格,那么我必须处理两种不同的表示 - 单元格中的存活属性和 GUI 中的 x,y 坐标.

有这个功能性的解决方案吗?该代码实际上有效(除了这个问题)并且此时没有固有问题,我知道基本的 OCaml。

更新 :

可以像这样将 x 和 y 坐标放入记录本身。

let drawgridrepresentation area (backing:GDraw.pixmap ref) grid =
let rec loop1 limit m y g1=
match m with
| m when m < limit ->
  (let rec loop x n g=
    match n with
    | n when n < limit ->
      let x = x + 20 in
      let width, height = 20,20 in
      displayrectangle area backing x y width height;
      (*Printf.printf "%3d %3d\n" x y;*)
      let gridmapi = 
      List.mapi (fun i el -> List.mapi ( fun i el1 ->
          if (n = el1.column && m = el1.row)
          then 
            ({ el1  with row = x; column = y}
             ) else el1) el ) g in

      loop x   (n + 1) gridmapi
    | n when n >= limit -> loop1  (List.length grid) (m + 1) (y + 20) g
  in loop 0  0 g1)
  (* when m >= limit *)  
  | m when m >= limit ->  g1
  in loop1 (List.length grid) 0 0 grid
  ;;

但我想我错过了一些东西。

4

1 回答 1

3

函数式编程有利于在数学对象上应用变换。我想说这是函数式思维的主要组成部分——函数式程序员根据转换进行推理,而 OOP 程序员根据对象进行推理。

功能推理的强大部分在于它与数学之间的紧密联系,特别是与作为数学基础的范畴论逻辑之间的紧密联系。

变换是数学对象之间的关系。数学对象本身是抽象的、纯粹的和不可变的。因此,每当函数式程序员(或数学家——同样如此)考虑转换时,他实际上会考虑两种抽象(一个在箭头的左侧,另一个在箭头的右侧)。

如果我们将数学思维应用于您的问题,那么我们可以将我们的问题表达为一组抽象。首先,我们需要谈谈坐标抽象。我们只关心游戏中的邻域关系,所以我会为坐标结构提出以下签名:

module type Coord = sig
  type t
  val fold_neighbors : t -> ('a -> t -> 'b) -> 'a -> 'b
end

这只是表达这种抽象的一种可能方式,例如这是另一种方式:

module type Coord' = sig
  type t
  val neighbors : t -> t list (* bad - we are encoding the list representation *)
end

但让我们坚持Coord签名。顺便说一句,请注意 OCaml 的说法如何与数学相匹配。我们有用于数学结构的 OCaml 结构和用于数学签名的 OCaml签名。

下一个抽象是我们的世界。基本上,它只是一个坐标的集合,我们也将使用该fold函数表示(尽管我们可以选择'a list或任何其他容器,但我不希望硬编码任何特定的数据结构)。

module type World = sig
  type t
  type coord

  val fold : t -> ('a -> coord -> 'b) -> 'a -> 'b
end

现在我们拥有了实现游戏所需的一切。从数学的角度来看,游戏只是一组规则,用以下签名描述:

module type Game = sig
  type world
  type coord
  val state : world -> coord -> [`Live | `Dead | `Empty]
  val step  : world -> world
end

规则的实现将是以下类型的函子:

module type Rules = functor
  (Coord : Coord)
  (World : World with type coord = Coord.t) ->
  Game with type world = World.t
        and type coord = Coord.t

有了这些抽象,我们已经可以开始玩游戏了,例如,选择不同的起始世界,看看World.step函数是否到达了一个固定点(即单元世界w,并且step w具有相同的状态),它需要多长时间才能到达一个固定点, ETC。

如果我们想要可视化,那么我们需要投入更多的抽象。由于我们现在不打算处理 3d 设备,例如 3d 打印机和全息监视器,我们将坚持使用 2d 可视化。对于我们的可视化,我们需要一个画布抽象,例如:

module type Canvas = sig
  type t

  val rectangle : t ->
    ?color:int ->
    ?style:[`solid | `raised] ->
    width:int -> height:int -> int -> int -> unit

  val width : t -> int
  val height : t -> int

  val redraw : t -> unit
end

我们还需要处理从抽象坐标到 Canvas 所在的笛卡尔坐标的坐标转换:

module type Cartesian = sig
  type t
  type coord
  type dom
  val x : t -> coord -> dom
  val y : t -> coord -> dom
end

最后,使用这些抽象我们可以实现一个动画游戏:

module Animation2d
    (World : World)
    (Game : Game with type world = World.t and type coord = World.coord)
    (Canvas : Canvas)
    (Coord : Cartesian with type coord = Game.coord and type dom = int) =
struct

  let black = 0x000000
  let white = 0xFFFFFF
  let red   = 0xFF0000

  let color_of_state = function
    | `Live -> red
    | `Dead -> black
    | `Empty -> white

  let run ?(width=10) ?(height=10) world canvas proj =
    let draw game =
      World.fold game (fun () coord ->
          let color = color_of_state (Game.state world coord) in
          let x = Coord.x proj coord in
          let y = Coord.y proj coord in
          Canvas.rectangle canvas ~color ~width ~height x y) () in
    let rec play world =
      draw world;
      Canvas.redraw canvas;
      play world in
    play world
end

如您所见,使用正确选择的抽象,您甚至不会遇到您所描述的问题(即同时存在同一抽象的两个表示)。因此,解决问题的一种实用方法不是创建它:)

参考书目

有两本基本教科书,分别教授函数式编程和函数式推理。他们不使用 OCaml,但使用 Scheme,尽管这些并没有降低它们的价值,因为 Scheme 是一个没有任何语法糖的纯抽象,这将帮助您理解本质,而不会因语法问题而模糊您的思想:

于 2017-01-30T19:28:40.047 回答