LINUX.ORG.RU

Помогите разобраться с мультиметодами.

 , , , ,


1

3

Я пытаюсь найти пример, по которому можно четко понять преимущество мультиметодов над одиночной диспетчеризацией и message-passing. Оговорюсь, на всякий случай, что это преимущество я не пытаюсь отрицать, просто до меня пока не доходит.

Сначала пытался понять пример с астероидами и кораблями с википедии.

https://ru.wikipedia.org/wiki/Мультиметод#Common_Lisp

Но,по этому примеру невозможно ничего понять, потому-что, на мой взгляд, задача сформулированна неверно. Во-первых, столкновение это событие. Причем здесь функция collide — абсолютно непонятно. Функция может быть инициатором события, а нам в данной задаче нужна реакция на событие. Во-вторых, когда сталкиваются 2 объекта, вопрос о том кто с кем столкнулся не имеет никакого смысла, тут невозможно выделить пассивную и активную сторону, активной стороной являются обстоятельства, которые привели к столкновению. Поэтому, пример, мягко говоря синтетический, и ничего не проясняет. Ну, и кроме того, в том виде он тривиально пишется и в «традиционном» стиле.

Затем я пытался понять преимущество мультиметодов на этом вот примере из PCL

http://spline-online.tk/wiki/doku.php?id=pcl:chapter16#defmethod

С банковским «приложением», но снова ничего не понял. Все что там показывается, без мультиметодов, по моему, будет не хуже, и не сложней.

Очень хотелось бы увидеть какое-то объяснение, или пример, где преимущество мультиметодов было бы очевидно и однозначно. К сожалению, я таких примеров пока не нашел.

Подскажите пожалуйста, где можно такое найти, или, если не трудно, приведите пример, демонстрирующий преимущество.



Последнее исправление: asteroidcollide (всего исправлений: 2)

Бесполезно искать примеры демонстрирующие преимущество. Просто в ООП множественная диспетчеризация обычно не нужна и её не используют. А иногда нужна и тогда в языках вроде Common Lisp или C# её используют, а в языках вроде Java или C++ ставят сотни костылей чтобы сделать вид что она есть.

Gentooshnik ★★★★★
()

Например, Visitor

Пример на Java

public class Demo {
	public static void main ( String [] args ) {
		Point p = new Point2d( 1, 2 );
		Visitor v = new Chebyshev();
		p.accept( v );
		System.out.println( p.getMetric() );
	}
}

interface Visitor {
	public void visit ( Point2d p );
	public void visit ( Point3d p );
}

abstract class Point {
	public abstract void accept ( Visitor v );
	private double metric = -1;
	public double getMetric () {
		return metric;
	}
	public void setMetric ( double metric ) {
		this.metric = metric;
	}
}

class Point2d extends Point {
	public Point2d ( double x, double y ) {
		this.x = x;
        this.y = y;
	}
	
	public void accept ( Visitor v ) {
		v.visit( this );
	}
	
	private double x;
	public double getX () { return x; }
	
	private double y;
	public double getY () { return y; }
}

class Point3d extends Point {
	public Point3d ( double x, double y, double z ) {
		this.x = x;
        this.y = y;
        this.z = z;
	}
	public void accept ( Visitor v ) {
		v.visit( this );
	}
	
	private double x;
	public double getX () { return x; }
	
	private double y;
	public double getY () { return y; }
	
	private double z;
	public double getZ () { return z; }
}

class Euclid implements Visitor {
	public void visit ( Point2d p ) {
		p.setMetric( Math.sqrt( p.getX()*p.getX() + p.getY()*p.getY() ) );
	}
	public void visit ( Point3d p ) {
		p.setMetric( Math.sqrt( p.getX()*p.getX() + p.getY()*p.getY() + p.getZ()*p.getZ() ) );
	}
}

class Chebyshev implements Visitor {
	public void visit ( Point2d p ) {
		double ax = Math.abs( p.getX() );
		double ay = Math.abs( p.getY() );
		p.setMetric( ax>ay ? ax : ay );
	}
	public void visit ( Point3d p ) {
		double ax = Math.abs( p.getX() );
		double ay = Math.abs( p.getY() );
		double az = Math.abs( p.getZ() );
		double max = ax>ay ? ax : ay;
		if ( max<az ) max = az;
		p.setMetric( max );
	}
}

Аналог на CLOS

(defun demo ()
  (let ((p (make-instance 'point2d :x 1 :y 2))
        (v (make-instance 'chebyshev)))
    (visit p v)
    (print (metric p v))))

(defclass point ()
  ((metric :initform -1 :accessor metric)))

(defclass point2d (point)
  ((x :initarg :x :reader get-x)
   (y :initarg :y :reader get-y)))

(defclass point3d (point)
  ((x :initarg :x :reader get-x)
   (y :initarg :y :reader get-y)
   (z :initarg :z :reader get-z)))

(defclass euclid () ())

(defmethod visit ((p point2d) (euclid v))
   ...)

(defmethod visit ((p point3d) (euclid v))
   ...)

(defclass chebyshev () ())

(defmethod visit ((p point2d) (chebyshev v))
   ...)

(defmethod visit ((p point3d) (chebyshev v))
   ...)

В отличие от Java не требуется заранее в интерфейсе visit перечислять всех потомков Point. Также нет обязанности забивать пустышками все возможные комбинации методов (предположим, есть метод foo, который считает только на плоскости, в Java всё равно обязательно определить оба метода интерфейса Visitor).

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

monk ★★★★★
()

А ещё позволяет делать красивые вещи типа: https://common-lisp.net/~frideau/lil-ilc2012/lil-ilc2012.html . Когда можно написать

(defgeneric convert
    (<destination> <origin> object)
  (:documentation "Convert an OBJECT
  following interface <ORIGIN> into a new object
  following interface <DESTINATION>."))

Кстати, эта библиотека ещё и обходит ограничения принципа Лисков. Операции определены на интерфейсах, а не на объектах, поэтому можно просто писать:

(define-interface <alist>
    (<map-empty-as-nil>
     <map-decons-from-first-key-value-drop>
     <map-update-key-from-lookup-insert-drop>
     <map-divide/list-from-divide>
     <map-map/2-from-fold-left-lookup-insert-drop>
     <map-join-from-fold-left-insert>
     <map-join/list-from-join>
     <map>)
  ((key-interface :type <eq>
    :initarg :key-interface
    :reader key-interface))
  (:parametric (&optional (eq <eql>))
     (make-interface :key-interface eq))
  (:singleton))
monk ★★★★★
()
Ответ на: комментарий от monk

Да, визитор — это, вероятно, единственный пример. Но паттерн визитор — это достаточно странная штука:) Он является тавтологией. Любой метод в ООП можно считать частным случаем визитора, потому что вызов foo.bar(baz) можно трактовать как то, что bar посещает foo, в данном случае. Поэтому, применение его, по-сути, бессмысленно:)

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

Кстати, эта библиотека ещё и обходит ограничения принципа Лисков.

А какие могут быть ограничения у принципа Лисков? Это ведь дело добровольное, хочешь придерживайся, хочешь нет. Просто если мы договорились его придерживаться, у нас клиент всегда может обратится к подтипу так же как и к надтипу, не зная об этом. Это всего лишь наш контракт, тут нет никаких «ограничений», это мы сами себя ограничиваем, тем, что мы запрещаем изменять интерфейс подтипа.

А пример Ваш второй я, к сожалению, не понял. Это слишком сложно для меня, понять, что там происходит:)

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

Когда можно написать

А вообще, это похоже на обычный прокси

asteroidcollide
() автор топика

Умножение двух чисел, про тип которых ничего статически неизвестно, кроме того, что это число (оно же и есть визитор).

den73 ★★★★★
()

Вот я в одном своём личном проекте использовал мультиметоды для написания макроса. Код есть только у меня и он довольно большой, но опишу его вкраце вот так: есть поток данных, которые первичный парсер преобразовывал в объекты-сигналы и отдавал коллектору. Коллектор - обобщённая функция, использующая мультиметоды, аргументами которого были: объект-сигнал и объект - собранные данные. После окончания потока данных из собранных данных - из множества готовых блоков, которые и должны сформироваться коллектором, собирался компилируемый CL код.

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

Конечно сложность была в отладке сборки эффективного метода из всего этого, но более простой путь я до сих пор не увидел.

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

оно же и есть визитор).

Почему нельзя так сделать:

Number10orMore := Object clone do(
    clone := method(initNumber,
       if(initNumber < 10, Exception raise("init number < 10"); return)
       n := resend; n value := initNumber; n
    )
   * := method(n, Number10orMore clone(value * n value)) 
   print := method(self value print)
)


write ( Number10orMore clone(10) * (Number10orMore clone(20)) ) // #>>>>200

?

asteroidcollide
() автор топика

Во-первых, столкновение это событие. Причем здесь функция collide — абсолютно непонятно

collide обрабатывает результат события - меняет состояние обоих объектов.

вопрос о том кто с кем столкнулся не имеет никакого смысла, тут невозможно выделить пассивную и активную сторону

Вот именно. Поэтому тут есть 2 варианта:

1.Менеджер столкновения. Компонент который вызывает collide у каждого столкнувшегося объекта и передает объект с которым столкнулись rock[n].collide(rock[m]). Но тут есть проблема - внутри collide, если мы хотим по-разному реагировать на разные типы объектов с которыми столкнулись, то придётся лепить либо switch по типу объекта, либо visitor. Если количество объектов больше 1, говнокод растёт в геометрической прогрессии.

2.Просто пишем мультиметод на каждый нужный вариант. collide (rock) (rock), collide (rock) (rocket), collide (debris) (rock), collide (debris) (rocket) и т.д. Тут можно подумать, что это можно сделать просто перегрузкой, но нет - перегрузка не будет учитывать наследование объектов, т.к. резольвится статически во время компиляции.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

collide обрабатывает результат события - меняет состояние обоих объектов.

Если он обрабатывает, то кто же тогда инициирует событие?

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

Если он обрабатывает, то кто же тогда инициирует событие

Вычислительный движок который обрабатывает коллекцию объектов, определяя векторы движения, столкновения и т.п. - это никакого отношения к проблеме не имеет.

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

это никакого отношения к проблеме не имеет.

боюсь, что имеет. Что если я хочу явно разделить инициацию события и его обработку, я могу это сделать с помощью мультиметодов?

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

Что если я хочу явно разделить инициацию события и его обработку, я могу это сделать с помощью мультиметодов?

Давай ты объяснишь, что ты хочешь без мультиметодов, на простых объектах. У меня такое впечатление, что ты сам этого не понимаешь.

no-such-file ★★★★★
()

Во-первых, столкновение это событие. Причем здесь функция collide — абсолютно непонятно. Бла-бла-бла

Болтать языком каждый может код давай покажи как ты без мультиметодов это реализуешь. А я тебе покажу что твоя реализация - херня.

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

как ты без мультиметодов это реализуешь

Конкретно это? Да два пальца.

Asteroid := Object clone do(
 collide := method(o,
 if(o type == "Asteroid", /*астероид сталкивается с астероидом*/)
 if(o type == "Spaceship", /*астероид сталкивается с космическим кораблем*/)
 )
)
Spaceship := Object clone do(
 collide := method(o, 
 if(o type == "Spaceship", /*космический корабль сталкивается с космическим кораблем*/)
 if(o type == "Asteroid", /*космический корабль сталкивается с астероидом*/)
 )
)
Я тебе даже мультиметоды твои реализую на коленке в 5 строк кода, только без наследования
MultyMethod := List clone do(
     dispatch := method(args,
        argTypes := args map(type)
        self foreach(block, if(block argumentNames == argTypes, return block callWithArgList(args)))
     )
)

/////////////////////////////////////////////

Asteroid := Object clone 
SpaseShip := Object clone 

asteroid := Asteroid clone
spaceShip := SpaseShip clone


multyMethod := MultyMethod clone
collide := method(multyMethod dispatch(call evalArgs))

multyMethod push(block(Asteroid, SpaseShip, writeln("asteroid collide with spaceship")))
multyMethod push(block(SpaseShip, Asteroid,  writeln("spaceship collide with asteroid")))

collide(asteroid, spaceShip) #>>>>asteroid collide with spaceship
collide(spaceShip, asteroid) #>>>>spaceship collide with asteroid

А я тебе покажу что твоя реализация - херня.

Ты мне лучше покажи пример, где они реально рулят, потому что из топика однозначно пока только то, что они заменяют визитор, который сам по себе — ненужный костыль, остальное пока в тумане.

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

Конкретно это? Да два пальца.

Теперь представь что этот код пришел к тебе из внешней либы и ты хочешь добавить логику collide для собственных объектов. Пример в студию.

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

Но вот интересно будет, как он будет дописывать методы уже имеющихся объектов из либы. При дополнительном условии, конечно, что код методов либы недоступен.

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

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

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

Теперь представь что этот код пришел к тебе из внешней либы и ты хочешь добавить логику collide для собственных объектов. Пример в студию.

Так это фигня. Вот простенький вариант, есть еще куча


// come from library

Collidable := Object clone do(
   collide := method(o,
      doMessage(o type asMessage)
   )
)

Asteroid := Collidable clone do(
   Spaceship := method(writeln("asteroid collide with spaceship"))
)
Spaceship := Collidable clone do(
   Asteroid := method(writeln("spaceship collide with asteroid"))
)

// come from library

Planet := Collidable clone

Asteroid Planet := method(writeln("asteroid collide with planet")) // extend behavor

asteroid := Asteroid clone
spaceship := Spaceship clone
planet := Planet clone 

asteroid collide(spaceship) #>>>> asteroid collide with spaceship
asteroid collide(planet)    #>>>> asteroid collide with planet
spaceship collide(asteroid) #>>>> spaceship collide with asteroid

asteroidcollide
() автор топика
Ответ на: комментарий от loz

Без наследования

Туда наследование прикрутить не так уж сложно, думается. Только вот, надо ли оно? Вот это сомнительно. Во первых, из-за сложного лукапа пострадает перформанс. Во-вторых, логика там неоднозначна и противоречива. например

collide(type, subtype)
collide(subtype, type)
общелисп безо всякой логики выберет второе.

Кроме того, это сложный неудобный интерфейс, использование предполагает детальное продумывание и отслеживание конфликта типов, а вот профиты по-прежнему неочевидны.

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

Две последние буквы у CLOS - object system. Ну как же здесь без наследования?

общелисп безо всякой логики выберет второе.

Сам то понял что сказал?

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

Asteroid Planet := method(writeln(«asteroid collide with planet»)) // extend behavor

Хитро ты прыгнул от цепочки условий к диспатчингу методов за счет типов. Допустим даже это сработает правильно с учетом наследования, не суть.

Суть мультиметодов в том, что в нем в отличие от «стандартного» деспатчинга по this диспатч происходит по всем аргументам. Покажи мне как ты в io добавишь третий аргумент?

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

добавить логику collide для собственных объектов

точней будет так


// come from library

Collidable := Object clone do(
   collide := method(o,
      doMessage(o type asMessage)
   )
)

Asteroid := Collidable clone do(
   Spaceship := method(writeln("asteroid collide with spaceship"))
)
Spaceship := Collidable clone do(
   Asteroid := method(writeln("spaceship collide with asteroid"))
)

// come from library

Planet := Collidable clone do( // own object
   Asteroid := method(writeln("planet collide with asteroid"))
)

Asteroid Planet := method(writeln("asteroid collide with planet")) // extend behavor

asteroid := Asteroid clone
spaceship := Spaceship clone
planet := Planet clone 

asteroid collide(spaceship) #>>>> asteroid collide with spaceship
asteroid collide(planet)    #>>>> asteroid collide with planet
spaceship collide(asteroid) #>>>> spaceship collide with asteroid
planet collide(asteroid)    #>>>> planet collide with asteroid

Это тот же самый интерфейс, только у собственного объекта собственное поведение

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

Сам то понял что сказал?

Да понял. Я читал про работу диспетчера, он выбирает наиболее специализированный тип из цепочки подтипов, в случае конфликта, который я обозначил, он выберет по приоритету левого аргумента, что лишено всякой логики.

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

Во первых, из-за сложного лукапа пострадает перформанс.

Без конкретных примеров и бенчмарков обоих вариантов нечего обсуждать перформанс.

общелисп безо всякой логики выберет второе.

Я не уверен описано ли это в стандарте, но соглашусь что логика диспатча не самая простая и плохо если это отдано на откуп реализации.

предполагает детальное продумывание и отслеживание конфликта типов

У тебя бывало что ты пишешь и пишешь, а потом понимаешь что херня, количество хаков зашкаливает и надо менять что-то в подходе? Любой нетривиальный код предполагает продумывание, за тебя язык не решит проблему самостоятельно.

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

А вот и нет. Он выберет это поумолчанию - это можно менять.

И «выберет» - это неправильный термин здесь. Правильнее - он собирает из методов эффективный метод. Интересно как ты собираешься это делать в соей реализации.

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

профиты по-прежнему неочевидны

Да, либо ситуации где мультиметоды решают проблемы действительно редки, либо мы мало о них знаем т.к. большинство книги и блогов рассказывают как легко и просто отнаследовать треугольник от формы и грузовик от машины.

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

Покажи мне как ты в io добавишь третий аргумент?

Вообще, это не совсем то что в CL видимо, но ты навел меня на мысль:)

Можно обеспечить даже более гибкий подход в этом смысле. Смотри что получается


Collidable := Object clone do(
   collide := method(
      call evalArgs foreach(o, doMessage(o type asMessage) )
   )
)

Asteroid := Collidable clone do(
   Spaceship := method(writeln("asteroid collide with spaceship"))
)
Spaceship := Collidable clone do(
   Asteroid := method(writeln("spaceship collide with asteroid"))
)

// come from library

Planet := Collidable clone do( // own object
   Asteroid := method(writeln("planet collide with asteroid"))
)

Asteroid Planet := method(writeln("asteroid collide with planet")) // extend behavor

asteroid := Asteroid clone
spaceship := Spaceship clone
planet := Planet clone 

asteroid collide(planet, spaceship)

#>>>> asteroid collide with planet
#>>>> asteroid collide with spaceship

lol, мультидиспетчеризация такой гибкости не сможет обеспечить:)

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

А вот и нет. Он выберет это поумолчанию - это можно менять.

Вот я и говорю, запаришься плясать с этими бубнами, проще свою упрощенную версию реализовать, где все проще. Меньше голову ломать придется. Все равно любой кейз, который я встречал на Io покрывается в несколько строк, плюс, не платим за лишние вычисления.

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

Я не настаиваю ни на чем. Тред я создал для того, чтобы понять, в каких ситуациях подход с мультидиспетчеризацией действительно выигрывает. В конце концов, не обязательно реализовывать все один в один, можно использовать упрощенную реализацию в качестве паттерна, если оно стоит того где-то.

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

lol, мультидиспетчеризация такой гибкости не сможет обеспечить:)

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

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

Они мне не особо нужны, потому что я напрямую могу определить особенности диспетчеризации. Например, я могу сделать так, чтобы вызовы прекращались, если при столкновении встретится «сородич»:


Collidable := Object clone do(
   collide := method(
      call evalArgs foreach(o, o isKindOf(self proto) ifTrue(return); doMessage(o type asMessage) )
   )
)

Asteroid := Collidable clone do(
   Asteroid := method(writeln("asteroid collide with asteroid"))
   Spaceship := method(writeln("asteroid collide with spaceship"))
   Planet := method(writeln("asteroid collide with planet"))
)

Spaceship := Collidable clone
Planet := Collidable clone


asteroid1 := Asteroid clone
asteroid2 := Asteroid clone
spaceship1 := Spaceship clone
spaceship2 := Spaceship clone
planet1 := Planet clone 
planet2 := Planet clone 

writeln("call with break on asteroid2", ": ")
asteroid1 collide(planet1, planet2, asteroid2, spaceship1, spaceship2)
writeln
writeln("call with break on asteroid2, on another place", ": ")
asteroid1 collide(planet1, planet2, spaceship1, asteroid2, spaceship2)
writeln
writeln("call with no break, cause no asteroid instance in arglist", ": ")
asteroid1 collide(planet1, planet2, spaceship1, spaceship2)




#>>>> call with break on asteroid2: 
#>>>> asteroid collide with planet
#>>>> asteroid collide with planet
#>>>> 
#>>>> call with break on asteroid2, on another place: 
#>>>> asteroid collide with planet
#>>>> asteroid collide with planet
#>>>> asteroid collide with spaceship
#>>>> 
#>>>> call with no break, cause no asteroid instance in arglist: 
#>>>> asteroid collide with planet
#>>>> asteroid collide with planet
#>>>> asteroid collide with spaceship
#>>>> asteroid collide with spaceship

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

Зачем только такие треды создавать?

Этот же анонiмус. Он псих.

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

Любой метод в ООП можно считать частным случаем визитора

Визитор позволяет добавлять методы foo.bar(baz) для новых типов baz не переписывая класс foo. Мультиметод позволяет то же самое, но проще.

Разумеется, мультиметоды можно реализовать вручную на любом языке (в CL первая реализация CLOS тоже была всего лишь набором макросов).

Поэтому, применение его, по-сути, бессмысленно

Паттерн определяет возможности дальнейшего развития программы, а не семантику. В одиночной диспетчеризации без визитора расширение по типу аргумента метода сделать никак.

monk ★★★★★
()
Ответ на: комментарий от asteroidcollide
Collidable := Object clone do(
   collide := method(o,
      doMessage(o type asMessage)
   )
)

А что будешь делать, если надо несколько методов, а не только один?

(defmethod collide ((asteroid o1) (spaceship o2))
  (print "asteroid collide with spaceship"))

(defmethod collide ((spaceship o1) (asteroid o2))
  (print "spaceship collide with asteroid"))

(defmethod fire (o1 o2)
  (print "it cannot fire"))

(defmethod fire ((spaceship o1) (asteroid o2))
  (print "spaceship destroyed asteroid")) 

(let ((asteroid (make-instance 'asteroid))
      (spaceship (make-instance 'spaceship)))
  (collide asteroid spaceship) ;; asteroid collide with spaceship
  (collide spaceship asteroid) ;; spaceship collide with asteroid
  (fire spaceship asteroid) ;; spaceship destroyed asteroid
  (fire asteroid spaceship)) ;; it cannot fire
monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от asteroidcollide

Они мне не особо нужны, потому что я напрямую могу определить особенности диспетчеризации.

И что это за ООП если там нет комбинаторов?

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

А что будешь делать, если надо несколько методов, а не только один?

Легко реализуемо


MultiMethod := List clone do(
    add := method(appendSeq(call evalArgs))
    call := method(args,
       types := args map(type)
       detect(argumentNames == types) clone setScope(call sender) callWithArgList(args)
    )
)

Object setMultimethod := method(name,
   mName := ("_" .. name)
   self setSlot(mName, MultiMethod clone)
   self setSlot(name, doString("method(#{mName} call(call evalArgs) )" interpolate))
)

/////////////////////////////////////////////////////////////////////////////////////////


Asteroid := Object clone do(
   setMultimethod("collide")
   _collide add(block(Spaceship, "asteroid collide with spaceship" println))
   setMultimethod("fire")
   _fire add(block(Spaceship, "asteroid destroyed spaceship" println))
)
Spaceship := Object clone do(
  setMultimethod("collide")
  _collide add(block(Asteroid, "spaceship collide with asteroid" println))
  setMultimethod("fire")
  _fire add(block(Asteroid, "it cannot fire" println))
)

asteroid := Asteroid clone
spaceship := Spaceship clone

asteroid collide(spaceship)  #>>>> asteroid collide with spaceship
spaceship collide(asteroid)  #>>>> spaceship collide with asteroid
asteroid fire(spaceship)     #>>>> asteroid destroyed spaceship
spaceship fire(asteroid)     #>>>> it cannot fire

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

Как не имеет? Наследование к ООП отношение же имеет. Это даже одно из ключевых компонент ООП. Но тогда какой будет толк от наследования если нет комбинаторов?

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

От комбинаторов нет толка, я и без них все могу разрулить. Это в лиспе костыли нужны.

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

Кстати, слегка модифицированная версия совместима с нативным наследованием, и сами мультиметоды наследуются

MultiMethod := List clone do(
    add := method(appendSeq(call evalArgs))
    call := method(args,
       types := args map(type)
       detect(argumentNames == types) clone setScope(call sender self) callWithArgList(args)
    )
)

Object setMultimethod := method(name,
   mName := ("_" .. name)
   self setSlot(mName, MultiMethod clone)
   self setSlot(name, doString("method(#{mName} call(call evalArgs) )" interpolate))
)

/////////////////////////////////////////////////////////////////////////////////////////

show := method(o1, o2, event, effect,
   writeln(o1 .. " " .. event .. " " .. o2 .. "; " .. effect .. " around here!!!")
   
)

SpaceObject := Object clone do(
   setMultimethod("collide")
   _collide add(
     block(Spaceship, show(self type, "Spaceship", "collide", sound)),
     block(Asteroid, show(self type, "Asteroid", "collide", sound))
    )
   setMultimethod("fire")
   _fire add(
    block(Spaceship, show(self type, "Spaceship", "fire", flame)),
    block(Asteroid, show(self type, "Asteroid", "fire", flame))
   )
)

Asteroid := SpaceObject clone do (sound := "boom" ; flame := "green flame")
Spaceship := SpaceObject clone do (sound := "baam"; flame := "red flame")

asteroid1 := Asteroid clone 
asteroid2 := Asteroid clone 
spaceship1 := Spaceship clone
spaceship2 := Spaceship clone

asteroid1 collide(spaceship1)    #>>>>Asteroid collide Spaceship; boom around here!!!
spaceship1 collide(asteroid1)    #>>>>Spaceship collide Asteroid; baam around here!!!
asteroid1 collide(asteroid2)     #>>>>Asteroid collide Asteroid; boom around here!!!
spaceship1 collide(spaceship2)   #>>>>Spaceship collide Spaceship; baam around here!!!
asteroid1 fire(spaceship1)       #>>>>Asteroid fire Spaceship; green flame around here!!!
spaceship1 fire(asteroid1)       #>>>>Spaceship fire Asteroid; red flame around here!!!
asteroid1 fire(asteroid2)        #>>>>Asteroid fire Asteroid; green flame around here!!!
spaceship1 fire(spaceship2)      #>>>>Spaceship fire Spaceship; red flame around here!!!

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

и сами мультиметоды наследуются

Уже почти хорошо. Осталось придумать, чтобы не приходилось SpaceObject'у знать о всех своих потомках и будет как настоящий.

Или

SpaceObject := Object clone do(
   setMultimethod("collide")
   setMultimethod("fire")
   
)

Asteroid := SpaceObject clone do (sound := "boom" ; flame := "green flame")
Spaceship := SpaceObject clone do (sound := "baam"; flame := "red flame"
   _collide add(
     block(Spaceship, show(self type, "Spaceship", "collide", sound)),
     block(Asteroid, show(self type, "Asteroid", "collide", sound))
    )
_fire add(
    block(Spaceship, show(self type, "Spaceship", "fire", flame)),
    block(Asteroid, show(self type, "Asteroid", "fire", flame))
   )
)
тоже будет работать? Вроде должно, если я правильно понял код.

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

Ничего плохого в этом нет. Это эквивалентно добавлению потомком в массив родителя, обычная практика. Чтобы сделать по другому, нужно чтобы в каждом из классов был собственный мультиметод.

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

хорошо.

Хорошо то хорошо, но преимущество такого подхода, и область его применения — эти темы по-прежнему не раскрыты

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