Re: [OCaml] Попинайте код новичка
От: Max Mouratov  
Дата: 11.09.11 04:08
Оценка: 1 (1)
Здравствуйте, demi, Вы писали:

D>Привет, я изучаю OCaml и ФП в частности. Вот функция которая идет по тексту и выкидывает комментарии вида /* ... */. Простенькая задачка Может как-то покрасивше написать можно? Что хорошо получилось, что — плохо? Есть ощущение что многа букаф) В частности, принимаются замечания по coding style, но с обоснованиями.


Начинаем пинать.

Во-первых, твоя программа не работает -- спотыкается на простейших случаях:

Незакрытый комментарий:
# removeComments "/*abc";;
Exception: Invalid_argument "String.sub".

Вложенные комментарии:
# removeComments "aaa/*bbb/*ccc*/ddd*/eee";;
- : string = "aaaddd*/eee"


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

  1. Простой функциональный вариант:
    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) Это уродский говнокод, тоже очень низкоуровневый. Такой подход плохо масштабируется: пришлось бы много думать при необходимости немного усложнить программу (добавить поддержку ещё одного вида комментариев? поддержку докстрингов?).

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

Успехов!
ocaml camlp4 peg
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.