LINUX.ORG.RU

[Ruby] связи между классами

 


0

1

Сперва, я очень извиняюсь, я не программист.

Преамбула: хочу написать простенький геометрический язык для решения геом. задач. Идея такая: будет некая доска (class Board ?), на которую мы будем помещать объекты (class Line, Point, Segment, Circle,...). Их можно пересекать и т. д. Выбрал ruby как наиболеее простой язык, практически для домохозяек, я после 20минутного туториала уже основные идеи вкурил.

Амбула: Например мне нужно найти точку как пересечение двух прямых. Значит мне надо сделать метод intersept в class Line, который вернет экземпляр class Point. Тут все вроде нормально.

Вопрос: А вот если мне нужно пересечь, скажем, прямую с окрежностью. Мне надо писать два раза один и тот же метод? Ну то есть в Line и в Circle? Вызываться потом будет примерно так

p = Point.new
p = the_line * the_circe
или
p = the_circle * the_line
Здесь * — это имя метода. Как это реализовать?


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

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

Смотри в сторону двойной диспетчеризации (мультиметодов). Не знаю как в Руби, в CL получится очень красиво.

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

Можно пример на CL? Я немного знаю Scheme (читал SICP), но не стал браться за функ. языки, т.к. мне кажется будет проблема с выводом (я собираюсь «доску» после всех манипуляций выводить в eps или metepost). На императивных это легче сделать (по крайней мере, новичкам).

Кстати, из ваших умных слов из первого предложения ничего не понял.

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

Ruby язык не самый простой, но все равно выбор однозначно очень хороший.

Мне надо писать два раза один и тот же метод?

Да, но один метод можно определить через другой. Решение в лоб:

class Line
  def initialize(...)
    ...
  end
  
  def intersect(object)
    case object
    when Circle then intersection_with_circle(object)
    when Line then intersection_with_line(object)
    end
  end

  def intersection_with_line
    # ...
  end

  def intersection_with_circle
    # ...
  end
end

class Circle
  def initialize(...)
    ...
  end
  
  def intersect(object)
    case object
    when Line then object.intersect(self)
    when Circle then intersection_with_circle(object)
    end
  end

  def intersection_with_circle
    # ...
  end
end

Но лучше вынести весь подсчет пересечений куда-нибудь в одно место, т.е.

class Intersector
  def self.intersect(obj1, obj2)
    case [obj1.class, obj2.class]
    when [Line, Circle] then line_n_circle(obj1, obj2)
    when [Circle, Line] then line_n_circle(obj2, obj1)
    when [Line, Line] then line_n_line(obj1, obj2)
    end
  end
  
  def line_n_circle(line, circle)
    # ...
  end
  
  def line_n_line(line1, line2)
    # ...
  end
end

class Figure
  def intersect(object)
    Intersector.intersect(self, object)
  end
end

class Line < Figure
  # ...
end

class Circle < Figure
  # ...
end

Мне ещё не понятно, как это всё «добавлять» на доску

Board можно определить как наследник класса Array, тогда некоторую часть функционала требуемого от Board получаешь задаром.

class Board < Array
end

board = Board.new
board << Circle.new(...)
board << Line.new(...)

Кстати, строка p = Point.new в твоем коде вызывает сильное подозрение что ты не все уловил. Если ты так попытался объявить тип переменной p то в Ruby этого делать не нужно, т.к. в нем динамическая типизация. p = Point.new создает экземпляр класса Point и присваивает его переменной p. Советую тебе еще дня 2-3 усиленно читать мануалы.

tarc
()
Ответ на: комментарий от paxac
(defgeneric intersection (figure-a figure-b)
  (:documentation: "Kiss me I am generic!"))

(defmethod intersection ((figure-a line) (figure-b line))
  ;; Тут мы находим пересечение линии с линией...
  )

(defmethod intersection ((figure-a line) (figure-b circle))
  ;; Тут мы находим пересечение линии с окружностью, офигеть!
  )

(defmethod intersection ((figure-a line) (figure-b triangle))
  ;; ...а теперь - линию с треугольником!!11адин
  )

Иными словами, на CL объявляем набор классов (line, circle, triangle и т.д.), объявляем обобщённую функцию intersection от двух аргументов-фигур, и специализируем её по типам аргументов разными частными случаями (линия с линией, линия с окружностью, линия с треугольником [или в общем случаем с n-угольником], и так далее). Для каждой специализации - свой код и своя логика, естественно.

Красота.

yoghurt

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

[quote] when [Line, Circle] then line_n_circle(obj1, obj2) when [Circle, Line] then line_n_circle(obj2, obj1) when [Line, Line] then line_n_line(obj1, obj2) [/quote]

Ололо, вот это говно так говно!

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

when [Line, Circle] then line_n_circle(obj1, obj2)
when [Circle, Line] then line_n_circle(obj2, obj1)
when [Line, Line] then line_n_line(obj1, obj2)

Ололо, вот это говно так говно!

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

Для тонких ценителей можно сделать неявную диспетчеризацию

class Intersector
  def self.intersect(obj1, obj2)
    part1, part2 = [obj1, obj2].map {|obj| obj.class.name.downcase}
    name1, name2 = "#{part1}_n_#{part2}", "#{part2}_n_#{part1}"
    return send(name1, obj1, obj2) if respond_to?(name1)
    send(name2, obj2, obj1)
  end

  def self.line_n_circle(line, circle)
    # ...
  end

  def self.line_n_line(line1, line2)
    # ...
  end
end

Кстати сорри, во втором примере немного накосячил, line_n_line и line_n_circle так же должны быть методами класса. Пора спать.

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

вот это уже интереснее, но прозреваю что дико медленнее

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

Большое спасибо. Много подчеркнул из вашего сообщения.

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

И ещё один вопрос: как правильнее организовать множественные конструкторы. Например прямую можно задать 1) угловым коэффициентом и константной добавкой (а-ля y=kx+b). Кстати, собираюсь внутри класса «хранить» параметры прямой именно таким способом: все остальные способы задания переводяться в этот. 2) по двум точкам (class Point) 3) параллельно другой прямой (class Line), на определенном расстоянии 4) перпендикулярно другой прямой и пересекающее в определенной точке и т.д.

Можно ли создавать объекты не через new (initialize), а через собственные методы? Типа

line1 = Line.new_kb(k,b)
line2 = Line.new_points(point1,point2)
line3 = Line.new_perp(line1,point3)

И ещё один вопрос: в процессе работы возможно деление на 0. Можно ли ввести собственный объект Infinity, чтобы обрабатывать это самому. Напр, если прямая совпадает с осью ординат, то у неё будет k=Infinity, b=0. Мне не нужно, чтобы ruby вызывал прерывание, мол делить на ноль нельзя.

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

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

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

Можно проверять тип параметров через is_a? и т.п.

Или указывать напрямую тип метода, передавая параметры в хэше.

Например,

class Line
    def initialize(params, type)
        case type
            when :kxb
                # тут что-то делать. В данном случае с params[:k] и params[:b]
        end
        self
    end
end
a = Line.new({ :k => 10, :b => 5 }, :kxb)

Deleted
()

можно еще манкейпатч, но осторожно.

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

прозреваю что дико медленно

Нет. object.method, object.respond_to?(«method») и object.send(«method») делают один и тот же лукап. Кроме того никто не запрещает тебе находить соответствие (class1, class2) -> method для каждой пары классов только один раз, а результаты кешировать.

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

class Line
  def initialize(k, b)
    ...
  end

  def from_kb(k, b)
    new(k, b)
  end
  
  def from_points(p1, p2)
    k = ...
    b = ...
    new(k, b)
  end
end

Мне не нужно, чтобы ruby вызывал прерывание, мол делить на ноль нельзя.

В Ruby уже есть Infinity, но только для вещественных чисел, то есть 1 / 0.0 возвращает Infinity, а 1 / 0 выдает ZeroDivisionError. Но можно сделать так:

Infinity = 1 / 0.0

def observe_inf
    yield
rescue ZeroDivisionError
    Infinity
end

p 1 + observe_inf { 1 / 1 } # => 2
p 1 + observe_inf { 1 / 0 } # => Infinity

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

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

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

>Про мультиметоды не слушай (особенно лисперов) их сюда пока тащить не надо

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

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

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

+ я ещё не говорю о :before, :after, :around-специализациях. И про комбинаторы методов тоже не говорю. В общем, руби в этом плане воощбе сосёт.

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

Таки наверно лучше:

class Line 
  def initialize(k, b) 
    ... 
  end 
 
  def self.from_kb(k, b) 
    new(k, b) 
  end 
   
  def self.from_points(p1, p2) 
    k = ... 
    b = ... 
    new(k, b) 
  end 
end 
anonymous
()
Ответ на: комментарий от anonymous

> костыль

Не костыль, а работающее решение. Лисперы, кстати, еще ни одного решения не представили - была попытка, но получилось абсолютно не про то.

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

[i]>Лисперы, кстати, еще ни одного решения не представили[/i]

[url=http://www.linux.org.ru/jump-message.jsp?msgid=5381390&cid=5381641]Смотри внимательней[/url]

[i]>но получилось абсолютно не про то.[/i]

А про что же? Поведай нам, болезным, если такой умный.

anonymous
()

Спасибо всем! ----------

Я вот думаю, может попробовать ещё на хаскелле попробывать реализовать. Знаю я его не очень хорошо, но и руби не лучше ;) У меня только один вопрос: можно ли организовать «перегрузку» функций на хаскелле.

type Line = (Float, Float)
type Float = (Float, Float)
-- ...
line_by_kb :: Float -> Float -> Line
-- ...
-- функция пересечения
cap_lines :: Line -> Line -> [Points]
cap_circle_line :: Line -> Circle -> [Points]
-- ...

-- делаем прямую y=kx+b
line1 = line_by_kb 1.0 0.0
-- делаем перп. ей прямую, общая точка -- (1,1)
line2 = line_by_perp line1 (point 1.0 1.0)
-- делаем окружность
circ = circle (point 1.0 2.0) 5.0


ps = circ `cap_circle_line` line1
qs = line1 `cap_lines` line2

Вот как сделать, чтобы вместо множества функций cap_* была одна cap, которая определяла параметры и делала нужное.

Извините, если что-то глупое спрашиваю.

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

Вторая строка:

type Point = (Float, Float)  

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

Тебе надо использовать ADT. Будет примерно так:

data Point = Point Float Float
data Figure = Line Float Float
    | Circle Point Float
data Intersection = OnePoint Point
    | TwoPoints Point Point

intersect :: Figure -> Figure -> Intersection
intersect f1 f2 = intersect' True f1 f2

intersect' :: Bool -> Figure -> Figure -> Intersection
intersect' _ (Line p1 p2) (Line p3 p4) = OnePoint ...
intersect' _ (Circle p1 r1) (Circle p2 r2) = TwoPoints ...
intersect' _ (Line p1 p2) (Circle p3 r2) = TwoPoints ...
intersect' False _ _ = error "не нашли подходящую реализацию intersect'"
intersect' True f1 f2 = intersect' False f2 f1

intersect (Circle (Point 3 4) 5) (Line 1 2)
intersect (Line 1 2) (Line 1 2)

Но вообще я бы писал на Ruby и не мучался.

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

> А про что же? Поведай нам, болезным, если такой умный.

А если функция intersection будет вызвана не так (intersection line circle) а так (intersection circle line)?

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

А где можнол прочитать про это ADT?

И чем лучше

data Point = Point Float Float 
По сравнению с
type Point = (Float, Float)   
?

P.S. OnePoint, TwoPoint — не очень хорошо. Лучше всегда возвращать список. Если пересечеий нет, то []. Даже с прямыми возможны 3 случая: одна общая точка, ни одной, либо бесконечно (это я даже не знаю, как в хаскелле определить). А с пересечением сложных фигур еще запутанней, поэтому, имхо, список точек луче.

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

А где можнол прочитать про это ADT?

В любом учебнике про Хаскель.

И чем лучше

Помогает компилятору правильно выводить типы.

Лучше всегда возвращать список

Если прямые совпадают то ты собираешься возвращать список всех общих точек? Лол. Вообще не знаю какие пересечения ты собираешься вычислять и что с ними потом делать, но такое определение тебе наверно подойдет:

data Intersection = Finite [Point] | Infinite

tarc
()
Ответ на: комментарий от tarc
такое определение тебе наверно подойдет:

Только этот Infinite никакой информации не несёт. В идеальном случае, если точек беск. много, то он должен возвратить фигуру, которую эти точки образуют. Напр. пересечение двух равных прямых должно возвратить такую же фигуру типа Line. Пересечение, к примеру, двух окружностей возвратит либо пустой список, либо список с 1 или 2 фигурами типа Point, либо ту же окружность, если они равны.

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

И что? Предлагаешь мне написать твою программу за тебя? Сначала пересечением двух фигур у тебя была точка, потом список, теперь фигура. Сначала определись уже с тем что хочешь получить, а только потом садись писать программу.

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

Предлагаешь мне написать твою программу за тебя?

Нет, просто хотел прокунсультироваться. Например, если бы я узнал, что в хаскелле невозможна «перегрузка» (функция с разными возможными параметрами и одним именем), то смысла в чтении полного учебника нет — я бы просто убил время. Т. е. все вопросы направлены только на то, чтобы узнать возможности хаскелля. Если их хватит, возьмусь читать полностью Real World Haskell.

Сначала определись уже с тем что хочешь получить

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

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

...[либо в виде] уже сущесвтующих фигур (в крайнем случае списка из них).

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