Как изменить значения в схеме, но просто используя чисто функциональную парадигму - PullRequest
3 голосов
/ 13 июня 2019

Я пытаюсь изменить значения некоторых переменных, но я не могу сделать это, не выходя из функциональной парадигмы, например, используя set!. Есть ли способ сделать это?

Пример кода:

(lambda ()
      (let ((more a))
        (set! a b)
        (set! b (+ more b))

Я хочу изменить a, принимая значение b, и я хочу изменить b, принимая значение (+ more b), но используя чисто функциональную парадигму , без set!.

Ответы [ 4 ]

6 голосов
/ 13 июня 2019

Вы не можете сделать это, но вы можете сделать что-то эквивалентное. Допустим, у меня есть какая-то функция f1, в которой связаны a и b (скажем, они являются аргументами, поскольку это облегчит задачу). И в какой-то момент я хочу поменять местами a и b. Итак, я начну с этого:

(define f1
  (λ (a b)
    ... code that uses a and b ...
    (let ([tmp a])
      (set! a b)
      (set! b tmp))
    ... code that uses a and b, now swapped ...))

И этот код явно не функционален, так как в нем есть назначение. Но я могу сделать это, придумав новую функцию, f2:

(define f1
  (λ (a b)
    ... code that uses a and b ...
    (f2 b a)))

(define f2
  (λ (a b)
    ... code that uses a and b, now swapped ...))

И этот код является функциональным, и он делает то же самое. И тогда я могу просто избавиться от имени f2, потому что функции первого класса:

(define f1
  (λ (a b)
    ... code that uses a and b ...
    ((λ (a b)
       ... code that uses a and b, now swapped ...)
     b a))

(И, очевидно, мы бы написали это как:

(define f1
  (λ (a b)
    ...
    (let ([b a] [a b])
      ...)))

это то же самое.)

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

Теперь вот умный бит: Схема должна быть корректно хвостовой рекурсивной . Это означает, что хвостовые вызовы должны быть исключены реализациями. Вызов функции в Схеме - это, по сути, GO TO, передающий аргументы, как это классно описано в Разоблачение мифа о «дорогостоящем вызове процедуры», или реализациях вызова процедур, считающихся вредными, или Lambda: The Ultimate GOTO , одна из знаменитых работ «Лямбда-ультрас». Вызов функции к тому, что изначально было f2 и стало анонимной функцией, является хвостовым вызовом, и поэтому должен быть исключен. Любая разумная реализация Схемы, скорее всего, превратит этот код в код, который такой же или, возможно, лучше, чем простой код с присваиванием.


Примечание к лямбда-ультрасовременным документам: сайт, на котором размещались их копии и на которые по-прежнему много ссылок, , в том числе из Википедии *, 1037 * превратился в спам: не подписывайтесь эти ссылки (у сайта было название, которое включало слова «читать» и «схема»). Лучшим местом для их поиска сейчас является хранилище AI Memos на MIT . Довольно досадно, что их стало так трудно найти, поскольку они являются абсолютно основополагающими документами.

4 голосов
/ 13 июня 2019

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

То, что вы можете сделать, - это вызвать другую функцию (возможно, такую ​​же функцию, если записываете цикл), передавая в качестве параметров новые значения "переменных". Например:

(define a 26)
(define b 16)

(define (print-new-values a b)
  ; the modified values exist
  ; only inside this procedure
  (printf "a is ~s~n" a)
  (printf "b is ~s~n" b))

(let ((more a))
  ; notice that the parameter a = b
  ; and that the parameter b = more + b
  ; we didn't reassign anything, instead
  ; the parameters got bound to new values  
  (print-new-values b (+ more b)))

=> a is 16
   b is 42

Приведенный выше код имеет те же выходные данные, что вы намеревались написать, но без использования set!. Для сравнения:

(define a 26)
(define b 16)

(let ((more a))
  (set! a b)
  (set! b (+ more b))
  (printf "a is ~s~n" a)
  (printf "b is ~s~n" b))

=> a is 16
   b is 42
1 голос
/ 14 июня 2019

Это возможно с тенями. Например.

(let ((a 10)) (b 20))
  (let ((b a) (a b))
    (list a b))) ; ==> (20 10)

Однако, если вы хотите иметь полную гибкость, вы делаете это с объектами:

(define (fib-node a b)
  (cons a b))

(define (fib-b f)
  (cdr f))

(define (fib-value f)
  (car f))

(define (fib-next f)
  (let ((b (fib-b f)))
    (fib-node b (+ b (fib-value f)))))

(define fib-null (fib-node 0 1))

;; iterating Fibonacci numbers without setting anything
(let loop ((n 10) (cur fib-null) (acc '()))
  (if (zero? n)
      (reverse acc)
      (loop (- n 1) (fib-next cur) (cons (fib-value cur) acc))))
; ==> (0 1 1 2 3 5 8 13 21 34)
0 голосов
/ 13 июня 2019

Есть много шаблонов, наиболее известная из них - монада.Но вот способ, названный продолжением стиля передачи:

(define set/a/b
  (lambda (a b return)
    ;; new/a <= b
    ;; new/b <= a+b
    (return b (+ a b))))

(define new/values
  (lambda (a b return)
    (set/a/b a b return)))

(new/values 10 20
  (lambda (new/a new/b)
    (display new/a)(newline)
    (display new/b)(newline)))
...