Связывание OCaml Record с его графическим представлением

У меня есть этот тип записи.

   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. Это в основном игра в жизнь, и если мне нужно сделать ячейку твердой, основываясь на том, жива ли ячейка или нет, тогда мне придется иметь дело с двумя различными представлениями - живым атрибутом в ячейке и координатами 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
  ;;

Но я думаю, что что-то упустил.

1 ответ

Решение

Функциональное программирование способствует применению преобразований на математических объектах. Я бы сказал, что это основная составляющая функционального мышления - функциональный программист рассуждает в терминах преобразований, а программист ООП - в терминах объектов.

Сильная часть функциональных рассуждений заключается в тесной связи между ним и математикой, в частности с теорией категорий и логикой, которые являются основами математики.

Преобразование - это отношение между математическими объектами. Математические объекты сами по себе абстрактны, чисты и неизменны. Таким образом, всякий раз, когда функциональный программист (или математик - то же самое) думает о преобразовании, он на самом деле думает о двух абстракциях (одна слева от стрелки, а другая справа).

Если мы применим математическое мышление к вашей проблеме, то мы можем выразить нашу проблему в виде набора абстракций. Прежде всего, нам нужно поговорить о координатной абстракции. Мы заботимся только об отношении соседства в нашей игре, поэтому я бы предложил следующую подпись для структуры координат:

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 имеют одинаковые состояния), сколько времени потребуется, чтобы достичь точки фиксации и т. д.

Если мы хотим визуализировать, то нам нужно добавить больше абстракций. Поскольку сейчас мы не собираемся работать с 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 - это чистая абстракция без какого-либо синтаксического сахара, которая поможет вам понять суть, не затуманивая синтаксические проблемы:

Другие вопросы по тегам