Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

destructor может вызываться много раз? #274

Closed
zerkalica opened this issue Oct 10, 2017 · 24 comments
Closed

destructor может вызываться много раз? #274

zerkalica opened this issue Oct 10, 2017 · 24 comments
Assignees

Comments

@zerkalica
Copy link
Collaborator

zerkalica commented Oct 10, 2017

https://github.com/eigenmethod/mol/blob/master/atom/atom.ts#L39

		destructor() {
			this.unlink()
			this.status = $mol_atom_status.actual
			
			const value = this['value()']
			
			if( value instanceof $mol_object ) {
				if( value.object_owner() === this ) value.destructor();
			}

			this['value()'] = undefined
		}		

Не пойму, как работают деструкторы. Если destructor есть у хоста, но хост не свойство атома, кто тогда его вызовет.

Не планируется вообще убрать функциональность с овнерами из mol_object в мапу, или хотя бы примешивать? Тогда можно для обычных объектов, не унаследованных от mol_object тоже использовать эти фичи.

Смущает то, что протекает этот mol_object на все слои.

Это не бага?

this.status = $mol_atom_status.actual 

Если после destroy произойдет get, мы получим undefined, если force не использовать.

if( !force && this.status === $mol_atom_status.actual ) return
@zerkalica
Copy link
Collaborator Author

Еще вопрос, в коде атомов везде используется $mol_atom.stack[0], а зачем тогда массив?

@nin-jin
Copy link
Member

nin-jin commented Oct 10, 2017

Если destructor есть у хоста, но хост не свойство атома, кто тогда его вызовет.

Никто. Раньше владелец проходился по всему имуществу и дестроил его, но со внедрением атомов это лишь лишние тормоза.

Не планируется вообще убрать функциональность с овнерами из mol_object в мапу, или хотя бы примешивать?

Да, вместе с контекстами хочу что-то такое замутить.

Если после destroy произойдет get, мы получим undefined, если force не использовать.

Так и задумано, чтобы при дестрое не искать атом в запланированных на обновление - он просто ничего не сделает. Уничтоженный атом не предназначен для дальнейшего использования.

Еще вопрос, в коде атомов везде используется $mol_atom.stack[0], а зачем тогда массив?

Потому, что это алиас для записи в $mol_state_stack. Я планировал его использовать для чего-то типа зон. Без которых атомы не совместимы с node-fibers.

@nin-jin nin-jin self-assigned this Oct 10, 2017
@zerkalica
Copy link
Collaborator Author

zerkalica commented Oct 10, 2017

А если надо что-то вроде такого замутить?

class AutocompleteService {
    @force $: AutocompleteService
    @mem nameToSearch: string = ''

    @props set props({initialValue}: IAutocompleteProps) {
        this.nameToSearch = initialValue
    }

    _handler: number = 0

    destructor() {
        clearTimeout(this._handler)
    }

    @mem get searchResults(): string[] {
        clearTimeout(this._handler)
        const name = this.nameToSearch
        this._handler = setTimeout(() => {
            fetch(`/api/autocomplete?q=${name}`)
                .then((r: Response) => r.json())
                .then((data: string[]) => {
                    this.$.searchResults = data
                })
                .catch((e: Error) => {
                    this.$.searchResults = e
                })
        }, 500)

        throw new mem.Wait()
    }

    @mem
    set searchResults(searchResults: string[] | Error) {}

    setValue = (e: Event) => {
        this.$.nameToSearch = (e.target: any).value
    }
}

Кто в хосте вызовет destructor, чтоб таймер подчистить? Или правильнее handler переделать на атом, который возвращает объект с свойством destroy? тогда прозрачность теряется.

Да, вместе с контекстами хочу что-то такое замутить.

DI, по-мне лучше, т.к. все создает объекты он, там любые преобразования над ними легко сделать. Еще контракт появляется у класса, не надо вчитываться в методы и смотреть, что они там из контекста используют. Еще класс становится пригоден для работы где угодно, вне контекстов mol.

