Например: если я хочу, чтобы функция equal?
распознавала мой собственный тип или запись, могу ли я добавить новое поведение equal?
? без стирания или перезаписи старого?
Или, например, если я хочу, чтобы функция "+"
принимала также строку?
Например: если я хочу, чтобы функция equal?
распознавала мой собственный тип или запись, могу ли я добавить новое поведение equal?
? без стирания или перезаписи старого?
Или, например, если я хочу, чтобы функция "+"
принимала также строку?
Вместо использования import
лучшим решением будет отслеживать исходную функцию, связывая ее let
. Также лучше проверить, что тип аргумента является строкой, а не числом. Использование обоих этих подходов означает возможность создания техники.
(define +
(let ((old+ +))
(lambda args
(if (string? (car args))
(apply string-append args)
(apply old+ args)))))
(define +
(let ((old+ +))
(lambda args
(if (vector? (car args))
(apply vector-append args)
(apply old+ args)))))
Вышеприведенное создаст функцию +
, которая работает с числами, строками или векторами. В общем, это более расширяемый подход.
Мне удалось убедиться, что описанное выше работает правильно в MIT/GNU Scheme, Guile, Racket, Chicken, TinyScheme и SCSH. Однако в некоторых реализациях, таких как Biwa Scheme, необходимо использовать set!
вместо define
. В Икарусе set!
нельзя использовать на импортированном примитиве, а define
портит окружение, поэтому необходимо сделать это в два этапа:
(define new+
(let ((old+ +))
(lambda args
(if (string? (car args))
(apply string-append args)
(apply old+ args)))))
(define + new+)
Обратите внимание, что согласно R5RS, define
и set!
должны быть эквивалентны в этот случай:
На верхнем уровне программы определение
(define <variable> <expression>)
имеет практически тот же эффект, что и выражение присваивания
(set! <variable> <expression>)
если
<variable>
привязан.
let
.
- person Lily Chung; 27.05.2014
#!racket ; ==> module: duplicate definition for identifier in: +
и #!r6rs ; ==> module: identifier is already imported in: +
. Он работает в рэкете на устаревшем языке R5RS (не языке модуля #!r5rs) со снятой специальной настройкой Запретить переопределение исходной привязки. В Ikarus вы получите исключение multiple definitions of identifier
, если запустите с --r6rs-script
. REPL, вероятно, более снисходителен, но это не поможет, если вы используете его для проекта.
- person Sylwester; 28.05.2014
define
является letrec
, что делает +
неопределенным во время привязки old+
(как я и предсказывал в своем первом комментарии), но работает вторая версия, в которой используется временный символ new+
.
- person Sylwester; 28.05.2014
Пока решения работают менее чем оптимально в среде R6RS/R7RS. Я думал об дженериках, когда начал играть с этим, но я не хотел создавать свою собственную систему типов. Вместо этого вы предоставляете предикатную процедуру, которая должна гарантировать, что аргументы подходят для этой конкретной процедуры. Это не идеально, но работает аналогично другим ответам R5RS, и вы никогда не переопределяете процедуры.
Я написал все на R6RS, но я думаю, что это легко переносится на R7RS. Вот пример:
#!r6rs
(import (sylwester generic)
(rename (rnrs) (+ rnrs:+))
(only (srfi :43) vector-append))
(define-generic + rnrs:+)
(add-method + (lambda x (string? (car x))) string-append)
(add-method + (lambda x (vector? (car x))) vector-append)
(+ 4 5) ; ==> 9
(+ "Hello," " world!") ; ==> "Hello, world!"
(+ '#(1) '#(2)) ; ==> #(1 2)
Как видите, я импортирую +
под другим именем, поэтому мне не нужно его переопределять (что не разрешено).
Вот реализация библиотеки:
#!r6rs
(library (sylwester generic)
(export define-generic add-method)
(import (rnrs))
(define add-method-tag (make-vector 1))
(define-syntax define-generic
(syntax-rules ()
((_ name default-procedure)
(define name
(let ((procs (list (cons (lambda x #t) default-procedure))))
(define (add-proc id pred proc)
(set! procs (cons (cons pred proc) procs)))
(add-proc #t
(lambda x (eq? (car x) add-method-tag))
add-proc)
(lambda x
(let loop ((procs procs))
(if (apply (caar procs) x)
(apply (cdar procs) x)
(loop (cdr procs))))))))))
(define (add-method name pred proc)
(name add-method-tag pred proc)))
Как видите, я использую передачу сообщений для добавления дополнительных методов.
Хитрость заключается в том, чтобы определить свою собственную расширенную функцию, чтобы она затмевала стандартную функцию, но вызывала стандартную функцию, когда это необходимо. Внутри вашей расширенной функции вы можете сделать import
, чтобы получить доступ к стандартной функции. Вот версия +
, которая также принимает строки:
(define +
(lambda args
(if (number? (car args))
(let ()
(import (scheme))
(apply + args))
(apply string-append args))))
(Это немного неаккуратно, поскольку предполагается, что есть по крайней мере один аргумент, и проверяется только тип первого аргумента. Но это иллюстрирует технику.)
lambda
или макроса, так как они не могут быть связаны в let
. Под расширением lambda
я имею в виду не расширение a lambda
, а переопределение того, что в целом означает само lambda
, путем расширения стандартного определения lambda
. Было бы лучше использовать технику, позволяющую сцеплять любое предыдущее определение lambda
. Есть ли способ сделать это?
- person Ben Kovitz; 27.05.2014
lambda
для отображения строки hello каждый раз, когда вызывается определенная с ней функция: (define-syntax old-lambda lambda)
, затем (define-syntax lambda (syntax-rules () ((_ (args ...) body ...) (old-lambda (args ...) (display "hello") body ...)) ((_ args body ...) (old-lambda args (display "hello") body ...))))
Редактировать: на самом деле, это все равно не будет цепочкой. Проблема в том, что макросы раскрываются лениво, поэтому, если вы попытаетесь связать их в цепочку, они просто войдут в бесконечный цикл.
- person Lily Chung; 28.05.2014
lambda
удалять предыдущее расширение old-lambda
? Редактировать: Ой, похоже да.
- person Ben Kovitz; 28.05.2014
Не чистая схема, но в Guile, например, вы можете использовать CLOS-подобная объектно-ориентированная система:
scheme@(guile-user)> (use-modules (oop goops))
scheme@(guile-user)> (define-method (+ (x <string>) ...) (string-append x ...))
scheme@(guile-user)> (+ "a" "b")
$1 = "ab"
scheme@(guile-user)> (+ "a" "b" "c")
$2 = "abc"
scheme@(guile-user)> (+ 1 2)
$3 = 3
scheme@(guile-user)> (+ 1 2 3)
$4 = 6
Вы не можете использовать
(define +
(let ((old+ +))
...))
потому что define
устанавливает рекурсивную среду для своей формы инициализации. Таким образом, при оценке +
в (old+ +)
он будет несвязан. Как таковой:
> (define +
(let ((old+ +))
(lambda (a b) (display "my+") (old+ a b))))
Unhandled exception
Condition components:
1. &undefined
2. &who: eval
3. &message: "unbound variable"
4. &irritants: (+)
Следующие работы:
> (define old+ +)
> (define + (lambda (a b) (display "my+\n") (old+ a b)))
> (+ 1 2)
my+
3
хотя это не так красиво.
define
создает рекурсивную среду?
- person Lily Chung; 27.05.2014
В R7RS-large (или вообще в любой схеме) вы можете использовать компараторы SRFI 128, которые объединяют идеи равенства, упорядочения и хеширования, чтобы сделать возможным общее сравнение. SRFI 128 позволяет создавать собственные компараторы и использовать их в функциях, поддерживающих компараторы. Например, <?
принимает объект компаратора и два или более объектов типа, связанного с компаратором, и возвращает #t
, если первый объект меньше второго объекта в смысле предиката упорядочивания компаратора.