Привет, я изучаю OCaml и ФП в частности. Вот функция которая идет по тексту и выкидывает комментарии вида /* ... */. Простенькая задачка Может как-то покрасивше написать можно? Что хорошо получилось, что — плохо? Есть ощущение что многа букаф) В частности, принимаются замечания по coding style, но с обоснованиями.
let substr s from n =
let slen = String.length s in
let last = from + n in
let nmin = (min slen last) - from
in String.sub s from nmin;;
let strrange s i j = substr s i (j - i);;
let findComments src =
let slen = String.length src in
let rec loop i =
if i < slen then
match substr src i 2 with
| "/*" -> let rec findEnd j = match substr src j 2 with
| "*/" -> (i, j + 2) :: loop (j + 2)
| _ -> findEnd (j + 1) in
findEnd (i + 2)
| _ -> loop (i + 1)
else
[]
in loop 0;;
let removeCommentsImpl src lst =
let slen = String.length src in
let rec aux i =
function
| [] -> strrange src i slen
| (a, b) :: tail -> (strrange src i a) ^ aux b tail
in aux 0 lst;;
let removeComments src = removeCommentsImpl src (findComments src);;
Спасибо.
Не стыдно попасть в дерьмо, стыдно в нём остаться!
Здравствуйте, demi, Вы писали:
D>Привет, я изучаю OCaml и ФП в частности. Вот функция которая идет по тексту и выкидывает комментарии вида /* ... */. Простенькая задачка Может как-то покрасивше написать можно?
У меня менее страшно получилось на ближайшем брате окамла:
(* parse - ф-ция, разбирающая строку и выбрасывающая комментарии *)let rec parse result = function
| '/' :: '*' :: tail -> parse_comment result tail
| c :: tail -> parse (c :: result) tail
| [] -> List.rev result
and parse_comment result = function
| '*' :: '/' :: tail -> parse result tail
| _ :: tail -> parse_comment result tail
| [] -> failwith "Некорректная строка"
[<EntryPoint>]
let main args =
(* Строка, которую надо разобрать *)let strToParse = "Привет /*Это комментарий*/ а это текст после комментария"(* Переводим string -> list *)let strToParseList = [for c in strToParse -> c]
(* Выбрасываем комментарии *)let resultList = parse [] strToParseList
(* Переворим list -> string *)let resultStr = new System.String (List.toArray resultList)
(* Выводим результат *)
printf "%s" resultStr
0
Здравствуйте, demi, Вы писали:
D>Привет, я изучаю OCaml и ФП в частности. Вот функция которая идет по тексту и выкидывает комментарии вида /* ... */. Простенькая задачка Может как-то покрасивше написать можно? Что хорошо получилось, что — плохо?
1. Много нехвостовой рекурсии — на длинных плохих строках жди проблем.
2. Квадратичная сложность построения результата (из-за конкатенаций). Для таких вещей в стандартной библиотеке есть модуль Buffer.
В итоге неэффективность решения не оправдывает его многословности.
Если хочется красиво, но неэффективно, то можно перевести в список и написать как Булат.
Здравствуйте, demi, Вы писали:
D>Привет, я изучаю OCaml и ФП в частности. Вот функция которая идет по тексту и выкидывает комментарии вида /* ... */. Простенькая задачка Может как-то покрасивше написать можно? Что хорошо получилось, что — плохо? Есть ощущение что многа букаф) В частности, принимаются замечания по coding style, но с обоснованиями.
Начинаем пинать.
Во-первых, твоя программа не работает -- спотыкается на простейших случаях:
Во-вторых, она непонятная -- за деревьями леса не видно. Абстракции очень неэффективные. Похоже на поток мысли задолбавшегося студента. Я бы дал конструктивные предложения к улучшению программы, если бы что-то в ней понял. Предпочёл не тратить время и написал пару своих версий:
Простой функциональный вариант:
exception Syntax_error of string
let unclosed_comment () =
raise (Syntax_error "Неожиданное закрытие комментария")
(* Удаление вложенных комментариев - - - - - - - - - - - - - - - - - - - - - - *)let purge_comments str =
let rec skip s i = function
| '*' :: '/' :: rest when i = 0 -> unclosed_comment ()
| '*' :: '/' :: rest -> skip s (i-1) rest
| '/' :: '*' :: rest -> skip s (i+1) rest
| a :: rest when i = 0 -> skip (a :: s) i rest
| a :: rest -> skip s i rest
| [] -> List.rev s in
str |> String.to_list
|> skip [] 0
|> String.of_list
Вложенные комментарии корректно отрабатываются, ошибочные ситуации локализуются. Обрати внимание что [skip] -- хвосторекурсивная функция и стек на больших строках не кончается.
Проблемы этой программы:
1) Производительность может в некоторых случаях оказаться неприемлимой. Можно увеличить скорость работы, если использовать модуль Buffer из стандартной библиотеки, но это будет выглядеть ещё неприятнее.
2) Это уродский говнокод, тоже очень низкоуровневый. Такой подход плохо масштабируется: пришлось бы много думать при необходимости немного усложнить программу (добавить поддержку ещё одного вида комментариев? поддержку докстрингов?).
Декларативный вариант с PEG парсером на camlp4:
(* Удаление вложенных комментариев - - - - - - - - - - - - - - - - - - - - - - *)let purge_comments =
<:peg<
somechar : char <- !"/*" !"*/" [^] as c -> c
comment : unit <- nested / somechar
nested : unit <- "/*" comment* "*/"
unclosed : unit <- "/*" comment*
text : string <-
somechar* as s1 nested somechar* as s2 -> String.of_list (s1 @ s2)
/ somechar* as s1 unclosed -> String.of_list s1
>>
Используется небольшой встроенный язык для описания парсеров, созданный при помощи синтаксического препроцессора. Этот вариант является субъективно куда более читабельным и объективно куда более расширяемым. Производительность можно увеличивать путём ускорения парсера (при этом ускоряются все программы, его использующие, а логика верхнего уровня не теряет в читабельности).
Используется чёрная магия -- camlp4, путь к которому тернист и опасен. Пример приведён для ознакомления с возможностями OCaml и закрепления мотивации для освоения этого весьма неоднозначного, но неплохо подходящего для решения многих практических задач инструмента.
Успехов!
Здравствуйте, BulatZiganshin, Вы писали:
BZ>Здравствуйте, demi, Вы писали:
D>>Привет, я изучаю OCaml и ФП в частности. Вот функция которая идет по тексту и выкидывает комментарии вида /* ... */.
BZ>я ваших окамлов не знаю, но это что=то страшное. должно быть типа
BZ>f ('/':'*':s) = ff s BZ>f ('_':s) = f s BZ>f "" = ""
BZ>ff ('*':'/':s) = f s BZ>ff ('_':s) = ff s BZ>ff "" = ""
Так ведь ваш вариант вложенные комментарии не сьест! А мне надо чтоб вложенные сьедал.
Не стыдно попасть в дерьмо, стыдно в нём остаться!
Здравствуйте, Max Mouratov, Вы писали:
MM>Здравствуйте, demi, Вы писали:
D>>Привет, я изучаю OCaml и ФП в частности. Вот функция которая идет по тексту и выкидывает комментарии вида /* ... */. Простенькая задачка Может как-то покрасивше написать можно? Что хорошо получилось, что — плохо? Есть ощущение что многа букаф) В частности, принимаются замечания по coding style, но с обоснованиями.
MM>Начинаем пинать.
MM>Во-первых, твоя программа не работает -- спотыкается на простейших случаях:
MM>Незакрытый комментарий: MM>
MM>Во-вторых, она непонятная -- за деревьями леса не видно. Абстракции очень неэффективные. Похоже на поток мысли задолбавшегося студента. Я бы дал конструктивные предложения к улучшению программы, если бы что-то в ней понял. Предпочёл не тратить время и написал пару своих версий:
MM> MM> Простой функциональный вариант: MM>
MM>exception Syntax_error of string
MM>let unclosed_comment () =
MM> raise (Syntax_error "Неожиданное закрытие комментария")
MM>(* Удаление вложенных комментариев - - - - - - - - - - - - - - - - - - - - - - *)
MM>let purge_comments str =
MM> let rec skip s i = function
MM> | '*' :: '/' :: rest when i = 0 -> unclosed_comment ()
MM> | '*' :: '/' :: rest -> skip s (i-1) rest
MM> | '/' :: '*' :: rest -> skip s (i+1) rest
MM> | a :: rest when i = 0 -> skip (a :: s) i rest
MM> | a :: rest -> skip s i rest
MM> | [] -> List.rev s in
MM> str |> String.to_list
MM> |> skip [] 0
MM> |> String.of_list
MM>
MM>Вложенные комментарии корректно отрабатываются, ошибочные ситуации локализуются. Обрати внимание что [skip] -- хвосторекурсивная функция и стек на больших строках не кончается.
MM>Проблемы этой программы: MM>1) Производительность может в некоторых случаях оказаться неприемлимой. Можно увеличить скорость работы, если использовать модуль Buffer из стандартной библиотеки, но это будет выглядеть ещё неприятнее. MM>2) Это уродский говнокод, тоже очень низкоуровневый. Такой подход плохо масштабируется: пришлось бы много думать при необходимости немного усложнить программу (добавить поддержку ещё одного вида комментариев? поддержку докстрингов?).
MM> Декларативный вариант с PEG парсером на camlp4: MM>
MM>(* Удаление вложенных комментариев - - - - - - - - - - - - - - - - - - - - - - *)
MM>let purge_comments =
MM> <:peg<
MM> somechar : char <- !"/*" !"*/" [^] as c -> c
MM> comment : unit <- nested / somechar
MM> nested : unit <- "/*" comment* "*/"
MM> unclosed : unit <- "/*" comment*
MM> text : string <-
MM> somechar* as s1 nested somechar* as s2 -> String.of_list (s1 @ s2)
MM> / somechar* as s1 unclosed -> String.of_list s1
>>>
MM>
MM>Используется небольшой встроенный язык для описания парсеров, созданный при помощи синтаксического препроцессора. Этот вариант является субъективно куда более читабельным и объективно куда более расширяемым. Производительность можно увеличивать путём ускорения парсера (при этом ускоряются все программы, его использующие, а логика верхнего уровня не теряет в читабельности).
MM>Используется чёрная магия -- camlp4, путь к которому тернист и опасен. Пример приведён для ознакомления с возможностями OCaml и закрепления мотивации для освоения этого весьма неоднозначного, но неплохо подходящего для решения многих практических задач инструмента.
MM>
MM>Успехов!
Супер, покурю ваш вариантик.
Не стыдно попасть в дерьмо, стыдно в нём остаться!
Здравствуйте, demi, Вы писали:
D>Здравствуйте, Max Mouratov, Вы писали: D>Да, а какой редактор ты используешь и как отлаживаешься?
Использую GNU Emacs [1] + Tuareg [2]. Из альтернатив популярен Vim [3].
Корректность программ проверяю обильными юнит-тестами.
Постоянно использую toplevel, чтобы тестировать куски кода в интерактивном режиме, экспериментировать. Удобно бывает при помощи ocamlbuild [4] собрать toplevel с уже загруженными модулями создаваемой программы.
В крайне редких случаях использую отладчик camldebug [5] (есть интеграция с Emacs). Обычно он бывает полезен при диагностике проблем, возникающих из-за изменяемых состояний. Хотя отладчик у OCaml великолепен (позволяет даже отматывать выполнение назад), при правильном подходе к разработке он практически не нужен.
Наличие проблем, которые сложно диагностировать без отладчика, обычно говорит мне о том, что при проектировании была допущена грубая ошибка.