Уничтоженный атом не предназначен для дальнейшего использования.

Это почему же? Вот такой кейс разве не должен отрабатываться?

        let destroyed: string = ''

        class A {
            @mem
            foo(): number {
                destroyed = ''
                return 1
            }

            destructor() {
                destroyed = 'foo'
            }
        }

        class B {
            _a = new A()

            @mem
            showing(next?: boolean): boolean {
                return next === undefined
                    ? true
                    : next
            }

            @mem
            bar(): ?number {
                return this.showing()
                    ? this._a.foo()
                    : null
            }
        }

        const b = new B()
        assert(b.bar() === 1)

        assert(destroyed === '')
        b.showing(false)
        b.bar()
        sync()

        assert(destroyed === 'foo')

        b.showing(true)
        sync()

        assert(destroyed === '')

Заюзали атом, потом перестали, потом опять заюзали.

Потому, что это алиас для записи в $mol_state_stack. Я планировал его использовать для чего-то типа зон. Без которых атомы не совместимы с node-fibers.

Может просто вынести static-и из атомов в отдельную сущность и назвать ее контекстом, чем так мудрить?

@nin-jin
Copy link
Member

nin-jin commented Oct 11, 2017

Кто в хосте вызовет destructor, чтоб таймер подчистить? Или правильнее handler переделать на атом, который возвращает объект с свойством destroy?

Если экземпляр AutocompleteService создаётся через атом, то он и вызовет деструктор. Если это синглтон, то да в отдельном свойстве создаём объект с конструктором (взводит таймер) и деструктором (сбрасывает таймер). Что-то типа:

@mem
defered_fetch() {
    return new class {
        let timer : number
        constructor() {
            this.timer = setTimeout( handler , 500 )
        }
        destructor() {
            clearTimeout( this.timer )
        }
    }
}

Соответственно этот вложенный класс лучше реализовать отдельным ReactiveTimerService-ом.

DI, по-мне лучше

Так одно другое не исключает. И контекст инъектится точно так же как любые другие свойства.

Еще контракт появляется у класса, не надо вчитываться в методы и смотреть, что они там из контекста используют.

А зачем вам знать что они там используют? :-)

Заюзали атом, потом перестали

В этот момент атом выкидывается из памяти. Иначе бы число атомов росло неограниченно. Когда появится зависимость - атом будет создан снова.

Может просто вынести static-и из атомов в отдельную сущность и назвать ее контекстом, чем так мудрить?

$mol_state_stack и есть такая сущность - состояние привязанное к стеку. Соответственно это состояние можно сохранить перед сменой стека (например, через node-fibers), а потом восстановить при возвращении стека.

@zerkalica
Copy link
Collaborator Author

zerkalica commented Oct 11, 2017

А зачем вам знать что они там используют? :-)

Что б не завязываться на магический this.$, который vendor lock-in

В этот момент атом выкидывается из памяти.

А если, как в примере _a = new A() не атом? WeakMap не выкинет, т.к. ссылка будет в B
У вас есть тест на подобный кейс? глянул бы. У меня не работает, ничего не выкидывается.

@nin-jin
Copy link
Member

nin-jin commented Oct 11, 2017

Ну так если _a - нереактивный мембер, то и дестроить его просто так нельзя. Я бы сделал его реактивным и тогда при потере зависимостей будет уничтожаться и А и владеющий им атом:

@mem
a() { return new A }

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

@zerkalica
Copy link
Collaborator Author

У меня логика была такая: если в классе все атомы дестроятся, то вызывать деструктор у класса.
А зачем лепить mem на каждое свойство ради деструкции?

@nin-jin
Copy link
Member

nin-jin commented Oct 11, 2017

Класс и без активных атомов может быть валидным.
Не ради деструкции, а ради понимания, что оно кому-то нужно.

@zerkalica
Copy link
Collaborator Author

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

@nin-jin
Copy link
Member

nin-jin commented Oct 11, 2017

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

@zerkalica
Copy link
Collaborator Author

