Поиск рекурсивного типа записи OCaml

Я пытаюсь рекурсивно искать значение поля в записи, которая является рекурсивным типом записи.

Мой тип записи

type node = {node_name:string; node_branch:node list}

Во-первых, я попытался просто обойти древовидную переменную этого типа:

let tree = {node_name="trunk"; node_branch=[{node_name="branch1L:1"; node_branch=[{node_name="branch2L:1"; node_branch=[]};
                                                                                  {node_name="foo_node"; node_branch=[]};];};
                                            {node_name="branch1L:2"; node_branch=[]}];}
    in
    let target = "foo_node" in
    print_newline();
    search_tree_for_name target tree;

Идея в том, что это примерно так:

                    trunk
            __________|_________
           |                    |
       branch1L:1           branch1L:2
    ______|_______
   |              |
branch2:1       foo_node

Итак, я ищу запись со значением поля node_name = foo_node. То, как я прошел его, просто чтобы доказать свою логику, было:

let rec search_tree_for_name target_name in_tree =
  if in_tree.node_name = target_name then
    (* First check the node_name *)
      Format.printf "[%s] Target node_name found\n" in_tree.node_name
  else
    (* Then evaluate the branches *)
    begin
      match in_tree.node_branch with
      | [] ->
         Format.printf "[%s] Branches: 0; End of branch\n" in_tree.node_name
      | head :: tail ->
         Format.printf "[%s] Branches: %i\n" in_tree.node_name (List.length in_tree.node_branch);
         search_tree_for_name target_name head;
         List.iter (search_tree_for_name target_name ) tail;
    end

Это распечатывает:

[trunk] Branches: 2
[branch1L:1] Branches: 2
[branch2L:1] Branches: 0; End of branch
[foo_node] Target node_name found
[branch1L:2] Branches: 0; End of branch

Теперь я действительно хочу просто посмотреть, существует ли экземпляр, где node_name - это то, что я ищу, поэтому я просто хочу вернуть логическое значение.

Я пробовал (я создал новую функцию только для целей тестирования):

let rec check_tree_for_name target_name in_tree =
  if in_tree.node_name = target_name then
    begin
    (* First check the node_name *)
      Format.printf "[%s] Target node_name found\n" in_tree.node_name;
      true
    end 
  else
    (* Then evaluate the branches *)
    begin
      match in_tree.node_branch with
      | [] ->
         Format.printf "[%s] Branches: 0; End of branch\n" in_tree.node_name;
         false
      | head :: tail ->
         Format.printf "[%s] Branches: %i\n" in_tree.node_name (List.length in_tree.node_branch);
         check_tree_for_name target_name head;
         List.iter (check_tree_for_name target_name ) tail;
    end

Я получаю следующую ошибку в своей функции, которую я передаю List.iter:

Error: This expression has type node -> bool
       but an expression was expected of type node -> unit
       Type bool is not compatible with type unit 

Затем я изменил последний блок сопоставления с образцом на

  | head :: tail ->
     Format.printf "[%s] Branches: %i\n" in_tree.node_name (List.length in_tree.node_branch);
     if check_tree_for_name target_name head then true
     else List.iter (check_tree_for_name target_name ) tail;

Так что условные операторы if проверяют возврат вызова функции и как бы передают его. Теперь у меня на одну ошибку меньше, но у меня все та же ошибка.

Насколько я понимаю, List.iter будет передавать каждый элемент переданного списка в качестве последнего аргумента функции, которая передается List.iter. Таким образом, это должно перебирать элементы моего списка, которые я ему передаю, и единственный выход - если поле node_name соответствует тому, что я ищу, или если это пустой список.

Основное различие, которое я вижу между двумя моими реализациями, заключается в том, что первая продолжается до тех пор, пока не будут оценены все узлы, а вторая реализация продолжается до тех пор, пока я не найду то, что ищу.

Любые предложения будут оценены, и, если возможно, краткое объяснение того, где я ошибаюсь, или, по крайней мере, выделение того, где я ошибаюсь (я чувствую, что улавливаю много концепций функционального программирования, но есть еще несколько, которые ускользают от меня ).


person Jesse    schedule 13.04.2017    source источник
comment
Ответ Lhooq правильный, но я также хочу указать, что функция, которую вы ищете, на самом деле является однострочной: let rec check name tree = tree.name = name || List.exists (check name) tree.branches (имена сокращены, чтобы соответствовать комментарию)   -  person Andreas Rossberg    schedule 14.04.2017
comment
@AndreasRossberg Спасибо за это, приятно видеть более идиоматические способы ведения дел.   -  person Jesse    schedule 15.04.2017


Ответы (1)


Ну, в последней ветке, если я правильно понимаю, вы хотите знать, есть ли у одного из узлов в tail хорошее имя. В этом случае просто используйте List.exists :

| head :: tail ->
         Format.printf "[%s] Branches: %i\n" in_tree.node_name 
            (List.length in_tree.node_branch);
         check_tree_for_name target_name head ||
         List.exists (check_tree_for_name target_name ) tail

И это будет работать так, как задумано (обратите внимание на || между check_tree... и List.exists .... Если check_tree ... возвращает true, вам не нужно проверять остальную часть списка.


Небольшое пояснение:

В OCaml все ветви сопоставления с образцом должны иметь один и тот же тип. Здесь ваша первая ветвь имеет тип boolean, потому что вы закончили ее на false, а вторая ветвь имеет тип unit, потому что вы закончили ее на List.iter .... List.iter имеет тип ('a -> unit) -> 'a list -> unit и применяет свой первый аргумент ко всем элементам в списке, указанном в качестве второго аргумента. List.exists имеет тип ('a -> bool) -> 'a list -> bool и возвращает true, если один элемент в списке, указанный в качестве второго аргумента, удовлетворяет предикату, указанному в качестве первого аргумента, и false, если такого элемента не существует.

Эквивалентно, if cond then e1 else e2 похоже на сопоставление с образцом, поэтому e1 и e2 должны иметь один и тот же тип, чтобы ваша вторая попытка была такой же неправильной, как и первая. ;-)

person Lhooq    schedule 13.04.2017
comment
Кроме того, стоит отметить, что List.exists — это оператор быстрого доступа: он вернется, как только встретится допустимый элемент. Если хотите посмотреть сами: List.exists (fun x -> print_int x; x = 3) [1;2;3;4;5];; - person Richard-Degenne; 14.04.2017
comment
Спасибо за это, для меня это было очевидно, но стоит упомянуть об этом. ;-) - person Lhooq; 14.04.2017
comment
Да, это было для ОП и других для чтения в качестве примечания. :) - person Richard-Degenne; 14.04.2017
comment
Lhooq Спасибо за объяснение! В этом есть смысл. Я думал, что поток программы как бы войдет в функцию, вызываемую List.iter, а затем выйдет. Я не думал, что он вернется. Но теперь я также вижу в документации List, что он возвращает unit. И спасибо @RichouHunter за разъяснение, это помогает! - person Jesse; 15.04.2017