OCaml объединить ошибки сортировки? - PullRequest
2 голосов
/ 26 августа 2011

В настоящее время я изучаю OCaml, и это доставляет мне некоторые проблемы.Я пытаюсь реализовать функцию merge_sort, но она продолжает выдавать ошибку в 5-й строке данного кода.Я просто совершенно сбит с толку относительно того, почему это дает мне ошибку, если кто-то может просветить меня, я буду очень признателен.Я даже не совсем уверен, правильно ли я настраиваю сопоставление с шаблоном (операторы сопоставления), поэтому, если бы вы могли взглянуть, это было бы очень полезно.

let rec merge_sorted (l1:int list) (l2:int list) : int list =
let end_list = [] in
begin match l1, l2 with 
    | [], [] -> end_list
    | h1::t1, h2::t2 -> if h1 < h2 then merge_sorted t1 t2 (h1 :: end_list) else merge_sorted t1 t2 h2::end_list, h1::end_list
end

Ошибка, которую яполучение:

Ошибка: эта функция применяется к слишком многим аргументам, возможно, вы забыли `; 'merge_sorted: int list -> int list -> int list

В части, где написано "если h1

Мне также было интересноесть ли места для изучения синтаксиса OCaml?Я пытался использовать книгу Джейсона Хикки, но есть вещи, в которых она не очень углубляется (например, это сопоставление с несколькими / параллельными образцами).Я только в основном программировал на Java, поэтому кодирование в OCaml стало для меня немного разочаровывающим опытом.

Ответы [ 2 ]

6 голосов
/ 26 августа 2011

Вы объявили merge_sorted как функцию типа int list -> int list -> int list или функцию, которая принимает два списка int в качестве параметров и возвращает другой список int.

Проблема, с которой вы сталкиваетесь, заключается в том, что вы вызываете merge_sorted с тремя int list аргументами:

h1::t1, h2::t2 -> if h1 < h2 then merge_sorted t1 t2 (h1 :: end_list) else merge_sorted t1 t2 h2::end_list, h1::end_list

в частности, merge_sorted t1 t2 (h1 :: end_list) . Вы не предусмотрели список результатов в определении своей функции.

Вы захотите изменить определение своей функции, возможно, так:

let rec merge_sorted l1 l2 results =
  (* code code code *)

Относительно вашего второго вопроса, приличный набор учебников можно найти здесь .

EDIT

В ответ на ваш комментарий-

Во-первых, оператор :: служит двум целям. Во-первых, он создает новый список с новой головой и существующим хвостом - 1 :: [2] приводит к [1; 2]. Во-вторых, он разбирает существующие списки во время сопоставления с образцом - match [1; 2] with x :: xs свяжет x с 1 и y с [2].

С учетом этого, в вашем подходе есть несколько вещей, которые отлично работают в Java, но не будут работать вообще в OCaml. Во-первых, вы повторно объявляете end_list как пустой список при каждом рекурсивном вызове вашей функции.

Во-вторых, поскольку у вас конкатенация списка в той же строке, что и рекурсивный вызов merge_sorted, компилятор будет (не имеет иного выбора) думать, что вы задаете третий вызов функции. Что вы, вероятно, имели в виду:

h1::t1, h2::t2 -> if h1 < h2 then
                      merge_sorted t1 t2
                      h1 :: end_list
                  else
                      merge_sorted t1 t2
                      h2 :: end_list
                      h1 :: end_list

НО! Это не ваше решение, читайте дальше.

В-третьих, и, что гораздо важнее, OCaml не работает таким образом. По умолчанию списки OCaml (и все переменные, на самом деле) являются неизменяемыми, то есть, если вы привязали значение к одному, вы не сможете его изменить. Когда вы говорите (h1 :: end_list), вы не меняете end-list; то, что вы на самом деле делаете, - это создание нового списка, в котором h1 является его головой, а end_list - его хвостом. Поскольку все это в одной строке, компилятор считает, что вы делаете эквивалент merge_sorted(t1, t2, new List(h1, end_list)) в Java-земле, где вы определили merge_sorted(List t1, List t2).

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

let rec map f data acc =
    match data with
    | []      -> List.rev acc
    | x :: xs -> map f xs ((f x) :: acc);;

map (fun x -> x + 1) [1; 2; 3;] [];;  (* yields [2; 3; 4] *)

Это каноническая функция map, которая принимает список и функцию, применяет каждый элемент в списке к этой функции и возвращает новый список, содержащий результаты. Как вы можете видеть, результаты применения каждого элемента от data до f сохраняются в acc и , результат передается следующему вызову , который должен быть возвращен, когда data пустой. Таким образом, мы можем выполнить серию модификаций списка без необходимости изменять переменную.

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

let map f data =
    let rec loop d acc =
        match d with
        | []      -> List.rev acc
        | x :: xs -> loop xs ((f x) :: acc)
    in
        loop data []

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

3 голосов
/ 26 августа 2011

Ответ Бена хороший, но я надеюсь, вы не возражаете против нескольких дополнительных комментариев. Во-первых, ваше сопоставление с образцом не является исчерпывающим. Он не охватывает случай, когда один из списков пуст, а другой нет. Во-вторых, если вы только изучаете OCaml, я бы начал с написания слияния в нерекурсивном стиле. То есть я не передал бы накопленный результат в качестве параметра. В итоге вы получите что-то вроде этого:

let rec merge_sorted l1 l2 =
    match l1, l2 with
    | (* Either one is empty *) -> (* answer is pretty obvious *)
    | h1::t1, h2::t2 ->
          if h1 < h2 then h1 :: (* Merge the rest *)
          else h2 :: (* Merge the rest *)

(Надеюсь, это не даст слишком много и не доставит удовольствия).

(Добавлено)

В блоке кода: "then h1 :: (* Объединить остальные *)", позволяет ли это :: вызывать другие функции?

Что ж, если вы скажете (xxx) :: (yyy), вы можете иметь практически любые вызовы функций, которые вам нужны в частях xxx и yyy (если они имеют смысл для типов). В этом и заключается суть функционального программирования: все можно делать с помощью вызовов функций. Хотите верьте, хотите нет, но после того, как вы к этому привыкнете, трудно вернуться к программированию другими способами.

Я думал, что это оператор для добавления в начало списка?

Да, вот что (: :). Вы хотите вернуть список, составленный из элементов двух ваших входных списков, поэтому вы можете рассчитывать на создание списка с помощью оператора (: :). Возможно, стоит отметить, что (: :) это просто функция.

Кроме того, с точки зрения сопоставления нескольких списков, когда между одним из совпадений с образцом есть запятая (например, «h1 :: t1, h2 :: t2 ->», это означает «оба все еще остались элементы ", или" либо остались элементы. "

Это означает, что у обоих еще остались предметы.

Кроме того, почему h1 :: t1 представляет непустой список? Это просто особый случай? Я думал, что :: должен был добавить в начало списка?

Как объясняет Бен, (: :) используется как оператор и как компонент шаблона. Когда вы используете его в шаблоне, это означает, что у вас есть список с головой и хвостом. Хвост может быть пустым, но голова должна быть чем-то. Таким образом, шаблон h1::t1 соответствует только списку длиной не менее 1. h1 соответствует заголовку списка (int в вашем примере), а t1 соответствует хвосту списка (другой список на 1 элемент короче).

Это не на самом деле особый случай. Когда вы определяете свои собственные типы в OCaml, вы получаете те же две вещи с одним и тем же именем: конструктор (используется для создания значений типа) и деструктор (используется для сделать сопоставление с образцом). Однако использование оператора (: :) для конструктора списка и деструктора немного особенное. Списки обычно получают немного особой обработки в OCaml. Но кроме специального синтаксиса, это просто обычный тип.

...