LINUX.ORG.RU

Необязательные параметры функции в Scheme

 ,


0

1

Есть ли общепринятые метода работы с необязательными параметрами в Scheme? Вот, для ясности, пример функции:

;; string-prefix? s1 s2 [start1 [end1 [start2 [end2]]]] -> boolean
(define (string-prefix? s1 s2 . optional) ...)

Можно для каждой функции делать уникальный разбор списка optional, но это не кошерно. Наверняка есть какой-то общепринятый макрос, который все используют?

Я вот попытался написать:

(define-syntax optional-args
  (syntax-rules ()
    ((_ (args (var0 default0) ...) body ...)
     (let-values (((var0 ...)
                   (if (> (length args) (length '(var0 ...)))
                       (syntax-error "too many args")
                       (apply values
                              (let loop ((a args) (d (list default0 ...)) (acc '()))
                                (if (null? d) (reverse acc)
                                    (if (null? a)
                                        (loop '() (cdr d) (cons (car d) acc))
                                        (loop (cdr a) (cdr d) (cons (car a) acc)))))))))
       body ...))))

Применяется так:

(define (string-prefix? prefix str . opt)
  ...
    (optional-args
     (opt (start1 0) (end1 prefix-length)
          (start2 0) (end2 str-length))
      ...)))

Но мне не нравится, что он очень медленно работает. Такой вариант значительно быстрее:

(define (string-prefix1? prefix str . opt)
  (let ((opt-length (length opt))
        ...)
    (let ((start1   (if (> opt-length 0) (nth opt 0) 0))
          (end1     (if (> opt-length 1) (nth opt 1) prefix-length))
          (start2   (if (> opt-length 2) (nth opt 2) 0))
          (end2     (if (> opt-length 3) (nth opt 3) str-length)))
      ...)))

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

(define-syntax optional
  (syntax-rules ()
    ((_ args ((var0 ...) (default0 ...)) body ...)
     '(let ((len (length args)))
        (let ((var0 default0) ...)
          body ...)))))

Должно быть по идее так:

        (let ((var0 (if (> (length args) X) (nth args X) default0)) ...)

где X - порядковый номер подстановки (arg0 ...), но я не нашел способа определить этот номер. Может быть кто-то подскажет? Или скажет, почему это не нужно?

Делается это всё для R7RS на chibi-scheme.

★★★★★

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

Соглашусь, нужно ветвление

Вот что я написал для варианта как в ракетке

(define-syntax-rule (my-define f-name args ... (optional ...))
    ;;;здеся твой код

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

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

Ятп в r7rs тоже что-то такое должно быть

Нету

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

Вот что я написал для варианта как в ракетке

Не работает для больше одного опционального параметра.

Я пытался так изобразить:

(define* (name arg0 ... (opt0 def0) ...) body ...)

но ругается на большое количество многоточий.

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

Короче пишут, что несколько ellipsis можно только в syntax-parse

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

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

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

Получается сделать только вот так:

(define** (f a b (x y) (0 1)) ###)

не очень красиво.

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

В r7rs large есть. Без syntax-case нерешаемо в принципе.

anonymous ()

Тебе часом не case-lambda нужна? В r7rs вроде это обязательным стало, раньше в SRFI только была.

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

Тебе часом не case-lambda нужна?

Можно попробовать и на case-lambda, но сути это не меняет. Но попробую.

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

Ты паттерн-матчинг хочешь что ли?

Нет, я хочу красиво и быстро переменное количество аргументов. А паттерн-матчинг в syntax-rules и так есть.

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

Функция string-prefix? с ручным распределением опциональных параметров (через let) в два раза быстрее функции на макросе (там через let-values):

> (time-repeat 100000 (string-prefix? "1234567890" "1234567890_"))
0.914436817169189
> (time-repeat 100000 (string-prefix1? "1234567890" "1234567890_"))
0.490775108337402

> (time-repeat 100000 (string-prefix? "1234567890qwerty" "1234567890qwerty_"))
1.07127904891968
> (time-repeat 100000 (string-prefix1? "1234567890qwerty" "1234567890qwerty_"))
0.645779132843018

Мне в принципе скорость не важна, но, как говорится, за державу обидно!

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

Я немного не понял твой критерий «быстро и красиво». Общего же пути все равно нет, на то они и необязательные параметры.

buddhist ★★★★★ ()

Можно так:

(define-syntax define**
  (syntax-rules ()
    ((_ (name arg ... (argn valn)) (optargs ...) body ...)
     (define** (name arg ...) ((argn valn) optargs ...) body ...))
    ((_ (name arg ...) ((optarg optval) ...) body ...)
     (define (name arg ... . optional)
       (let ((optarg optval) ...)
         (unless (null? optional)
           (set! optarg (car optional))
           (set! optional (cdr optional))) ...
         body ...)))))
           
(define-syntax define*
  (syntax-rules ()
    ((_ (name arg ...) body ...)
     (define** (name arg ...) () body ...))))

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

Ты хочешь произвольные деревья опциональных параметров?

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

Супер! Спасибо! Это то что нужно.

Надо наращивать скилл в рекурсивных макросах :)

Ну ладно с рекурсией у меня тяжело, но такое вроде бы очевидное решение

(unless (null? optional)
           (set! optarg (car optional))
           (set! optional (cdr optional))) ...

мог бы придумать :(

Красиво!

Puzan ★★★★★ ()
Последнее исправление: Puzan (всего исправлений: 1)
Ответ на: комментарий от monk
(let ((optarg optval) ...)
         (unless (null? optional)
           (set! optarg (car optional))
           (set! optional (cdr optional))) ...
         body ...)

лучше так:

(let* (optarg (if (null? optional) optval
                  (begin0 (car optional) 
                          (set! optional (cdr optional))))) ...
          body ...)

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

там нету возможности сгенерить последовательность 1 2 3

Зато можно сгенерить последовательность (+), (+ 1), (+ 1 1), (+ 1 1 1) длиной с какой-нибудь список.

И вообще, более Scheme-way было бы раскрытие

(define* (string-prefix? s1 s2 (start1 0) (end1 -1) (start2 0) (end2 -1))
  'procedure-body )
во что-то такое:
(define string-prefix?
  (let ((code (lambda (s1 s2 start1 end1 start2 end2)
                'procedure-body )))
    (case-lambda
      ((s1 s2)                         (code s1 s2 0      -1   0      -1))
      ((s1 s2 start1)                  (code s1 s2 start1 -1   0      -1))
      ((s1 s2 start1 end1)             (code s1 s2 start1 end1 0      -1))
      ((s1 s2 start1 end1 start2)      (code s1 s2 start1 end1 start2 -1))
      ((s1 s2 start1 end1 start2 end2) (code s1 s2 start1 end1 start2 end2)) ) ) )
Вполне можно сделать на syntax-rules, оставаясь в рамках переносимого R7RS. Ведь тут нет создания новых идентификаторов, которые должны быть видимы вне макроса — по сути единственной вещи, которая приниципиально невыразима с их помощью.

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

Зато можно сгенерить последовательность (+), (+ 1), (+ 1 1), (+ 1 1 1)

О, а это хитро. Не подумал.

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

Зато можно сгенерить последовательность (+), (+ 1), (+ 1 1), (+ 1 1 1) длиной с какой-нибудь список.

Как это можно сделать на этапе раскрутки макроса, а не в рантайме? Хотя я и в рантайме это пока не придумал.

И вообще, более Scheme-way...

Да, это будет правильно. Еще более scheme-way наверное будет так:

(define string-prefix?
  (case-lambda
    ((prefix str)
     (string-prefix? prefix str 0))
    ((prefix str start1)
     (string-prefix? prefix str start1 (string-length prefix)))
    ((prefix str start1 end1)
     (string-prefix? prefix str start1 end1 0))
    ((prefix str start1 end1 start2)
     (string-prefix? prefix str start1 end1 start2 (string-length str)))
    ((prefix str start1 end1 start2 end2)
     ('procedure-body)))

Я так пробовал сделать, но до макроса дело не дошло, потому что скорость не понравилась.

Puzan ★★★★★ ()
Последнее исправление: Puzan (всего исправлений: 1)
Ответ на: комментарий от Puzan

Как это можно сделать на этапе раскрутки макроса, а не в рантайме? Хотя я и в рантайме это пока не придумал.

Хотя бы так:

(define-syntax iota
  (syntax-rules ()
    ((_ xs) (iota xs ((+))))
    ((_ () is) is)
    ((_ (x . xs) (ii ... (i ...)))
     (iota xs (ii ... (i ...) (i ... 1)))) ) )

Я так пробовал сделать, но до макроса дело не дошло, потому что скорость не понравилась.

Зависит от реализации case-lambda, скорее всего. Для какой-нибудь реализации с более агрессивным статическим анализом она может быть быстрее ручного разбора списка. Но Chibi это ж интерпрератор, case-lambda там разворачивается в пачку if по длинам списков аргументов и последующим apply. Так что да, я не думаю, что там такой вариант будет быстрее варианта в лоб от Монка.

ilammy ★★★ ()
Последнее исправление: ilammy (всего исправлений: 1)
Ответ на: комментарий от Puzan

Как это можно сделать на этапе раскрутки макроса, а не в рантайме?

вот для варианта из оп-поста:

Как это можно сделать на этапе раскрутки макроса, а не в рантайме? 

anonymous ()
Ответ на: комментарий от anonymous
(define-syntax Zero
  (syntax-rules ()
    [(_) 0]))

(define-syntax Succ
  (syntax-rules ()
    [(Succ x) (+ 1 x)]))

(define-syntax optional-args
  (syntax-rules ()
    [(optional-args (opt [x v] ...) body ...) 
     (let ([opt-length (length opt)])
       (helper opt-length ([x v] ...) (body ...) (Zero)))]))

(define-syntax helper
  (syntax-rules ()
    [(_ opt-length () (body ...) n) (begin body ...)]
    [(_ opt-length ([x1 v1] [x v] ...) (body ...) n) 
     (let ([x1 (if (> opt-length n) (list-ref opt n) v1)])
       (helper opt-length ([x v] ...) (body ...) (Succ n)))]))
anonymous ()
Ответ на: комментарий от Puzan

(iota (a b c)) : 1 : { xs = (a b c) } => (iota (a b c) ((+)))

(iota (a b c) ((+))) : 3 : { x = a, xs = (b c), (ii ...) = (), (i ...) = (+) } => (iota (b c) ((+) (+ 1)))

(iota (b c) ((+) (+ 1))) : 3 : { x = b, xs = (c), (ii ...) = ((+)), (i ...) = (+ 1) } => (iota (c) ((+) (+ 1) (+ 1 1)))

(iota (c) ((+) (+ 1) (+ 1 1))) : 3 : { x = c, xs = (), (ii ...) = ((+) (+ 1)), (i ...) = (+ 1 1) } => (iota () ((+) (+ 1) (+ 1 1) (+ 1 1 1)))

(iota () ((+) (+ 1) (+ 1 1) (+ 1 1 1))) : 2 : { is = ((+) (+ 1) (+ 1 1) (+ 1 1 1)) } => ((+) (+ 1) (+ 1 1) (+ 1 1 1))

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