LINUX.ORG.RU

[Lisp] Hello-world

 


0

2

Осваиваю Lisp. В качестве первого упражнения решаю следующую задачу. Имеется текстовый файл с часами разработчика в формате

<дата> <модификатор деятельности> <количество часов> <описание>

(Файл может содержать комментарии, начинающие с знака «#».) Необходимо подсчитать общее количество часов работы.

Например, для файла

20.05.2011 RD 2 Чтение глав «Функции» и «Параметры» из PCL
20.05.2011 OT 1 Установка SBCL
20.05.2011 CO 2 Кодирование и отладка функции count-hours

Результат должен быть 5.

Вот мое решение:

;;;; Program reads file with working hours of developer                                                                                        
;;;; and outputs sum of hours.

;; Counts working hours from specified filename.                                                                                               
(defun count-hours (filename)                                                                                                                
  (let ((in (open filename :if-does-not-exist nil)) (hours 0))                                                                                 
       (when in                                                                                                                                
             (loop for line = (read-line in NIL) while line do          
                   ; Doesn't process line with comments and empty line                                                                       
                   (if (not (or (= (length line) 0) (char= (elt line 0) #\#)))             
                       (setf hours (+ hours (parse-integer (get-word line 3))))))                                                            
             (close in)) hours))                                                                                                               
                                                                                                                                               
;; Returns nth word in string. Words are separated by Space and Tab                                                        
(defun get-word (str num)                                                                                                                    
  (let* ((white-spaces (list #\Space #\Tab)) (pos (get-white-space-min-pos white-spaces str)))                                                 
       (if (not pos) "0"                                                                                                                       
           (if (= (1- num) 0) (subseq str 0 pos)                                                                                               
               (get-word (string-trim white-spaces (subseq str pos)) (1- num))))))
                                                                                                                                               
                                                                                                                                               
;; Returns minimum position in str of character from list of characters (white-spaces).
;; If str doesn't have characters from list then nil is returned.                                                                              
(defun get-white-space-min-pos (white-spaces str)                                                                                              
  (let ((min-pos NIL))                                                                                                                         
       (dolist (white-space white-spaces)                                                                                                      
               (let ((pos (position white-space str)))                                                                                         
                    (if (not min-pos) (setf min-pos pos))                                                                                      
                    (if (and pos min-pos) (setf min-pos (min min-pos pos)))))                                                                  
       min-pos))                                                                                                                               

Запускать можно так:

(сount-hours «wh-dimv.txt»)

Собственно вопросы:
1. Не кажется ли вам, что здесь все написано в императивном стиле, просто с использованием скобочек? Если да, то направьте на путь истинный.
2. Есть ли в коде места, которые лучше было бы реализовать с помощью макросов? Я таких мест сейчас вижу, скорее всего, потому что слишком мало знаком с lisp. Или задача слишком маленькая, чтобы понадобились макросы?


1. кто сказал, что на лиспе нельзя писать императивно? 2. Зачем пихать макросы туда, где они не нужны?

anonymous
()
Ответ на: комментарий от anonymous

Нет ощущения легкости и свободы, как обещается адептами Lisp.[br]

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

dimv
() автор топика
Ответ на: комментарий от dimv

> Нет ощущения легкости и свободы, как обещается адептами Lisp.

Уверен, что правильно распознал «адептов»? ;) Кроме того, на всё нужно время, а новый язык, особенно лисп, естественно будет в начале идти тяжеловато.

Не кажется ли вам, что здесь все написано в императивном стиле,

просто с использованием скобочек?



Если говорить о Common Lisp, то использование функционального стиля носит в нём лишь эпизодический характер.

Есть ли в коде места, которые лучше было бы реализовать

с помощью макросов?



Макросы это последнее средство для борьбы с дублированием кода. Если дублирования нет или с ним можно справиться другими средствами, то макросы не нужны.

archimag ★★★
()

1. Ты пишешь не системный софт, не ограничивай себя.

2. У тебя очень примитивная задача (вернее формат файла данных), для нее достаточно awk, и лучше него для таких табличек нет DSL.

Если в cl есть реализация awk, то используй её. (впрочем как оказывается можно и наоборот, лисп в авк :)

cl-awk

Common Lisp package with the features of AWK and more. An implementation of the features of the Unix AWK language, within Common Lisp, using macros. In addition, it provides more complicated processing capabilities than that of AWK.

3. Если формат данных более сложен, то у того же архимага на форуме были примеры генератора произвольного синтаксиса через грамматики в лиспе.

psv1967 ★★★★★
()
Ответ на: комментарий от psv1967

2. У тебя очень примитивная задача (вернее формат файла данных), для нее достаточно awk, и лучше него для таких табличек нет DSL.

Ну это понятно. Просто нужно было что-нибудь в качестве задачи технически чуть серьезнее чем hello-world , а до этого как раз задачу c часами решал (на bash).

3. Если формат данных более сложен, то у того же архимага на форуме были примеры генератора произвольного синтаксиса через грамматики в лиспе.

На это я бы посмотрел.

dimv
() автор топика

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

o
()

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

Вот пример на clojure, sbcl под рукой нет:

(require '[clojure.string :as str])
(require '[clojure.java.io :as io])

(defn read-data [filename]
  (filter #(not (or (= (.length %) 0) (= (first %) \#)))
          (line-seq (io/reader filename))))

(defn extract-time [strings]
  (map #(Integer/parseInt (nth (str/split % #"\s+") 2)) strings))

(defn sum [lst]
  (reduce + 0 lst))

(sum (extract-time (read-data "/home/ott/tmp/t3.txt"))) ; возвращает 5

тут определено три функции - одна читает файл и выделяет только нужные строки (пустые и начинающиеся с # отбрасываются), вторая - выделяет время, а третья - выполняет суммирование... Как видно - программа не императивная (тут она даже ленивая). На лиспе тоже так можно написать...

Я бы посоветовал посмотреть на книжку How to design programs - там есть советы по проектированию и организации программ

ott ★★★★★
()
Ответ на: комментарий от dimv

3. Если формат данных более сложен, то у того же архимага на форуме были примеры генератора произвольного синтаксиса через грамматики в лиспе.

На это я бы посмотрел.

Вообще-то я не помню, что писал такое на форуме, но, например, парсер cl-closure-template (описание самого языка здесь) можно посмотреть тут.

archimag ★★★
()

Старайтесь писать и мыслить функционально. Нельзя начинающему лисперу разрешать писать императивно. Будет опыт - будете чувствовать и выбирать как писать. Бывает действительно поюзать какой-то loop нагляднее, чем вертеть рекурсию, что правда бывает крайне редко.

observer ★★★
()
Ответ на: комментарий от archimag

Вообще-то я не помню, что писал такое на форуме

Может он тебя с Love5an перепутал?

o
()

Кусок конфига для stumpwm, который делает ровно то, что ты хочешь. Код непричесанный, убогий и не всегда работает. http://pastebin.com/qC2dTqgV

anonymous
()
Ответ на: комментарий от o

> Работа с файлом и сложение чисел в цикле.

лиспа не знаю, но…

perl -MList::Util=sum -E 'say sum map /^\S+\s+\S+\s+(\d+)/, grep !/^#/, <>' test.txt

никаких циклов ;)

arsi ★★★★★
()
Ответ на: комментарий от o

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

ott ★★★★★
()
Ответ на: комментарий от dimv

Да, да. Вот что-то такое хочется. Просто, лекго.

(asdf:operate 'asdf:load-op 'iterate)
(asdf:operate 'asdf:load-op 'cl-ppcre)

(defpackage #:test
  (:use #:cl #:iter))

(in-package #:test)

(defun count-hours (file)
  (iter (for line in-file file using #'read-line)
        (unless (char= (char line 0) #\#)
          (sum (parse-integer (third (ppcre:split "\\s" line)))))))

Так устроит?

archimag ★★★
()
Ответ на: комментарий от archimag

мопед, в смысле форум твой, писал не ты :)

psv1967 ★★★★★
()
Ответ на: комментарий от psv1967

Вы затронули тему весьма интересную для меня.

Есть у меня проект один (на Java) с некоторым подъязычком, грамматика которого парсится с помощью Flex+byaccj.

В лисп-образных языках мне обещали возможность безболезненно встраивать свои конструкции в язык. Собственно поэтому я полез в lisp (чтобы понять концепцию), а потом посмотреть на clojure (это чтобы была доступна JVM).

dimv
() автор топика

И что в этом вашем лиспе красивого? :)

import fileinput
from itertools import imap
from itertools import ifilter

def count_hours(file_name):
    return sum(imap(
                    lambda line: int(line.split()[2]),
                    ifilter(
                        lambda line: not (len(line) == 0 and line[0] != '#'),
                        fileinput.input(file_name))))

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

dizza ★★★★★
()
Ответ на: комментарий от dizza

> Но просто как язык он не такой уж и волшебный.

Ну, волшебства, как известно, вообще не существует.

Почти все хорошое из лиспа доступно и в питоне.


Что бы так утверждать, надо знать всё хорошее, что есть в лиспе, а это невозможно без хорошего практического опыта работы с ним )

archimag ★★★
()
Ответ на: комментарий от archimag

Ну я же написал «Почти» :) Но некий общий базис есть, по крайней мере это видно на не сложных примерах. А сложный код я стараюсь вообще не писать.

dizza ★★★★★
()
Ответ на: комментарий от dizza

> Но некий общий базис есть, по крайней мере это видно на не

сложных примерах.


Ну да, Норвинг это и утверждает, что python это мини-лисп. Ключевое слово «мини» ))

archimag ★★★
()
Ответ на: комментарий от archimag

Какую не возьми, а использовать esrap намного удобней.

Возможно, но вот особый случай у человека. Он переносит проект с уже написанных парсером на yacc. [риторический вопрос] Ему то удобнее будет не переписывать все?

psv1967 ★★★★★
()

0. нормальный пример. Чтобы он был ещё и полезным, можно освоить org-mode и делать хронометраж из emacs : http://sachachua.com/blog/2007/12/clocking-time-with-emacs-org/ . Формат немного другой, так что тут будут отличия, суть та же + удобный интерфейс из емакса.

1. ну да, императивно написано. Ну и что, пиши так, чтобы а) код заработал б) был написан понятно в) работал эффективно, в таком порядке. Уже потом, когда у тебя есть какой-то рабочий код, можно думать, что может дать функциональный подход. Например: а. может меняться формат файла, и парсер придётся переделать. б. основной кусок «подсчёт суммы часов» не должен изменяться при переписывании парсера. Отсюда идёт разбиение на примитивы, которые могут работать функционально и которые могут работать императивно. Если довести «функциональность» до конца, то придём к функциональному парсеру, то есть, парсер-комбинаторам или PEG/packrat.

2. Чисто теоретически рассуждая, если бы например, парсер разбирался регекспами, то вот эти регекспы надо когда-то компилировать (в конечные автоматы). И это можно делать макросами во время компиляции, а не во время работы программы. Опять, можно формато-завимисую часть парсера, которая не ложится на функциональщину, выделить в виде DSL.

anonymous
()

Есть ли в коде места, которые лучше было бы реализовать с помощью макросов?

Есть. Весь код следует реализовать с помощью макроса loop примерно вот так:

(require :cl-ppcre)
(defvar *f* "/home/ugoday/project/plan.data")

(defun extract (line pos)
  (let ((time (elt (cl-ppcre:split "\ " line) pos)))
    (or (parse-integer time :junk-allowed t) 0)))

(defun count-hours (file)
  (with-open-file (in file)
    (loop for line = (read-line in nil)
       while line
       sum (extract line 2))))

(count-hours *f*)

И это будет наиболее коммон-лисповое решение.

P.S. Коммон-лисп --- лучший в мире язык императивного программирования.

ugoday ★★★★★
()
Ответ на: комментарий от archimag

Ну да, Норвинг это и утверждает,

«Python supports all of Lisp's essential features except macros» (c) Peter Norvig

Ого, мои мысли сошлись с мыслями Питера, прям +100500 к ЧСВ ))

Вообще первый раз о родстве лиспов и питонов я задумался, когда увидел вот это: SRFI-49

dizza ★★★★★
()
Ответ на: комментарий от dizza

> Вообще первый раз о родстве лиспов и питонов я задумался, когда увидел вот это: SRFI-49

вот это:

The requested URL /srfi-49/srfi-49.htm was not found on this server.

? =)

ты l недозацепил

korvin_ ★★★★★
()
Ответ на: комментарий от dimv

> Нет ощущения легкости и свободы, как обещается адептами Lisp.

Адепты лгут. // К.О.

Алсо, поздравляю новообращённого зелота первого уровня. Через некоторое время прикрутишь к своей поделке CL-GTK и возвысишься до уровня RusNekromant'а.

anonymous
()
Ответ на: комментарий от archimag

> cl-yacc жутко не удобен и смотрится уже слишком монстрообразно.

не удобен и смотрится уже слишком монстрообразно.

не удобен


монстрообразно



В устах лиспера это имеет какой-то особенный ернический оттенок.

anonymous
()
Ответ на: комментарий от dimv

> Есть у меня проект один (на Java) с некоторым подъязычком, грамматика которого парсится с помощью Flex+byaccj.

В лисп-образных языках мне обещали возможность безболезненно встраивать свои конструкции в язык.


Адепты снова солгали. Относительно безболезненно («всего-навсего» написав кучку макр на каждый узел AST, ага) можно получить только «малый DSL» — это будет тот же лисп, с теми же обрезками ногтей, только с твоим дополнительным набором макросов. Адепты гордо называют такое «новым языком».

Чтобы реализовать полноценный DSL внутри лиспа, тебе потребуется ничуть не меньше телодвижений, чем при использовании ANTLR, Flex, Bison и подобных инструментов. А геморроя огребёшь больше. Добро пожаловать в реальность, Нео.

anonymous
()
Ответ на: комментарий от anonymous

И да, это Лавсан — он наркоман, не слушай его.

anonymous
()
Ответ на: комментарий от ugoday

> Коммон-лисп --- лучший в мире язык императивного программирования.

Коммон-лисп --- лучший в мире язык

Коммон-лисп --- лучший в мире



Харе Кришна,
Харе Кришна,
Кришна, Кришна,
Харе, Харе,

Харе Рама,
Харе Рама,
Рама, Рама,
Харе, Харе!

anonymous
()
Ответ на: комментарий от anonymous

Там случилась история неуспеха.
При отсутствии понимания, что должно было в конце получиться (читай спецификации и проектирования), писался некий код, смотрелось что получилось, затем приходила какая-то «гениальная» идея, которая приклеивалась на сопли встраивалась в существующий код.

Результат получился очевидным. 4500 тысячи строк (+ 2500 генерируется) малоподдерживаемого, глючного, спагетти-образного говна кода, в котором через некоторое время (я туда уже не заглядывал месяц и не тянет) навряд ли разберешься.

Поэтому ищутся варианты (не конкретно для этого проекта, там уже стало более-менее понятно, что требуется, а вообще), при которых можно запилить что-нибудь по быстрому и смотреть что получилось. Говорят, что один из вариантов - это лисп.

dimv
() автор топика

> Есть ли в коде места, которые лучше было бы реализовать с помощью макросов? Я таких мест сейчас вижу, скорее всего, потому что слишком мало знаком с lisp. Или задача слишком маленькая, чтобы понадобились макросы?

Вольно цитируя onlisp - чтобы написать Правильный макрос нужно сначала написать что он дожен сделать, а только потом реализацию. Поскольку у тебя только реализация, то и применения макросов за тоннами ненужного кода нее видишь. Базовый принцип лиспа в том чтобы писать только необходимые вещи в удобной именно для тебя форме, а всю рутину засунуть в реализацию. Некоторые псевдоэлитарии будут с пеной у рта доказывать, что это DSL, но скромные лисперы-практики знают - фетишизация DSL удел убогих негибких языков. Дань ЛОР-у, однако.

Далее по делу

antares0 ★★★★
()
Ответ на: комментарий от antares0

Для простоты забудем пока про комментарии.

Смотрим на строку и видим список из полей разного типа с раделителями, вот их и запишим.

(defvar delimiters '(:\Space #/Tab))

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

antares0 ★★★★
()
Ответ на: комментарий от antares0

Формат строки жестко задан в виде

<дата> <модификатор деятельности> <количество часов> <описание>

но мы то знаем ничто не вечно и как минимум количество, состав и порядок полей может запросто быть изменен указом свыше или по велению сердца, поэтому програмист просвещенный мудростью лиспа быстренько набросает робота подстилающий сомку в нужном месте. А поскольку идеальная программа должна состоять из одной команды |сделать зашибись <праметры необходимого зашибись>|, то так и напишем.

(time-task-parser '(дата |модификатор деятельности| |количество часов| описание)
	;;;Код использующий распарсеные значения
	)
antares0 ★★★★
()
Ответ на: комментарий от antares0

Еще одна опечатка :(

(time-task-parser (дата |модификатор деятельности| |количество часов| описание)
   ;;;Код использующий распарсеные значения
   )
antares0 ★★★★
()
Ответ на: комментарий от antares0

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

(define-field-parser дата
  ...)
...
(define-field-parser описание
  ...)

Как именно будет реализован парсер при данном подходе, не принципиально. Можно собирать парсер из строки из маленьких парсеров полей, а можно скормить список спецификаций полей esrap-у дабы не плодить велосипедизм. Так или иначе распарсеные значения биндим поименно согласно порядку полей из списка вокруг кода. А тот кто этот макрос будет использовать сам уже будет решать что ему этим кодом делать в БД складывать или почту отправлять.

Таким образом получаем код с минимальным дублированием и максимальной адаптацией для повтороного использования.

antares0 ★★★★
()
Ответ на: комментарий от Neksys

> awk

функциональщиной и не пахнет ;)

arsi ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.