В tree может и нормально, но в классе забыть сделать атомом легко. Т.к. смысл атома в реактивности, а она в этом месте не нужна, не очевидно получается.

@nin-jin
Copy link
Member

nin-jin commented Oct 11, 2017

Смысл mem в создании по требованию и кешировании. Когда привыкаешь к ленивости - не забываешь.

@zerkalica
Copy link
Collaborator Author

Ну это субъективно. Это в вашей mol-песочнице может и привычно.

По-требованию это хорошо, но получается все тотально надо заворачивать в фабрики, так себе решение.

Вместо

class B {
 _a = new A()
}

писать

class B {
 @mem _a() { return new A() }
}

Спрашивается, а зачем, если класс B и так живет только вместе с компонентом, в котором используется.

@nin-jin
Copy link
Member

nin-jin commented Oct 11, 2017

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

@zerkalica
Copy link
Collaborator Author

zerkalica commented Oct 11, 2017

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

@nin-jin
Copy link
Member

nin-jin commented Oct 11, 2017

Логичнее, но нужно написать кучу утилит и плагинов к IDE и всё равно пересадить людей на свой язык будет непосильной задачей. view.tree тому пример :-D

@zerkalica
Copy link
Collaborator Author

Вопрос возник, почему столько генераций функций в mol, даже там, где ленивость не нужна, как хэндлеры для атомов и деструкторы в mem:

const destructor = atom.destructor
atom.destructor = ()=> {
	store.delete( host )
	destructor.call( atom )
}

Памяти они больше жрут, время на генерацию чуть больше. Если все приложение в таком стиле написано, то разница может быть существенная. Я смоделировал на бешенном кол-ве итераций, но все же.

Почему просто классы не создавать или больше деталей прокидывать в атом? да, будет чуть больше кода, но памяти то меньше.

@nin-jin
Copy link
Member

nin-jin commented Oct 12, 2017

Ну, на микробенчмарке разница в 2 раза. А в реальном приложении - капля в море.
Ещё эффективней - отнаследоваться от атома.

@zerkalica
Copy link
Collaborator Author

Атомов может быть 10к, если dict учитывать, там разница будет 10-15%

@nin-jin
Copy link
Member

nin-jin commented Oct 12, 2017

Может, но к ним и объектов будет не одна тысяча, а к ним биндингов ещё в несколько раз больше, а биндинги через классы делать - совсем много кода.

@zerkalica
Copy link
Collaborator Author

С биндингами тут тоже есть всякие ухищрения, когда контекст передается отдельно, вроде как в inferno onClick={[this, func]}.

Я к тому, что генерацию функций пытаются всячески избежать. Сейчас наверное это не так актуально, правда. А вот во времена bluebird, запарывались этим, поэтому он быстрее нативных и всяких q был, где генерация на генерации.

@nin-jin
Copy link
Member

nin-jin commented Oct 12, 2017

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

@zerkalica
Copy link
Collaborator Author

Я думаю, this в функциях допилят скоро

microsoft/TypeScript#3694

Typescript:

class A { 
    x: number
}
function f(this: A, a: number) {
  this.x = 3;
}

class B {}
const b = new B()

f.call(b, 1)

facebook/flow#452

flow:

class A { 
    x: number
}
function f(a: number) {
   (this: A);
  this.x = 3;
}

class B {}
const b = new B()

f.call(b, 1)
5:    (this: A);
       ^ B. This type is incompatible with
5:    (this: A);
             ^ A
6:   this.x = 3;
          ^ property `x`. Property not found in
6:   this.x = 3;
     ^ B

В ts, в отличие от flow, call пока не typesafe, flow вообще зашибись типы выводит.

@nin-jin
Copy link
Member

nin-jin commented Oct 12, 2017

Тут дело даже не в проверке, а в том, что когда пишешь код внутри функции среда разработки не знает тип this и приходится его писать вручную, что для однострочников сильно избыточно.

@nin-jin nin-jin closed this as completed Jan 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants