Как распечатать списки с разными типами в Ocaml? - PullRequest
0 голосов
/ 14 октября 2019

Я работаю со списками в Ocaml, поэтому я написал функцию, которая печатает содержимое списка. Вот мой код:

let leastB = [false; true; true; true; true; false; false; false; true]
let leastI = [-16; 4; 7; 3444; -100]

let prListInt l =
    Printf.printf "[";
let rec prListIntrec l =    
    match l with
    [] -> Printf.printf "]\n"
    | g :: []-> Printf.printf "%d]\n" g
    | g :: t -> Printf.printf "%d; " g; prListIntrec t
in
    prListIntrec l


let prListB l =
    Printf.printf "[";  
let rec prListBrec l =  
    match l with
    [] -> Printf.printf "]\n"
    | g :: []-> Printf.printf "%B]\n" g
    | g :: t -> Printf.printf "%B; " g; prListBrec t
in
    prListBrec l


let () =
    prListB leastB;
    prListInt leastI

Он работает нормально, но мне нужно иметь новую функцию для каждого типа данных или есть способ объединить эти функции?

Есть ли какие-либо подсказки дляулучшения в коде (это идиоматический Ocaml?)?

Ответы [ 2 ]

0 голосов
/ 14 октября 2019

Если вы сравните две функции, вы можете увидеть, что они почти идентичны, за исключением %d против %B.

Теперь одним из способов их объединения будет передача строки формата в качестве аргумента,Но с магией черного ящика, которая скрывается за форматными строками, это не так просто. Это также не общее решение, так как не все значения могут быть напечатаны с помощью простой строки формата.

Поэтому, чтобы сделать это еще более общим, вы можете передать функцию, которая печатает один элемент списка и объединяет все остальныелогика добавления "[", ";" и "]" и итерации по списку.

В вашем случае создание функций для печати одного элемента тривиально, потому что ocaml использует каррирование. Вы просто используете (Printf.printf "%d") и (Printf.printf "%B"). В других случаях более сложные функции могут быть переданы для печати более сложных элементов списка.

PS: Не указывать источник, потому что это может быть домашнее задание. Информация, приведенная здесь, должна помочь вам начать работу в правильном направлении.

0 голосов
/ 14 октября 2019

Идиоматический способ распечатать список вещей в OCaml - использовать стандартный модуль Format, который, кстати, предоставляет функцию pp_print_list, которая принимает принтер элементов, принтер-разделитель и списоки печатает список указанных элементов, например,

let pp_comma ppf () = Format.fprintf ppf ",@ "

let pp_int_list ppf ints = 
  Format.fprintf ppf "[%a]" 
    Format.(pp_print_list ~pp_sep:pp_comma pp_print_int) ints

# Format.printf "@[hello = [%a]@]@\n" pp_int_list [1;2;3];;
hello = [1, 2, 3]

Функция pp_print_list имеет тип

?pp_sep:(formatter -> unit -> unit) ->
(formatter -> 'a -> unit) -> 
(formatter -> 'a list -> unit)

Т.е. это функция, которая принимает две функции (одна из которыхнеобязательно) и возвращает функцию, которая печатает список. Если мы опускаем параметр pp_sep для краткости, то мы можем сказать, что pp_print_list берет принтер 'a и возвращает принтер 'a list, где принтер является функцией типа formatter -> 'a -> unit. Функция pp_print_list - это так называемая функция высшего порядка, поскольку она принимает в качестве параметра другую функцию. В функциональном программировании в целом и в OCaml в частности функции высшего порядка очень распространены, поэтому никто не делает из этого большого дела.

Поскольку в OCaml нет никаких средств для самоанализа, а информация о типе стираетсяво время компиляции в модуле нет общих средств для красивой печати, кроме модуля Format. Поэтому для каждого вновь определенного типа мы должны предоставить принтер, т. Е. Функцию типа formatter -> t -> unit, где t - наш новый тип. Это идиома, общая для многих статически скомпилированных языков, ср. с классом Show Haskell или реализацией функции << в C ++. В OCaml нет классов типов или перегрузки, поэтому нет официального или канонического имени для функции принтера, но существует соглашение называть такую ​​функцию pp, cf Int.pp и Float.pp и т. Д. В ядре Janestreet. библиотека.

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

let rec prGen prItem l =  
  match l with
  [] -> Printf.printf "]\n"
  | g :: []-> Printf.printf "%s]\n" (prItem g) g
  | g :: t -> Printf.printf "%s; " (prItem g); prGen prItem t

Эта функция уже будет более общей,хотя немного не идиоматично. Поскольку перевод в строку, а затем печать не очень эффективны (зачем создавать промежуточный объект, если мы можем напечатать его напрямую), мы используем принтеры и спецификаторы %a (описание соответствующих принтеров см. В соответствующих модулях Printf и Format),Наконец, мы не используем camelCase в OCaml (несмотря на то, что OCaml - это OCaml).

...