Объектно ориентированный подход на функциях в Scheme

imageПривет. В данной статье хотелось бы еще разок осветить вопрос объектного программирования на языке Scheme, так, как его рассматривают в книге «Структура и интерпретация компьютерных программ».
Далее предлагаю тем, кто еще ни когда не программировал на Scheme скачать DrRacket и попробовать по шагам пройтись по примерам из данной статьи. Опытные программисты Scheme, Racket… эта статья будет очень скучна, так как написана для новичков и людей, желающих «потрогать» racket.

И так по порядку. Разомнемся для начала в DrRacket IDE.
Создадим новый файл программы. Сохраним его.
Вставим в начале файла директиву указывающую на язык:
#lang racket

Определим функцию:
(define (func1 x) x) ; принимает на вход x, возвращает x

Использовать функцию можно так:
(func1 10) ; результат будет 10


Теперь определим другую функцию:
(define (func2) "func2")

А теперь попробуем определить переменную такую, что она применяет первую функцию ко второй и возвращает вторую функцию:
(define x (func1 func2)) ; теперь x - это объект экземпляра функции func2

Использовать x нужно как функцию:
(x) ; вернет стоку "func2"

Тут мы использовали возможность того, что функция может возвращать функции, а их результат можно определить как переменные.
Далее давайте создадим объект, инкапсулирующий в себе внутренние переменные и другие функции:
(define (MyObject field1 field2) ;  объект у которого при конструировании будут две переменные field1 и field2  
    (let ((f1 field1) ; инициализация внутренней переменной f1, с помощью field1
          (f2 field2)) ; ...
    (define (get-f1) f1) ; вернуть значение внутреннего поля f1
    (define (get-f2) f2) ; ...
    (define (set-f1 x) (set! f1 x)) ; присваиваем f1 значение x
    (define (set-f2 x) (set! f2 x)) ; ...
    ; далее идет самое интересное
    (define (dispatch m)    ; функция диспетчирования функций в нашем объекте
          (cond ((eq? m 'get-f1) get-f1) ; если m равно get-f1, то возвращается функция get-f1
                ((eq? m 'set-f1) set-f1) ; ...
                ((eq? m 'get-f2) get-f2) ; ...
                ((eq? m 'set-f2) set-f2) ; ...
          )
    )
dispatch)) ; тут мы в функции MyObject возвращаем функцию диспетчирования 

Ну вот объект определен, теперь сделаем экземпляр объекта MyObject:
(define Obj1 (MyObject " Hello " " world!!! ")) ; теперь Obj1 экземпляр

Далее просто используем экземпляр так как захотим:
(display ((Obj1 'get-f1))) ; тут ф-ия display что-то типа printf, а двойные скобки
; в ((Obj1 'get-f1)) надо писать для того, чтобы вычислялась функция get-f1
(display ((Obj1 'get-f2))) ; аналогично
(newline)    ; переход на новую строку
; результатом будет " Hello world!!! "

Создадим новый экземпляр того же объекта:
(define Obj2 (MyObject " Hello " " habra!!! ")) ;
; результат: "Hello  Hello  habra!!!  world!!!"

Попробуем выполнить:
(display ((Obj1 'get-f1))) 
(display ((Obj1 'get-f2))) 
(newline)    
(display ((Obj2 'get-f1))) 
(display ((Obj2 'get-f2))) 
(newline)
; результатом будет "Hello world!!!"
; и "Hello habra!!!" 

Что меня поразило — каждый экземпляр возвращает одни и теже функции, но их поведение не одинаковое.
То есть в Scheme, в отличие от С++, функции берут переменные и функции для вычисления из своего экземпляра.
Данная особенность сильно помогает организации списков функций для изменения внутренних состояний разных экземпляров одного или нескольких объектов:
(define func-list (list (Obj1 'get-f1) (Obj2 'get-f1) (Obj2 'get-f2) (Obj1 'get-f2)))

Распечатаем выполнения каждой функции из этого списка так:
(map (lambda (x) (display (x))) func-list)
; результат: «Hello Hello habra!!! world!!!»

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 7

    +1
    Очень интересно было прочитать о взаимосвязи ФП и ООП. В Scheme такая реализация инкапсуляции красиво выглядит. Вопрос: а в Коммон Лиспе можно такое провернуть?
    0
    Написал для CLISP в файл prog1.lisp:
    (defun Object (x1 )
    	(defvar var1 x1)
    	(defun get-var1 () var1)
    	(defun set-var1 (x) (setf var1 x))
    	(defun dispatch (m)
    		(cond ((EQ m 'get-var1) (function get-var1))
    			  ((EQ m 'set-var1) (function set-var1))
    		)
    	)
    	 (function  dispatch)
    	)
    (defun main ()
      (defparameter Obj1 (Object " Hello world!!! " ))
      (format t "get text result:~s~%" (funcall (funcall Obj1 'get-var1)))
      (funcall (funcall Obj1 'set-var1) "Hello habra!!!")
      (format t "get text result:~s\~%" (funcall (funcall Obj1 'get-var1)))
    )
    

    Компилируем:
    $ clisp -c prog1.lisp
    

    Запускаем:
    $ clisp -q -q -on-error abort -x '(progn (load "prog1") (main) (quit))'
    

      0
      Да, в Scheme красивее выглядит. Без funcall в Коммон Лиспе не получится так сделать, я правильно понимаю?
      Кстати, defvar и defun определяют переменные и функции глобально. В Вашей программе при создании второго объекта всё перемешается. Нужно поля и методы в лексическом окружении делать через let и labels
      (defun Object (x1 )
          (let ((var1 x1))
              (labels ((get-var1 () var1)
      		 (set-var1 (x) (setf var1 x))
      		 (dispatch (m)
      		     (cond ((EQ m 'get-var1) (function get-var1))
      			   ((EQ m 'set-var1) (function set-var1)))))
                  (function  dispatch))))

Only users with full accounts can post comments. Log in, please.