[Ненормальное программирование, Ruby, Программирование] Программирование только классами (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В моем посте Implementing numbers in "pure" Ruby ("Разрабатываем числа на "чистом" Ruby") я обозначил рамки, которые разрешали использовал базовые вещи из Ruby вроде оператора равенства, true/false, nil, блоки и т.п.
Но что, если бы у нас вообще ничего не было? Даже базовых операторов вроде if и while? Приготовьтесь к порции чистого объектно-ориентированного безумия.
Рамки
- Можем определять классы и методы
- Мы должны писать код так, будто в Ruby нет никаких готов классов из коробки. Просто представьте, что мы начинаем с абсолютного нуля. Даже nil не существует
- Единственный оператор, который мы можем использовать — присваивание (x = something).
Никакого if-оператора? Серьезно? Он есть даже у процессоров!
Условные операторы важны — они являются основой логики для наших программ. Как же справляться без них? Я придумал такое решение: мы можем интегрировать логику во ВСЕ объекты
Сами подумайте, в динамических языках вроде Ruby логические выражения не обязательно должны вычисляться в какой-нибудь класс вроде "Boolean". Вместо этого, эти языки считают любой объект правдимым кроме некоторых особых случаев (nil и false в Ruby;
false, 0 и '' в JS). Именно поэтому добавление этого функционала не так уж дико, как кажется на первый взгляд. Но давайте начнем.
Базовые классы
Давайте создадим самый базовый класс, который будет предком всего остального:
class BaseObject
def if_branching(then_val, _else_val)
then_val
end
end
Метод if_branching — основа нашей логической системы. Как видите, мы сразу же предполагаем, что любой объект правдив, так что мы возвращаем then_val.
Что насчет лжи? Давайте начнем с null:
class NullObject < BaseObject
def if_branching(_then_val, else_val)
else_val
end
end
То же самое, но возвращаем второй параметр.
В Ruby практически все классы наследуются от класса Object. Но на самом деле есть другой класс по имени BasicObject, который находится даже выше в иерархии. Давайте сымитируем этот стиль и создадим наш собственный Object:
class NormalObject < BaseObject
end
Все, что мы определим позже должно быть унаследовано от NormalObject. Потом мы можем добавить в него глобальные вспомогательные методы (вроде #null?).
If-выражения
Всего этого уже достаточно для того, чтобы определить наши if-выражения:
class If < NormalObject
def initialize(bool, then_val, else_val = NullObject.new)
@result = bool.if_branching(then_val, else_val)
end
def result
@result
end
end
И все! Я серьезно. Оно просто работает.
Гляньте вот этот пример:
class Fries < NormalObject
end
class Ketchup < NormalObject
end
class BurgerMeal < NormalObject
def initialize(fries = NullObject.new)
@fries = fries
end
def sauce
If.new(@fries, Ketchup.new).result
end
end
BurgerMeal.new.sauce # ==> NullObject
BurgerMeal.new(Fries.new).sauce # ==> Ketchup
Возможно, вы уже думаете: "каким боком нам это полезно, если мы не можем использовать с ним блоки кода?". И что насчет "ленивости"?
Ознакомьтесь с этим примером:
# Псевдокод
if today_is_friday?
order_beers()
else
order_tea()
end
# Наш If класс
If.new(today_is_friday?, order_beers(), order_tea()).result
В нашем примере мы закажем пиво ВМЕСТЕ с чаем вне зависимости от дня недели. Это происходит из-за того, что аргументы вычисляются до передачи в конструктор.
И это (ленивость) очень важный механизм, т.к. без него наши программы были бы медленные и даже неправильные.
Решением этого является просто оборачивание кода в другой класс. Позже я буду называть такие обертки процедурами (ориг. "callable"):
class OrderBeers
def call
# do something
end
end
class OrderTea
def call
# do something else
end
end
If.new(today_is_friday?, OrderBeers.new, OrderTea.new)
.result
.call
Собственно, код не будет исполнен, пока мы явно не вызовем метод #call. Вот и все. Таким образом мы можем комбинировать сложную логику и наш класс If.
Булевы типы (просто потому, что мы можем)
У нас уже есть логические типы (null и все остальное), но было бы неплохо добавить специальные булевы классы для выразительности. Приступим:
class Bool < NormalObject; end
class TrueObject < Bool; end
class FalseObject < Bool
def if_branching(_then_val, else_val)
else_val
end
end
Мы определили собирательный класс Bool, класс TrueObject без какой либо логики (она не нужна, т.к. любой экземпляр этого класса уже автоматически будет считаться правдивым) и класс
FalseObject, переопределяющий #if_branching так же, как и NullObject.
Вот и все. У нас есть специальные булевы классы. Я еще добавил логическое НЕ для удобства:
class BoolNot < Bool
def initialize(x)
@x = x
end
def if_branching(then_val, else_val)
@x.if_branching(else_val, then_val)
end
end
Оно всего-лишь "переворачивает" аргументы для #if_branching. Просто, но очень полезно.
Циклы
Окей, другая важная вещь в языках программирования — циклы. Мы можем добиться цикличности с помощью рекурсии. Но давайте напишем специальный оператор While.
В целом он выглядит так:
while some_condition
do_something
end
Что может быть описано вот так: "если условие выполнено, то сделай вот это и повтори цикл".
Интересная особенность в нашем случае то, что условие должно быть динамичным — оно должно быть в состоянии меняться между шагами цикла. Процедуры спешат на помощь!
class While < NormalObject
def initialize(callable_condition, callable_body)
@cond = callable_condition
@body = callable_body
end
def run
is_condition_satisfied = @cond.call
If.new(is_condition_satisfied,
NextIteration.new(self, @body),
DoNothing.new)
.result
.call
end
# Запускает "тело" и потом снова While#run.
# Таким образом цикличность определена рекурсивно
# (жаль, что хвостовая рекурсия не оптимизирована)
class NextIteration < NormalObject
def initialize(while_obj, body)
@while_obj = while_obj
@body = body
end
def call
@body.call
@while_obj.run
end
end
class DoNothing < NormalObject
def call
NullObject.new
end
end
end
Программа для примера
Давайте создадим связные списки и функцию, которая считает сколько в списке null-объектов.
Список
Ничего особенного:
class List < NormalObject
def initialize(head, tail = NullObject.new)
@head = head
@tail = tail
end
def head
@head
end
def tail
@tail
end
end
Еще нам нужно как-то его обходить (никаких #each с блоком в этот раз!). Давайте создадим класс, который будет этим заниматься:
#
# Позволяет обойти лист один раз
#
class ListWalk < NormalObject
def initialize(list)
@left = list
end
def left
@left
end
# Возвращает текущую голову и присваивает хвост к current.
# Возвращает null если конец достигнут
def next
head = If.new(left, HeadCallable.new(left), ReturnNull.new)
.result
.call
@left = If.new(left, TailCallable.new(left), ReturnNull.new)
.result
.call
head
end
def finished?
BoolNot.new(left)
end
class HeadCallable < NormalObject
def initialize(list)
@list = list
end
def call
@list.head
end
end
class TailCallable < NormalObject
def initialize(list)
@list = list
end
def call
@list.tail
end
end
class ReturnNull < NormalObject
def call
NullObject.new
end
end
end
Думаю, основная логика вполне проста. Нам также понадобились вспомогательные процедуры для #head и #tail, чтобы избежать null-pointer ошибок (даже при том, что наш null на самом деле не null, мы все равно рискуем вызвать несуществующий метод).
Счетчик
Просто объект, который будет использоваться для подсчетов:
class Counter < NormalObject
def initialize
@list = NullObject.new
end
def inc
@list = List.new(NullObject.new, @list)
end
class IncCallable < NormalObject
def initialize(counter)
@counter = counter
end
def call
@counter.inc
end
end
def inc_callable
IncCallable.new(self)
end
end
У нас пока нет чисел и я решил не тратить время на их создание. Вместо этого я использовал списки (гляньте мой пост про создание чисел здесь).
Интересная штука здесь — метод #inc_callable. Мне кажется, если бы мы хотели разработать наш собственный "язык" со всеми этими базовыми классами, то это могло быть принятым соглашанием добавлять методы, оканчивающиеся на _callable и возвращающие процедуру. Это что-то вроде передачи функций в качестве аргументов в функциональном программировании.
Считаем null в списках
Для начала нам нужна проверка на null. Мы можем добавить ее в NormalObject и NullObject как вспомогательный метод #null? (схожий с #nil? из Ruby):
class NormalObject < BaseObject
def null?
FalseObject.new
end
end
class NullObject < BaseObject
def null?
TrueObject.new
end
end
Ну а теперь мы можем определить наш null-счетчик:
#
# Возвращает счетчик, увеличенный раз за каждый NullObject в списке
#
class CountNullsInList < NormalObject
def initialize(list)
@list = list
end
def call
list_walk = ListWalk.new(@list)
counter = Counter.new
While.new(ListWalkNotFinished.new(list_walk),
LoopBody.new(list_walk, counter))
.run
counter
end
class ListWalkNotFinished < NormalObject
def initialize(list_walk)
@list_walk = list_walk
end
def call
BoolNot.new(@list_walk.finished?)
end
end
class LoopBody < NormalObject
class ReturnNull < NormalObject
def call
NullObject.new
end
end
def initialize(list_walk, counter)
@list_walk = list_walk
@counter = counter
end
def call
x = @list_walk.next
If.new(x.null?, @counter.inc_callable, ReturnNull.new)
.result
.call
end
end
end
Вот и все. Мы можем скормить ему любой список и он подсчитает количество null-объектов в нем.
Заключение
Объектно-ориентированное программирование — очень интересный концепт и, видимо, очень мощный. Мы, по сути, создали язык программирования (!), используя только лишь чистое ООП без каких-либо дополнительных операторов. Все, что нам нужно было: способ создать класс и переменные. Другая прикольная фишка — у нас нет никаких примитивов в нашем языке (например, у нас нет null, вместо этого мы просто создаем экземплярNullObject). О, чудеса программирования...
Код можно найти в моем репозитории experiments.
===========
Источник:
habr.com
===========
===========
Автор оригинала: Dmitry Non
===========Похожие новости:
- [C++, Программирование] C++20. Coroutines
- [Программирование, Конференции, Научно-популярное] Core Dump — видео канал о компьютерной науке
- [Карьера в IT-индустрии, Программирование] Украденное резюме, человек, который ушел в Кемерово, призыв кандидата и другие «увлекательные» истории трэш-собеседовани
- [Swift, Программирование, Разработка мобильных приложений, Разработка под iOS] Виджеты в iOS 14 – возможности и ограничения
- [Программирование, Разработка под iOS, Разработка мобильных приложений] Формальные грамматики на службе мобильного клиента
- [FPGA, Высокая производительность, Искусственный интеллект, Конференции, Программирование микроконтроллеров] Бывший вице-президент Sun, MIPS и DEC прокомметировал покупку компании ARM компанией NVidia
- [Java, Open source, Анализ и проектирование систем, Программирование] Добавляем ORM в проект за четыре шага
- [Алгоритмы, Программирование, Спортивное программирование, Хакатоны] Программирование как вид спорта: что делать, чтобы побеждать на соревнованиях
- [Lua, Программирование] Транслитерация русского текста для отправки сообщений в Телеграмм из Микротик РоутерОС
- [JavaScript, Node.JS, Программирование, Разработка веб-сайтов] Web Cryptography API: пример использования
Теги для поиска: #_nenormalnoe_programmirovanie (Ненормальное программирование), #_ruby, #_programmirovanie (Программирование), #_oop (ооп), #_oop_golovnogo_mozga (ооп головного мозга), #_nenormalnoe_programmirovanie (ненормальное программирование), #_ruby, #_nenormalnoe_programmirovanie (
Ненормальное программирование
), #_ruby, #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:14
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В моем посте Implementing numbers in "pure" Ruby ("Разрабатываем числа на "чистом" Ruby") я обозначил рамки, которые разрешали использовал базовые вещи из Ruby вроде оператора равенства, true/false, nil, блоки и т.п. Но что, если бы у нас вообще ничего не было? Даже базовых операторов вроде if и while? Приготовьтесь к порции чистого объектно-ориентированного безумия. Рамки
Никакого if-оператора? Серьезно? Он есть даже у процессоров! Условные операторы важны — они являются основой логики для наших программ. Как же справляться без них? Я придумал такое решение: мы можем интегрировать логику во ВСЕ объекты Сами подумайте, в динамических языках вроде Ruby логические выражения не обязательно должны вычисляться в какой-нибудь класс вроде "Boolean". Вместо этого, эти языки считают любой объект правдимым кроме некоторых особых случаев (nil и false в Ruby; false, 0 и '' в JS). Именно поэтому добавление этого функционала не так уж дико, как кажется на первый взгляд. Но давайте начнем. Базовые классы Давайте создадим самый базовый класс, который будет предком всего остального: class BaseObject
def if_branching(then_val, _else_val) then_val end end Метод if_branching — основа нашей логической системы. Как видите, мы сразу же предполагаем, что любой объект правдив, так что мы возвращаем then_val. Что насчет лжи? Давайте начнем с null: class NullObject < BaseObject
def if_branching(_then_val, else_val) else_val end end То же самое, но возвращаем второй параметр. В Ruby практически все классы наследуются от класса Object. Но на самом деле есть другой класс по имени BasicObject, который находится даже выше в иерархии. Давайте сымитируем этот стиль и создадим наш собственный Object: class NormalObject < BaseObject
end Все, что мы определим позже должно быть унаследовано от NormalObject. Потом мы можем добавить в него глобальные вспомогательные методы (вроде #null?). If-выражения Всего этого уже достаточно для того, чтобы определить наши if-выражения: class If < NormalObject
def initialize(bool, then_val, else_val = NullObject.new) @result = bool.if_branching(then_val, else_val) end def result @result end end И все! Я серьезно. Оно просто работает. Гляньте вот этот пример: class Fries < NormalObject
end class Ketchup < NormalObject end class BurgerMeal < NormalObject def initialize(fries = NullObject.new) @fries = fries end def sauce If.new(@fries, Ketchup.new).result end end BurgerMeal.new.sauce # ==> NullObject BurgerMeal.new(Fries.new).sauce # ==> Ketchup Возможно, вы уже думаете: "каким боком нам это полезно, если мы не можем использовать с ним блоки кода?". И что насчет "ленивости"? Ознакомьтесь с этим примером: # Псевдокод
if today_is_friday? order_beers() else order_tea() end # Наш If класс If.new(today_is_friday?, order_beers(), order_tea()).result В нашем примере мы закажем пиво ВМЕСТЕ с чаем вне зависимости от дня недели. Это происходит из-за того, что аргументы вычисляются до передачи в конструктор. И это (ленивость) очень важный механизм, т.к. без него наши программы были бы медленные и даже неправильные. Решением этого является просто оборачивание кода в другой класс. Позже я буду называть такие обертки процедурами (ориг. "callable"): class OrderBeers
def call # do something end end class OrderTea def call # do something else end end If.new(today_is_friday?, OrderBeers.new, OrderTea.new) .result .call Собственно, код не будет исполнен, пока мы явно не вызовем метод #call. Вот и все. Таким образом мы можем комбинировать сложную логику и наш класс If. Булевы типы (просто потому, что мы можем) У нас уже есть логические типы (null и все остальное), но было бы неплохо добавить специальные булевы классы для выразительности. Приступим: class Bool < NormalObject; end
class TrueObject < Bool; end class FalseObject < Bool def if_branching(_then_val, else_val) else_val end end Мы определили собирательный класс Bool, класс TrueObject без какой либо логики (она не нужна, т.к. любой экземпляр этого класса уже автоматически будет считаться правдивым) и класс FalseObject, переопределяющий #if_branching так же, как и NullObject. Вот и все. У нас есть специальные булевы классы. Я еще добавил логическое НЕ для удобства: class BoolNot < Bool
def initialize(x) @x = x end def if_branching(then_val, else_val) @x.if_branching(else_val, then_val) end end Оно всего-лишь "переворачивает" аргументы для #if_branching. Просто, но очень полезно. Циклы Окей, другая важная вещь в языках программирования — циклы. Мы можем добиться цикличности с помощью рекурсии. Но давайте напишем специальный оператор While. В целом он выглядит так: while some_condition
do_something end Что может быть описано вот так: "если условие выполнено, то сделай вот это и повтори цикл". Интересная особенность в нашем случае то, что условие должно быть динамичным — оно должно быть в состоянии меняться между шагами цикла. Процедуры спешат на помощь! class While < NormalObject
def initialize(callable_condition, callable_body) @cond = callable_condition @body = callable_body end def run is_condition_satisfied = @cond.call If.new(is_condition_satisfied, NextIteration.new(self, @body), DoNothing.new) .result .call end # Запускает "тело" и потом снова While#run. # Таким образом цикличность определена рекурсивно # (жаль, что хвостовая рекурсия не оптимизирована) class NextIteration < NormalObject def initialize(while_obj, body) @while_obj = while_obj @body = body end def call @body.call @while_obj.run end end class DoNothing < NormalObject def call NullObject.new end end end Программа для примера Давайте создадим связные списки и функцию, которая считает сколько в списке null-объектов. Список Ничего особенного: class List < NormalObject
def initialize(head, tail = NullObject.new) @head = head @tail = tail end def head @head end def tail @tail end end Еще нам нужно как-то его обходить (никаких #each с блоком в этот раз!). Давайте создадим класс, который будет этим заниматься: #
# Позволяет обойти лист один раз # class ListWalk < NormalObject def initialize(list) @left = list end def left @left end # Возвращает текущую голову и присваивает хвост к current. # Возвращает null если конец достигнут def next head = If.new(left, HeadCallable.new(left), ReturnNull.new) .result .call @left = If.new(left, TailCallable.new(left), ReturnNull.new) .result .call head end def finished? BoolNot.new(left) end class HeadCallable < NormalObject def initialize(list) @list = list end def call @list.head end end class TailCallable < NormalObject def initialize(list) @list = list end def call @list.tail end end class ReturnNull < NormalObject def call NullObject.new end end end Думаю, основная логика вполне проста. Нам также понадобились вспомогательные процедуры для #head и #tail, чтобы избежать null-pointer ошибок (даже при том, что наш null на самом деле не null, мы все равно рискуем вызвать несуществующий метод). Счетчик Просто объект, который будет использоваться для подсчетов: class Counter < NormalObject
def initialize @list = NullObject.new end def inc @list = List.new(NullObject.new, @list) end class IncCallable < NormalObject def initialize(counter) @counter = counter end def call @counter.inc end end def inc_callable IncCallable.new(self) end end У нас пока нет чисел и я решил не тратить время на их создание. Вместо этого я использовал списки (гляньте мой пост про создание чисел здесь). Интересная штука здесь — метод #inc_callable. Мне кажется, если бы мы хотели разработать наш собственный "язык" со всеми этими базовыми классами, то это могло быть принятым соглашанием добавлять методы, оканчивающиеся на _callable и возвращающие процедуру. Это что-то вроде передачи функций в качестве аргументов в функциональном программировании. Считаем null в списках Для начала нам нужна проверка на null. Мы можем добавить ее в NormalObject и NullObject как вспомогательный метод #null? (схожий с #nil? из Ruby): class NormalObject < BaseObject
def null? FalseObject.new end end class NullObject < BaseObject def null? TrueObject.new end end Ну а теперь мы можем определить наш null-счетчик: #
# Возвращает счетчик, увеличенный раз за каждый NullObject в списке # class CountNullsInList < NormalObject def initialize(list) @list = list end def call list_walk = ListWalk.new(@list) counter = Counter.new While.new(ListWalkNotFinished.new(list_walk), LoopBody.new(list_walk, counter)) .run counter end class ListWalkNotFinished < NormalObject def initialize(list_walk) @list_walk = list_walk end def call BoolNot.new(@list_walk.finished?) end end class LoopBody < NormalObject class ReturnNull < NormalObject def call NullObject.new end end def initialize(list_walk, counter) @list_walk = list_walk @counter = counter end def call x = @list_walk.next If.new(x.null?, @counter.inc_callable, ReturnNull.new) .result .call end end end Вот и все. Мы можем скормить ему любой список и он подсчитает количество null-объектов в нем. Заключение Объектно-ориентированное программирование — очень интересный концепт и, видимо, очень мощный. Мы, по сути, создали язык программирования (!), используя только лишь чистое ООП без каких-либо дополнительных операторов. Все, что нам нужно было: способ создать класс и переменные. Другая прикольная фишка — у нас нет никаких примитивов в нашем языке (например, у нас нет null, вместо этого мы просто создаем экземплярNullObject). О, чудеса программирования... Код можно найти в моем репозитории experiments. =========== Источник: habr.com =========== =========== Автор оригинала: Dmitry Non ===========Похожие новости:
Ненормальное программирование ), #_ruby, #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:14
Часовой пояс: UTC + 5