Pull to refresh

Comments 10

т.к. все объекты это функции, а функции объекты

({})() // TypeError: object is not a function

ЧЯДНТ?
Спасибо за статью, занятно. Выскажу ИМХО:

Строгая типизация это, конечно, замечательно, но явная в языке, транслируемом в js… От подобного:
public MessagesLoaded: Events.Event<Events.ICallback<string[]>, string[]>
    = new Events.Event<Events.ICallback<string[]>, string[]>();
у меня волосы становятся дыбом.

Ради автодополнения городить такое слишком, когда без явной типизации аналогичный код укладывается в 8 строк:
class Event
 -> @Callbacks = []
 Add : ->
  Callback: @Callbacks[*] = it
  Event: @
  Unsubscribe: @Remove.bind @,it
 Remove : !(callback)-> @Callbacks .= filter (!=callback) 
 Trigger : !(options,context)-> for @Callbacks => ..apply context,options

Лучше я буду писать в блокноте, но во много раз меньше :) В разы меньше кода — сложнее сделать ошибку. Конечный js аналогичен — скорость соответствует. Ну а строгую типизацию проще вбить в голову, чем писать, повторюсь, в разы больше.
С одной стороны тут ваша правда есть. Меня тоже временами от подобных конструкций передергивает. С другой, никак.

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

Во-вторых, нет ничего страшного в том, чтобы один раз объявить событие. Вам же не нужно писать такой «ужас» повсеместно. Подписка и т.д. вполне в стиле JS.

В любом случае, нет идеальных решений. Любой подход реализуется в контексте проекта. В моем это нужно. В куче других нет.
Автор увлекается аннотированием, на мой взгляд, излишне. В статье почти все (!) аннотации типов можно выкинуть, *ничего* не потеряв в типизиции. TS отлично умеет выводить типы, как по инициализаторам, так и по использованию.

У в моей аналогичной библиотеке такой код выглядел бы так:
public messagesLoaded = new Event<string[]>();

При этом messagesLoaded имеет четкий тип Event<string[]> — аннотация тут просто не нужна.
я всё-таки так и не понял зачем нам 2 раза руками указывать параметр Options
export class Event<Delegate extends ICallback<Options>, Options>
чем мой вариант из прошлого поста хуже?
Честно говоря, как-то не внимательно прочитал ваш комментарий к прошлой статье и вообще упустил подобный вариант записи.

Тем не менее, отличия есть.

У вас было так:

interface IEvent<Data> { on(fn:(Data) => boolean): ISubscribe { } trigger(d: Data): boolean { } }

По сути, разницы никакой, за исключением того, что вы использовали «анонимный тип». fn:(Data) => boolean это не что иное, как вынесенный мной наружу ICallback записанный анонимно. Ваша запись короче и имеет полное право на жизнь, если мы гарантированно больше нигде не будем использовать этот тип callback'а. Если же нам надо, чтобы сигнатуры callback'ов разных событий были строго одинаковы по той или иной причине, то нам нужно их типизировать делегатом, иначе мы можем получить неявные последствия при рефакторинге в виде расхождений сигнатур, что при необязательных параметрах или параметрах типа any может иметь сложноуловимые последствия. Тут уже возникает синтаксическая избыточность или я просто не нашел способа более короткой записи.

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

Идеальным вариантом может быть унаследовать мою реализацию от вашей и применять их по необходимости.
В общем, если все совместить, то получим:

export interface ICallback<ArgType>
{
    (arg: ArgType, context?: any);
}

export interface ICallbackDesc<ArgType>
{
    Callback: ICallback<ArgType>;
    Subscriber: any;
}

export class Event<ArgType>
{
    private Callbacks: ICallbackDesc<ArgType>[] = [];

    public Subscribe(callback: ICallback<ArgType>, subscriber: any): ITypedSubscription<ArgType>
    { 
        //Выполняем работу
    }

    public Unsubscribe(callback: ICallback<ArgType>): void 
    { 
        //Выполняем работу
    }

    public Trigger: ICallback<ArgType> = function (arg: ArgType, context?: any)
    {
        //Выполняем работу
    }
}

/** Базовый интерфейс подписки на событие. Минимальная функциональность. Можем просто отписаться и все. */
export interface ISubscription
{
    Unsubscribe: { (): void };
}

/** Типизированная версия. Включает ссылки на событие и callback */
export interface ITypedSubscription<ArgType> extends ISubscription
{
    Callback: ICallback<ArgType>;
    Event: Event<ArgType>;
}


т.е., ровно то, что вы предлагали, но с вынесенным во вне делегатом.

Я немного перестарался, как и писал в прошлом комментарии. Спасибо за подсказку.
Да, я пришел к такому же результату.
Ждем продолжения цикла :)
Спасибо. Продолжение обязательно будет. Но пока мне надо завершить внутренний холивар на тему очередных велосипедов и их необходимости в реальной жизни :) Ну, и еще выспаться после рабочей недели)
Sign up to leave a comment.

Articles