LINUX.ORG.RU

Почему в scheme не любят set! ?

 , ,


2

2

Может, кто-нибудь знает, почему в Scheme (и в Racket) не приянто использовать set!, особенно в списках

Например, если мне надо список разделить на два по «выполняется/не выполняется условие», то в CL это будет выглядеть как

(defun split (list pred)
  (let ((res1 nil) (res2 nil))
     (dolist (l list)
        (if (pred l)
            (push l res1)
            (push l res2)))
     (values (nreverse res1) (nreverse res2))))

На Scheme полный аналог

(define (split list pred)
  (let ([res1 null] [res2 null])
     (for-each (lambda (l)
                 (if (pred l)
                    (set! res1 (cons l res1))
                    (set! res2 (cons l res2))))
          list)
     (values (reverse res1) (reverse res2))))

Но в учебниках по Scheme пишут, что так писать очень плохо, а надо так:

(define (split list pred)
   (define (loop list res1 res2)
     (cond
       [(null? list) (values (reverse res1) (reverse res2))]
       [else
         (if (pred (car list))
             (loop (cdr list) (cons (car list) res1) res2)
             (loop (cdr list) res1 (cons (car list) res1)))]))
   (loop list null null))
,мол, это понятнее.

Помогите понять, в чём смысл?

★★★★★

список разделить на два по «выполняется/не выполняется условие»

Вроде есть map*, который вычисляет выражение? Или схема не наследует библиотеку CL?

По теме. Этот set! это «опасная» операция? В CL я как-то попался - в макросе аргумент setf был 1. После разворачивания он оказался одним для всех выражений. Я часов пять потратил, пока это обнаружил.

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

Этот set! это «опасная» операция?

Он тождественен setq из CL. Изменять car и cdr через него нельзя.

В scheme есть set-cdr! и set-car! для изменения, а в Racket вообще все списки immutable. Если очень надо, то приходится делать (mcons head tail), который не является списком (нельзя использовать map, length, и т.д.)

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

Просто предпочитают функциональный стиль

Это я понял. Но не понял, почему.

Я понимаю, когда приходится предпочитать функциональный стиль в Haskell, SQL, XSLT. Но ведь Scheme полноценный лисп! Или это такая аскеза?

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

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

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

я бы сделал через remove-if

В смысле (values (remove-if pred list) (remove-if-not pred list)) ?

Здесь два прохода по последовательности будет. В результате в два раза медленнее.

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

Если Common Lisp, то R7RS-small-Scheme

Можем сравнивать Common Lisp и Racket. Они одной весовой категории.

А вот традиции... В Racket неизменяемые списки, нелюбовь к set! и макросам (почти везде, где в CL макрос с body, в Racket функция с параметром-функцией). Пытаюсь понять, это чем-то обусловлено или как писали в одном учебнике «вариант без set! будет проще понять людям, которые не знают, что такое пременная»

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

Думаю, это тоже наследие Scheme. Батарейки батарейками, но дух Scheme (lambda — единственный инструмент абстракции) и общая wannabe-функциональщина остаётся.

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

А я бы сделал через remove-if. Долой императивщину!

<fat> тогда сразу выбирай другой ЯП, вот, например, в С++ есть готовые std::partition и std::partition_copy </fat>

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

Здесь два прохода по последовательности будет. В результате в два раза медленнее.

Ну и что? На списках за быстротой гоняться что ли?

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

А почему вообще чистую функциональность хаскеля выдают за фичу?

Изначально, насколько я помню, как фича для оптимизатора. То есть пишешь ты x = f(a), y = g(a), а оптимизатор может f и g посчитать в другом порядке, а то и вообще параллельно. А если вдруг a — константа, так и вообще сразу вычислить при компиляции.

Затем надо было объяснить, почему это удобно программисту :-) И начался пиар «отсутствие возможности менять переменные лучше, чем её наличие». :-)

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

И начался пиар «отсутствие возможности перейти в произвольное место в программе в любой момент времени лучше, чем её наличие».

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

А шо, схема тоже умеет такие оптимизации?

(Хотя вычислять аргументы в произвольном порядке, если можно, щас кто только не умеет)

vonenij ()

Попутно вопросик. Зачем в Scheme ввели эти шалости, вроде values, ведь можно вернуть просто список? Как это сочетается с идеологией минимализма?

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

И начался пиар «отсутствие возможности пейсать программы, наращивая при этом критическую массу быдла дрочащего на структурное программирование оптимизацию и факториалы, лучше, чем её наличие».

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

Зачем в Scheme ввели эти шалости, вроде values, ведь можно вернуть просто список? Как это сочетается с идеологией минимализма?

Зтем же, зачем у функции несколько параметров. Можно же передавать список :-)

Для симметрии. А также, чтобы показать, что результатов строго определённое количество. Хотя, imho, лучше бы tuple реализовали

Как это сочетается с идеологией минимализма?

Там не минимализм, а минимально необходимые функции для реализации любого алгоритма. Иначе не было бы call/cc, syntax-case, delay и т. д.

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

Это не противоречие минимализму, а обобщение идеи функций: функции могут возвращать не только одно значение. Если понимать минимализм в настолько узком смысле, то тогда всякие векторы и числа тоже не нужны — ведь достаточно списков и символов. Вон функциональные языки в другую крайность идут — оттуда убрали примитив «функции нескольких аргументов».

Правда, что меня смущало в values, так это то, что явную асимметрию входящих и исходящий значений они так и не прячут: для аргументов есть красивый компактный синтаксис, а для значений есть только формы вроде let-values. В том же Прологе сделано более красиво. Но по-другому вряд ли получится, так как в Scheme значения связываются только формами.

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

Кстати, не все в восторге. Вот что, например Товальдс пишет:

No, you've been brainwashed by CS people who thought that Niklaus Wirth

actually knew what he was talking about. He didn't. He doesn't have a frigging clue.

Полностью тут

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

Так же эту точку зрения, ЕМНИП, не разделяли Кнут, Макконнел, и еще многие ученые.

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

Просто он никогда не имел дело с идеоматичным fortran кодом с обильными GOTO, COME FROM и ENTRY. Также нельзя не заметить, что единственный фактический аргумент Линуса был не отсутствие произвольного goto, а об отсутствии break'а в паскале.

Так-то частенько они хороши с точки зрения читаемости тривиальных алгоритмов. Раз в тысячелетие.

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

И чем бы в Scheme tuple отличался от vector?

Синтаксисом :-) Структура от вектора тоже ничем не отличается.

Хочу иметь возможность делать как в питоне

(define (f) (tuple 1 2))
(define-values (x y) (f))
(define (g)
  (let ([res (f)])
    (foo)
    res))

А сейчас приходится использовать let-values с явным перечислением аргументов или begin0 в частных случаях.

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

Для симметрии.

Симметрия не полная (в обоих лиспах): если функция возвращает несколько значений, без танцев с бубном (бинда) их не передашь как параметры другой функции.

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

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

Scheme:

(call-with-values 
  (lambda () (values 1 2))
  (lambda (x y) (+ x y))) ; => 3

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

Тем, что длина кортежа — часть типа.

Учитывая что типизацация в Scheme динамическая, то отличий никаких:

(define ((tuple? length) x)
  (and (vector? x) (= (vector-length x) length)))

(define (tuple . xs)
  (list->vector xs))
korvin_ ★★★★★ ()
Ответ на: комментарий от monk

Хочу иметь возможность делать как в питоне

Так сделай, это ж лисп.

(define-syntax define-match
  (syntax-rules ()
    ((_ (name ...) form)
     (define-values (name ...)
       (match form
         ((vector name ...) (values name ...))
         (else (error "incompatible types")))))))

(define (f) (tuple 1 2))
(define-match (x y) (f))
(define (g)
  (let ((res (f)))
    (printf "(~a, ~a)~%" x y)
    res))
> (g)
(1, 2)
'#(1 2)
korvin_ ★★★★★ ()
Ответ на: комментарий от yyk

Симметрия не полная (в обоих лиспах): если функция возвращает несколько значений, без танцев с бубном (бинда) их не передашь как параметры другой функции.

В Go, кстати, можно:

	func(x, y int) {
		fmt.Printf("(%d, %d)\n", x, y)
	}( func() (int, int) {
		return 1, 2
	}() )

Но сохранить в одну переменную несколько значений нельзя. =/

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

Второе лямбда-выражение лишнее:

(call-with-values (lambda () (values 1 2)) +)
Begemoth ★★★★★ ()
Ответ на: комментарий от vonenij

А шо, схема тоже умеет такие оптимизации?

Кто-то умеет, кто-то нет. Емнип, Gambit, Bigloo и Chicken умеют. Пруфов не будет, лениво искать сейчас.

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

кастыль с неявным биндом - такую макру и в CL нафигачить можно

Бинда, положим, там нет. И вообще это примитив языка, а не макра.

В CL аналогично:

(multiple-value-call #'+ (funcall (lambda () (values 1 2)))

P.S. Если считаешь, что это можно реализовать макрой, то жду реализации. В CL это почему-то special operator :-)

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

Если считаешь, что это можно реализовать макрой, то жду реализации.

Тривиально через multiple-value-list и apply? Даже писать лень :)

В CL это почему-то special operator :-)

в плане вызова «хрен редьки не слаще»

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

Зачем в Scheme ввели эти шалости, вроде values, ведь можно вернуть просто список?

Потому что функция в scheme функция может принимать произвольное количество аргументов. А в языке с нативной поддержкой продолжений нету разницы между возвратом значения из функции и вызовом функции. То есть врозврат (values x y z ...) это просто вызов продолжения, которое принимает x y z ... В общем для консистентности языка.

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

Вот зачем это в схеме я не знаю, а в CL затем, чтобы ничего не аллоцировать в куче

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

Тривиально через multiple-value-list ...

Через самого себя нельзя:

(macroexpand '(multiple-value-list form))

(MULTIPLE-VALUE-CALL #'LIST FORM)

multiple-value-list — как раз макра.

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

считается, что при прочих равных лучше решение без set!.

А как-нибудь аргументируется? Или «розовый ноутбук всегда лучше» (с)?

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

Изучи хаскель - поймёшь. А в двух словах уже много раз объяснили, раз у тебя 5 звёзд наверняка видел (но, видно, не понял).

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

ну, имеем две взаимозаменяемые формы. То, что стандарт требует именно multiple-value-call как спецформу - историческая данность. Могли оставить на откуп реализации

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