Аниме и IT


Гео и язык канала: не указан, не указан
Категория: не указана


Рассуждения, заметки по прочитанной профессиональной IT литературе. Мнение об аниме. И еще всякая фигня.
Я: @dielone

Связанные каналы

Гео и язык канала
не указан, не указан
Категория
не указана
Статистика
Фильтр публикаций


Скоро открывается новая сессия курса по алгоритмам от Принстонского университета на Coursera. Курс состоит из двух частей: первая охватывает элементарные структуры данных, сортировку и алгоритмы поиска, вторая часть сфокусирована на алгоритмах построения графов и строк. В сумме обучение займет 3 месяца, нагрузка в неделю 6-10 часов. Обучение бесплатное.

https://www.coursera.org/learn/algorithms-part1/
https://www.coursera.org/learn/algorithms-part2/


Дошел до главы про метапрограммирование в книге Мацумото по Ruby, поэтому вижу смысл сразу перейти к Metaprogramming Ruby 2 за авторством Paolo Perrotta.
В предыдущей же главе описывалась работа с классами и модулями, но не так подробно, как хотелось бы.
Остается недосказанность в работе методов include и extend, не до конца понятно, что из себя представляет метакласс.

Нашел две классные статьи, в которых всё это и объясняется. В первой подробно расписано о пути поиска метода, даётся нормальное определение метаклассу, как происходит наследование классов. Вторая статья проливает свет на работу include и extend.

Объяснить лучше, чем в этих статьях будет очень сложно, хуже – запросто. Поэтому вот ссылки на материал:
https://habrahabr.ru/post/143990/
https://habrahabr.ru/post/143483/

PS: книжку The Ruby Programming Language все же стоит прочитать, но обязательно в оригинале, так как перевод на русский такой себе.


Немного про итераторы (внешние и внутренние) и перечислители в руби. Википедия говорит, что итератор – это интерфейс, предоставляющий доступ к элементам коллекции (массива или контейнера) и навигацию по ним.
В Ruby итераторы – это просто доступные коллекции методы, которые могут взаимодействовать с блоком кода. Эта возможность достигается с помощью инструкции yield.

Итератор в данном случае не подразумевает обязательное наличие итерации, что может сбивать с толку.
К примеру, существует метод tap в классе Object. Когда мы вызываем tap у какого-то объекта, мы один раз передаем этот объект в блок, который впоследствии и вернет обратно объект. Таким образом, этот метод позволяет нам вклиниваться в цепочку методов. В виде кода будет понятней:
"Hello world".tap { |x| puts x }
.each_char.tap { |x| puts x.inspect }

Hello world
#

Заметим, что метод each_char нам возвращает Enumerator (перечислитель).

Что такое перечислитель? Это Enumerable-объект, предназначенный для перечисления другого объекта. Мы можем воспользоваться этим новым объектом в качестве прокси, чтобы не менять оригинальный. Чтобы создать нумератор, достаточно вызвать у объекта метод to_enum.
Зачастую нет необходимости явно вызывать этот метод, так как встроенные итераторы автоматически возвращают Enumerator, когда вызываются без блока (как в примере выше с each_char ).
Мы можем и сами определить такое поведение, если захотим написать собственный итератор:

def twice
if block_given?
yield
yield
else
self.to_enum(:twice)
end
end


Перечислители нашли также применение в качестве внешних итераторов. Мы можем последовательно перебирать элементы коллекции с помощью метода next, когда элементы в коллекции кончаются, метод выдает исключение StopIteration.
Когда итерация управляет клиентом путем вызова метода next, итератор называется внешним, а когда она управляется клиентом – внутренним.
Внешние итераторы решают проблему параллельной итерации и вообще они сильно гибче внутренних.

Пример метода, в котором мы осуществляем итерацию коллекций и передаем по одному значению из каждой коллекции (аналог Enumerable.zip ).


def bundle(*enumerables)
enumerators = enumerables.map { |e| e.to_enum }
loop { yield enumerators.map { |e| e.next } }
end

a,b,c = [1,2,3], [4,5,6], [7,8,9]
bundle(a,b,c) { |x| print x }
[1, 4, 7][2, 5, 8][3, 6, 9]=> [1, 2, 3]


Теперь попробуем выполнить следующий код:
range = 1..Float::INFINITY
print range.collect { |x| x*x }.first(5)

Мы получаем бесконечный цикл, вызов метода first никогда не произойдет. У руби есть Enumerable#lazy метод, который позволяет обойти бесконечный цикл.

p range.lazy.collect { |x| x * x }.first(5)
[1, 4, 9, 16, 25]


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

https://github.com/ruslanshakirov/roadmap


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

Гомоиконность – свойство языков программирования, чья реализация основана на собственной структуре данных. Это свойство позволяет писать язык на самом языке. Когда мы говорим, что язык гомоиконный, мы подразумеваем, что код имеет такую же структуру, как его абстрактное синтаксическое дерево (AST). Примером такого языка является Scheme или Clojure (диалекты Лиспа).

Каждый язык программирования преобразовывает исходный код в нечто, что может быть скомпилировано или выполнено. Большинство языков преобразовывает код в AST. Но так как мы пишем код на Clojure через структуры данных, он уже сам по себе является AST, поэтому нет необходимости в его трансформации. Становится очевидно, что это свойство делает метапрограммирование проще.


Метапрограммирование – довольно интересный для меня вид программирования, так как он часто используется в Ruby, в языке, на котором я активно пишу уже несколько лет. До этого момента я немного сторонился этой темы, знал о нём очень поверхностно. Но думаю, что пришло время разобраться в магии Ruby и Ruby on Rails.

Что такое метапрограммирование и почему это очень мощная штука?
Под метапрограммированием понимается написание кода, который манипулирует языковыми конструкциями. Иначе говоря, мы пишем код, который пишет код.
Мы можем написать код на языке Java, используя аннотации, и после этого воспользоваться генератором кода для получения конфигурационных файлов xml. В широком смысле это ведь и есть метапрограммирование, но не совсем то, о котором я писал выше. Назовем этот вид метапрограммирования статическим, тогда как метапрограммирования на Ruby динамическим, так как код на Ruby манипулирует языковыми конструкциями в рантайме.

Пример:

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


class Entity
attr_reader :table, :id

def initialize(table, id)
@table = table
@id = id
Database.sql "INSERT INTO #{@table} (id) VALUES (#{@id})"
end

def set(col, val)
Database.sql "UPDATE #{@table} SET #{col}='#{val}' WHERE id=#{@id}"
end

def get(col)
Database.sql ("SELECT #{col} FROM #{@table} WHERE id=#{@id}")[0][0]
end
end


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

class Movie < Entity
def initialize(id)
super "movies", id
end

def title
get "title"
end

def title=(value)
set "title", value
end

def director
get "director"
end

def director=(value)
set "director", value
end
end

Таким образом мы можем взаимодействовать с любой таблицей.

Для каждой таблицы мы должны:
1. Создать класс для взаимодействия с таблицей, наследоваться от Entity.
2. Вызвать конструктор родительского класса, передав название таблицы и id.
3. Для каждого столбца в таблице написать геттер и сеттер, использующий методы родительского класса.

Но вот это всё плохо, очень плохо. Много дублирования кода!

Мы можем решить эту задачу, например, воспользовавшись гемом ActiveRecord (который крайне часто используются во фреймворке Ruby on Rails ). Как взаимодействовать с бд теперь?


class Movie < ActiveRecord::Base
end


Этого достаточно для нашей задачи. Мы просто наследуемся от ActiveRecord::Base. Нам не нужно указывать название таблицы (но мы можем это сделать, если хотим изменить обычное поведение). Нам не нужно писать геттеры, сеттеры и многое другое.

Что делает в данном случае ActiveRecord? Он просто ищет таблицу по названию класса (во множественном числе). Также он определяет геттеры и сеттеры на основе схемы бд.
Не будем пока что вдаваться в детали реализации, самое главное сейчас понять, что нам даёт динамическое метапрограммирование. Это правда очень мощная штука.


Только сейчас заметил, что в ruby 2.5.0 был добавлен branch coverage, его очень не хватало.
Статья на тему: https://medium.com/@coorasse/the-ruby-2-5-0-feature-nobody-talks-about-38e6c4585fdd


Цитирую: "Программист должен равно стремиться к совершенству в деталях, и к соразмерности сложного целого".

Язык программирования служит средой, в которой мы организуем своё мышление о процессах. Каждый язык для этого обладает тремя механизмами, это:
1. Элементарные выражения
2. Средства комбинирования
3. Средства абстракции

От любого мощного ЯП мы ожидаем, что он будет способен работать с данными, используя для этого процедуры/функции/методы.

Немного Лиспа:
Программы, написанные на Scheme, состоят из s-выражений(Symbolic expression).
S-выражение — это либо атом, либо список.
Атомы — это простейшие объекты Лиспа, из которых строятся остальные структуры.
Список — это последовательность элементов.

Элементарные выражения могут сочетаться с процедурами
(* 2 2)
;; => 4

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


(+ 1 2 3 4 5)
;; => 15


Существуют правила форматирования кода на Лиспе. Согласно этому форматированию, каждая комбинация записывается так, чтобы её операнды выравнивались вертикально. Отступы показывают структуру выражения.

Пример без форматирования:
(+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6))

С форматированием (pretty printing):
(+ (* 3
(+ (* 2 4)
(+ 3 5)))
(+ (- 10 7)
6))


Окей, это будет своеобразный initial commit для моего канала.

Есть одна очень хорошая книга по программированию, которую я бы посоветовал изучить абсолютно каждому разработчику. Это "Structure and Interpretation of Computer Programs" или просто SICP.

Она идеально подойдет новичкам, так как особо не требует каких-либо дополнительных знаний. Преподавание проходит сквозь призму языка "Scheme". Сделано это для того, чтобы облегчить процесс обучения и дать возможность сконцентрироваться на действительно важных вещах. В книге очень много задач, которые обязательно нужно решать. Здесь дается объяснение порядков вычисления, рекурсивных и итеративных процессов, lambda-выражений, объектов, потоков, параллелизма и многого другого. Эта книга издана в 1985 году и остается актуальной по сей день.

Перед тем как ознакомиться с этой книгой, я прочитал кучу отзывов и постов. Люди писали, что она стала неким откровением для них. Безусловно, SICP принесет огромную пользу не только новичкам, но и людям с опытом. Я изучил только первую главу (всего их 5), но уже успел убедиться в пользе книги для себя, поэтому и собираюсь наверстать упущенное. Это будет третий раз, когда я сажусь за эту книгу, на этот раз собираюсь добить её до конца.

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

Полезные ссылки:
SICP: http://newstar.rinet.ru/~goga/sicp/sicp.pdf
Лекции по книге на английском: https://www.youtube.com/playlist?list=PLE18841CABEA24090
Решения задач для проверки: http://sicp.sergeykhenkin.com/category/sicp/solutions/

Показано 9 последних публикаций.

30

подписчиков
Статистика канала