Как стать автором
Обновить
12
0
Павел @ppskriptum

Любитель интересных ЯП

Отправить сообщение

Ещё насчёт реализации в статически типизированных языках — мне подсказали, что, считай, ровно такой же механизм в плюсах называется Policy, и был как следует проработан Андреем Александреску (Modern C++ Design, 1 глава).


Если кратко, Policy — это класс с шаблонной базой.

Да, в TS само собой будет косвенная рекурсия. Это пример на выдуманном языке, построенном на миксинах.


Смысл как раз в том, чтобы для this внутри BaseImpl и внутри Derived были разные методы b. Знаю, это не совсем late-bound this, но смысл как раз в том, чтобы он был late-bound только для методов, указанных в Base. Потому я и говорю, что в таком случае мне не нравится, что интерфейс начнёт влиять на рантайм.

О, насчёт перетирания можно ещё сделать так. Рассмотрим следующий пример:


interface Base {
    a(): void
}

class BaseImpl implements Base {
    a(): void {
        b()
    }

    b(): void {
        log('BaseImpl')
    }
}

class Derived extends Base {
    b() {
        a()
        log('Derived')
    }
}

Если мы выполним (new (Derived(Base))()).b(), это должно вывести сначала BaseImpl, а потом Derived. То есть, для перегрузки недоступны методы, которые не указаны в интерфейсе базового класса.


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


interface IBaseImpl {
    a(): void
    b(): void
}

И объекты из Derived(Base) ему соответствуют, просто метод b уже не тот.


Что мне не нравится в этом решении — что интерфейс уже не является просто шаблоном для проверки структуры объекта, он влияет на резолв перегрузки. Возможно, тогда стоит разделить две сущности: интерфейсы как шаблоны объектов и интерфейсы как фильтр для базового класса?

  1. В TS запрещено, но ничего не мешает нам придумать такой язык, в котором можно) Окей, здесь сложно. Нужно каким-то образом заводить локальные скоупы имён для каждого миксина в цепочке, чтобы при этом late-bound this всё ещё работало. Что-то похожее на open/override из Kotlin должно помочь.
  2. То, перетирают ли они методы друг друга, зависит от порядка их применения. Если это нежелательно, нужно придумать какой-нибудь механизм явного disambiguation. Вообще это похоже на проблему с коллизией параметров конструктора.
Как Вы собираетесь сохранять стейтмент дерайвед класса во внешнем контексте?

Что вы имеете в виду?


В случае, если целью LBC является расширение типов

Оригинальный тип, который был передан в LBC, не меняется, просто создаётся новый тип, наследующийся от переданного. Так что я не совсем понимаю, как это может вызвать те же проблемы, которые вызывают Ruby-миксины. Разве что вы о том, что мы не можем знать заранее, какие ещё методы есть в конкретном базовом классе кроме заявленных в интерфейсе. Но это можно решить с помощью приватного наследования с явным реэкспортом имён, как в плюсах.


Как Вы технически собираетесь реализовать динамическую расширяемость типов при строгой типизации в языке со статической проверкой типов?

Так динамической расширяемости, кажется, и нет. После применения LBC B к классу A получается класс, объекты которого имеют известный тип: A & B (либо B, если применять приватное наследование).


Что тогда должна возвращать Extends, в случае несоответствия интерфейсов?

Ошибку компиляции? Не понял вопроса.

Да, действительно, это TS-овские миксины и есть. В JS (ES2015 и позже) есть классы, так что там они будут выглядеть точно так же. Напишу об этом в самом начале, спасибо.


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

Да, точно. Сейчас поправлю, спасибо

Окей, тогда в последующих статьях буду искать примеры получше + мотивировать выбор паттерна, спасибо за совет!

Окей, тогда в последующих статьях буду искать примеры получше + мотивировать выбор паттерна, спасибо за совет!


Upd. Ответил немного не туда, но вам тоже спасибо! За столь долгое и продуктивное участие в дискуссии.

контр пример на плюсах для раста не очень убедителен

А что изменилось бы, если бы тот код писался на Расте?


И, кажется, мы в этом треде больше говорим не про смысл использования паттерна State в Rust, а про смысл использования этого паттерна вообще, потому что я не вижу, в чем растовские перечисления лучше перечислений в C++/Java/… с точки зрения реализации конечных автоматов.
Но это уже не является темой данной статьи.


UPD. Ладно, насчёт перечислений в Расте я не совсем прав, потому что хранение данных внутри перечисления упрощает моделирование. Пример можно посмотреть здесь. Но, опять же, цель этой статьи в том, чтобы показать, как реализовывать паттерн State в Rust, а не как писать State Machines на Rust.

Ничего не мешает, но так будет сложнее найти места, где из-за добавления нового состояния нужны какие-то осмысленные изменения: если в match нет такого catch-all, он просто не скомпилируется. Это, собственно, одна из причин, по которой в Rust матчи exhaustive.

Чем это лучше enum?

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


  1. при использовании enum добавление одного нового состояния изменяет весь код, где используется наш enum. Хотя бы потому, что match должен быть exhaustive, а одними if let не везде можно обойтись
  2. с использованием паттерна код, исполняющийся при каком-то конкретном состоянии, сосредоточен в одном месте. Без него, наоборот, в одном месте сосредоточен код, исполняющийся при обработке какого-то конкретного сообщения (/ вызове метода). Как правило, этот код занимает больше места. Возможность хранить данные внутри перечисления упрощает ситуацию, но сам match на все, например, 600 состояний никуда не денется.

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

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

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность