将 OCaml Record 与其 GUI 表示相链接
Linking OCaml Record with its GUI representation
我有这个记录类型。
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 ()
;;
我想知道如何将 cell 类型与其具有 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
;;
但我想我错过了什么。
函数式编程偏爱transformations on mathematical objects的应用。我会说这是函数式思维的主要组成部分——函数式程序员根据转换进行推理,而 OOP 程序员根据对象进行推理。
函数推理的强大之处在于它与数学之间的紧密联系,尤其是 Category Theory and Logic,这是数学的基础。
变换是数学对象之间的关系。数学对象本身是 abstract、纯粹且不可变的。因此,每当函数式程序员(或数学家 - 相同)考虑转换时,他实际上会考虑两个抽象(一个在箭头左侧,另一个在箭头右侧)。
如果我们将数学思维应用于您的问题,那么我们可以将我们的问题表达为一组抽象。首先,我们需要谈谈坐标抽象。我们只关心我们游戏中的邻域关系,所以我建议坐标结构的签名如下:
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 的说法如何与数学相匹配。我们有 mathematical structures and OCaml signatures for mathematical signatures 的 OCaml 结构。
下一个抽象是我们的世界。基本上,它只是我们还将使用 fold
函数表示的坐标集合(尽管我们可以选择 'a list
或任何其他容器,但我不希望 hard-code 任何特定数据结构体)。
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
具有相同的状态),到达固定点需要多长时间等
如果我们想要可视化,那么我们需要投入更多的抽象。由于我们现在不打算处理 3d 设备,如 3d 打印机和全息显示器,我们将坚持 2d 可视化。对于我们的可视化,我们需要一个 canvas 抽象,例如:
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 是一个没有任何语法糖的纯抽象,这将帮助您理解本质,而不会因语法问题而模糊您的思路:
我有这个记录类型。
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 ()
;;
我想知道如何将 cell 类型与其具有 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
;;
但我想我错过了什么。
函数式编程偏爱transformations on mathematical objects的应用。我会说这是函数式思维的主要组成部分——函数式程序员根据转换进行推理,而 OOP 程序员根据对象进行推理。
函数推理的强大之处在于它与数学之间的紧密联系,尤其是 Category Theory and Logic,这是数学的基础。
变换是数学对象之间的关系。数学对象本身是 abstract、纯粹且不可变的。因此,每当函数式程序员(或数学家 - 相同)考虑转换时,他实际上会考虑两个抽象(一个在箭头左侧,另一个在箭头右侧)。
如果我们将数学思维应用于您的问题,那么我们可以将我们的问题表达为一组抽象。首先,我们需要谈谈坐标抽象。我们只关心我们游戏中的邻域关系,所以我建议坐标结构的签名如下:
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 的说法如何与数学相匹配。我们有 mathematical structures and OCaml signatures for mathematical signatures 的 OCaml 结构。
下一个抽象是我们的世界。基本上,它只是我们还将使用 fold
函数表示的坐标集合(尽管我们可以选择 'a list
或任何其他容器,但我不希望 hard-code 任何特定数据结构体)。
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
具有相同的状态),到达固定点需要多长时间等
如果我们想要可视化,那么我们需要投入更多的抽象。由于我们现在不打算处理 3d 设备,如 3d 打印机和全息显示器,我们将坚持 2d 可视化。对于我们的可视化,我们需要一个 canvas 抽象,例如:
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 是一个没有任何语法糖的纯抽象,这将帮助您理解本质,而不会因语法问题而模糊您的思路: