Русский
Русский
English
Статистика
Реклама

Перевод Программирование только классами

image


В моем посте 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  endend

Метод if_branching основа нашей логической системы. Как видите, мы сразу же предполагаем, что любой объект правдив, так что мы возвращаем then_val.


Что насчет лжи? Давайте начнем с null:


class NullObject < BaseObject  def if_branching(_then_val, else_val)    else_val  endend

То же самое, но возвращаем второй параметр.


В Ruby практически все классы наследуются от класса Object. Но на самом деле есть другой класс по имени BasicObject, который находится даже выше в иерархии. Давайте сымитируем этот стиль и создадим наш собственный Object:


class NormalObject < BaseObjectend

Все, что мы определим позже должно быть унаследовано от 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  endend

И все! Я серьезно. Оно просто работает.


Гляньте вот этот пример:


class Fries < NormalObjectendclass Ketchup < NormalObjectendclass BurgerMeal < NormalObject  def initialize(fries = NullObject.new)    @fries = fries  end  def sauce    If.new(@fries, Ketchup.new).result  endendBurgerMeal.new.sauce # ==> NullObjectBurgerMeal.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  endendclass OrderTea  def call    # do something else  endendIf.new(today_is_friday?, OrderBeers.new, OrderTea.new)  .result  .call

Собственно, код не будет исполнен, пока мы явно не вызовем метод #call. Вот и все. Таким образом мы можем комбинировать сложную логику и наш класс If.


Булевы типы (просто потому, что мы можем)


У нас уже есть логические типы (null и все остальное), но было бы неплохо добавить специальные булевы классы для выразительности. Приступим:


class Bool < NormalObject; endclass TrueObject < Bool; endclass FalseObject < Bool  def if_branching(_then_val, else_val)    else_val  endend

Мы определили собирательный класс 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)  endend

Оно всего-лишь "переворачивает" аргументы для #if_branching. Просто, но очень полезно.


Циклы


Окей, другая важная вещь в языках программирования циклы. Мы можем добиться цикличности с помощью рекурсии. Но давайте напишем специальный оператор While.


В целом он выглядит так:


while some_condition  do_somethingend

Что может быть описано вот так: "если условие выполнено, то сделай вот это и повтори цикл".


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


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  endend

Программа для примера


Давайте создадим связные списки и функцию, которая считает сколько в списке null-объектов.


Список


Ничего особенного:


class List < NormalObject  def initialize(head, tail = NullObject.new)    @head = head    @tail = tail  end  def head    @head  end  def tail    @tail  endend

Еще нам нужно как-то его обходить (никаких #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  endend

Думаю, основная логика вполне проста. Нам также понадобились вспомогательные процедуры для #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)  endend

У нас пока нет чисел и я решил не тратить время на их создание. Вместо этого я использовал списки (гляньте мой пост про создание чисел здесь).


Интересная штука здесь метод #inc_callable. Мне кажется, если бы мы хотели разработать наш собственный "язык" со всеми этими базовыми классами, то это могло быть принятым соглашанием добавлять методы, оканчивающиеся на _callable и возвращающие процедуру. Это что-то вроде передачи функций в качестве аргументов в функциональном программировании.


Считаем null в списках


Для начала нам нужна проверка на null. Мы можем добавить ее в NormalObject и NullObject как вспомогательный метод #null? (схожий с #nil? из Ruby):


class NormalObject < BaseObject  def null?    FalseObject.new  endendclass NullObject < BaseObject  def null?    TrueObject.new  endend

Ну а теперь мы можем определить наш 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  endend

Вот и все. Мы можем скормить ему любой список и он подсчитает количество null-объектов в нем.


Заключение


Объектно-ориентированное программирование очень интересный концепт и, видимо, очень мощный. Мы, по сути, создали язык программирования (!), используя только лишь чистое ООП без каких-либо дополнительных операторов. Все, что нам нужно было: способ создать класс и переменные. Другая прикольная фишка у нас нет никаких примитивов в нашем языке (например, у нас нет null, вместо этого мы просто создаем экземплярNullObject). О, чудеса программирования...


Код можно найти в моем репозитории experiments.

Источник: habr.com
К списку статей
Опубликовано: 18.09.2020 12:15:01
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Ruby

Ненормальное программирование

Программирование

Ооп

Ооп головного мозга

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru