Как стать автором
Обновить

Почему шаблоны в $mol такие странные?

Время на прочтение8 мин
Количество просмотров4.8K

Здравствуйте, меня зовут Дмитрий Карловский и я.. дуб. Я пустил свои корни в адептов святого $mol, и выращиваю из них сверх‑людей, способных каждый год сбрасывать былые привычки и убеждения, но тут же пускать побеги свежих идей, базирующихся на прочном рациональном основании.

А в качестве примера, позвольте посеять и в вас зерно сомнения в правильности традиционных решений, и показать, почему синтаксис языка композиции компонент в $mol такой странный, и почему другие языки для этой задачи совсем не подходят.

Требования

  • Компактность. Чем больше кода, тем больше времени тратится на его поддержку, больше точек отказа, сложнее ориентироваться.

  • Наглядность. Синтаксис должен недвусмысленно говорить о том, что происходит. По возможности в мозгу должны вырабатываться мнемоники. Иерархия расположения и направления потоков данных должны считываться визуально.

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

  • Сложные свойства. Любое свойство может содержать любые JSON данные вперемешку с объектами и различными привязками свойств.

  • Локализация. Локализуемые текстовые значения выносятся в файлы локализации и заменяются на ключи. А нелокализуемые остаются в коде как есть.

  • Связывания свойств. Подконтрольные компоненты через свойства связываются с владельцем. Виды связей:

    • Хаки — настройка подконтрольного объекта.

    • Алиасы — делегирование свойства подконтрольному объекту.

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

  • Ленивые реестры. Свойства могут работать не только с одним единственным значением, но и с разными в зависимости от переданного ключа.

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

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

  • Эффективность. pull семантика, минимум телодвижений в рантайме для сборки дерева компонент и обмена значениями между компонентами.

  • Инверсия контроля. Обилие точек расширения, слотов, параметров. Возможность детально настроить поведение, компоновку и визуализацию компонента из вне. Управление поддеревом компонент через контексты.

Варианты языков

view.ts — исполняемые классы компонент со статической типизацией

view.xjs — JavaScript с вымышленным расширением для инлайн объявлений методов.

view.jsx — JavaScript расширенный XML синтаксисом.

view.html — мимикрия под языки HTML‑шаблонов.

view.json — декларативная JSON структура

view.tree — DSL основанный на формате Tree.

view.ts

❌ Очень много нетривиального кода.

❌ Плоская структура, потеряна иерархия расположения.

❌ Сложный и медленный статический анализ.

✅ Не требует дополнительных трансляторов помимо TypeScript.

✅ Наглядно видно исполняемый код.

✅ Высочайшая гибкость.

✅ Отличная поддержка всеми IDE.

export class $my_app extends $mol_page {
	
	override head() {
		return [
			... super.head(),
			this.Filter(),
			this.Add(),
		]
	}
	
	override body() {
		return [ this.Task_rows() ]
	}
	
	@ $mol_mem
	filter( next = '' ) {
		return next
	}
	
	@ $mol_mem
	Filter() {
		return this.$.$mol_string.make({
			hint: ()=> this.$.$mol_locale.text( '$my_app_Filter_hint' ),
			value: ( next?: string )=> this.filter( next )
		})
	}
	
	add_allowed() {
		return false
	}
	
	add( next: Event ) {
		return null
	}
	
	@ $mol_mem
	Add() {
		return this.$.$mol_button_major.make({
			title: ()=> this.$.$mol_locale.text( '$my_app_Add_title' ),
			enabled: ()=> this.add_allowed(),
			click: ( next: Event )=> this.add( next )
		})
	}
	
	@ $mol_mem_key
	Task( id: string ) {
		return this.$.$my_task.make({})
	}
	
	task_drop( id: string, next: Event ) {
		return null
	}
	
	@ $mol_mem_key
	Task_row( id: string ) {
		return this.$.$my_task_row.make({
			Task: ()=> this.Task( id ),
			drop: ( next: Event )=> this.task_drop( id, next )
		})
	}
	
	task_rows(): readonly $my_task_row[] {
		return [ this.Task_row(id) ]
	}
	
	@ $mol_mem
	Task_rows() {
		return this.$.$mol_list.make({
			rows: ()=> this.task_rows(),
		})
	}
	
}

export class $my_task_row extends $mol_row {
	
	override title( next?: string ) {
		return this.Task().title( next )
	}
	
	@ $mol_mem
	Task() {
		return this.$.$my_task.make({})
	}
	
	override sub() {
		return [
			this.Title(),
			this.Drop(),
		]
	}
	
	@ $mol_mem
	Title() {
		return this.$.$mol_string.make({
			value: ( next?: string )=> this.title( next )
		})
	}
	
	drop( next: Event ) {
		return null
	}
	
	@ $mol_mem
	Drop() {
		return this.$.$mol_button_minor.make({
			title: ()=> this.$.$mol_locale.text( '$my_task_row_Drop_title' ),
			click: ( next?: string )=> this.drop( next )
		})
	}
	
}

Подробно о TypeScript

view.xjs

❌ Много нетривиального кода.

❌ Сложный и медленный статический анализ.

❌ Изменённая семантика некоторых конструкций языка.

❌ Полное отсутствие тулинга и поддержки IDE.

✅ Высочайшая гибкость.

✅ Наглядная иерархия расположения.

✅ Легко изучается, если разработчик уже владеет JavaScript.

export class $my_app extends $mol_page {
	
	head() {
		return [
			... super.head(),
			this.Filter() = this.$.$mol_string.make({
				hint: ()=> this.$.$mol_locale.text( '$my_app_Filter_hint' ),
				value: ( next?: string )=> this.filter( next ) = '',
			}),
			this.Add() = this.$.$mol_button_major.make({
				title: ()=> this.$.$mol_locale.text( '$my_app_Add_title' ),
				enabled: ()=> this.add_allowed() = false,
				click: ( next: Event )=> this.add( next ) = null,
			}),
		]
	}
	
	body() {
		return [
			this.Task_ros() = this.$.$mol_list.make({
				rows: ()=> this.task_rows() = [
					this.Task_row( id = '0' ) = this.$.$my_task_row.make({
						Task: ()=> this.Task( id ) = this.$.$my_task_row.make({}),
						drop: ( next: Event )=> this.task_drop( id, next ) = null,
					}),
				] as readonly $my_task_row[],
			}),
		]
	}
	
}

export class $my_task_row extends $mol_row {
	
	@ $mol_mem
	Task() {
		return this.$.$my_task.make({})
	}
	
	sub() {
		return [
			this.Title() = this.$.$mol_string.make({
				value: ( next?: string )=> this.title( next ) = this.Task().title( next ),
			}),
			this.Drop() = this.$.$mol_button_minor.make({
				title: ()=> this.$.$mol_locale.text( '$my_task_row_Drop_title' ),
				click: ( next?: string )=> this.drop( next ) = null,
			}),
		]
	}
	
}

view.jsx

❌ Очень много нетривиального кода.

❌ Плоская структура, потеряна иерархия расположения.

❌ Сложный и медленный статический анализ.

❌ Слабая поддержка IDE самого языка композиции.

export const $my_app = $mol_page.extends( ({
	$mol_page,
	$mol_string,
	$mol_button_major,
	$mol_list,
	$mol_mem,
	$mol_mem_key,
	$mol_locale,
	$my_task_row,
}, {
	head: _head,
}, {
	
	head = ()=> [
		... _head(),
		<Filter/>,
		<Add/>,
	],
	
	body = ()=> <>
		<Task_rows/>
	</>,
	
	filter = $mol_mem( ( next = '' )=> next ),
	
	Filter = $mol_mem( ()=> <$mol_string
		hint={ ()=> $mol_locale( '$my_app_Filter_hint' ) }
		value={ ( next: string )=> filter( next ) }
	/> ),
	
	add_allowed = ()=> false,
	
	add = ( next: Event )=> null,
	
	Add = $mol_mem( ()=> <$mol_button_major
		title={ ()=> $mol_locale( '$my_app_Add_title' ) }
		enabled={ ()=> add_allowed() }
		click={ ( next: Event )=> add( next ) }
	/> ),
	
	Task = $mol_mem_key( ( id: string )=> <$my_task/> ),
	
	task_drop = ( id: string, next: Event )=> null,
	
	Task_row = $mol_mem_key( ( id: string )=> <$my_task_row
		Task={ ()=> Task( id ) }
		drop={ ( next: Event )=> task_drop( id, next ) }
	/> ),
	
	task_rows = ()=> [
		<Task_row key={"0"} />,
	] as readonly ReturnType< typeof $my_task_row >[],
	
	Task_rows = $mol_mem( ()=> <$mol_list
		rows={ ()=> task_rows() }
	/> ),
	
})=> <$mol_page {...{ head, body }} /> )

export const $my_task_row = $mol_row.extends( ({
	$mol_string,
	$mol_button_minor,
	$mol_row,
	$mol_mem,
	$mol_locale,
}, {
}, {
	
	Task: { title } = $mol_mem( ()=> <$my_task/> ),
	
	sub = ()=> [
		<Title/>,
		<Drop/>
	],
	
	Title = $mol_mem( ()=> <$mol_string
		value={ ( next?: string )=> title( next ) }
	/> ),
	
	drop = ( next: Event )=> null,
	
	Drop = $mol_mem( ()=> <$mol_button_minor
		title={ ()=> $mol_locale( '$my_task_row_Drop_title' ) }
		click={ ( next?: string )=> drop( next ) }
	/> ),
	
})=> <$mol_row {...{ title, sub }} /> )

Подробно о JSX

view.html

❌ Много визуально зашумлённого кода.

❌ Даже со всеми наворотами IDE HTML не очень удобно редактировать.

❌ Слабая поддержка IDE самого языка композиции.

❌ Не наглядный и неконсистентный синтаксис.

❌ Привычки и ассоциации с HTML слабо помогают и только путают.

✅ Наглядная иерархия расположения.

✅ Простой парсинг стандартными средствами поддерживающими пользовательские DOCTYPE.

<mol_page id="my_app">
	
	<head list>
		
		<super/>
		
		<mol_string bind="Filter">
			<hint locale="Filter tasks or Add one" />
			<value bidi="filter_query" string="" />
		</mol_string>
		
		<mol_button_major bind="Add">
			<title locale="Add this task" />
			<enabled bind="add_allowed" boolean="false" />
			<click bidi="add" null />
		</mol_button_major>
		
	</head>
	
	<body hack>
		
		<mol_list bind="Task_rows">
			<rows bind="task_rows" list="my_task_row">
				
				<my_task_row bind="Task_row" key="0">
					<Task>
						<my_task bind="Task" key />
					</Task>
					<drop bidi="task_drop" key null />
				</my_task_row>
				
			</rows>
		</mol_list>
		
	</body>
		
</mol_page>

<mol_row id="my_task_row">
	
	<my_task bind="Task">
		<title alias bidi="title" />
	</my_task>
	
	<sub list>
		
		<mol_string bind="Title">
			<value bidi="title" />
		</mol_string>
		
		<mol_button_minor bind="Drop">
			<title locale="Drop" />
			<click bidi="drop" null />
		</mol_button_minor>
		
	</sub>
	
</mol_row>

Подробно о HTML

view.json

❌ Очень много визуально зашумлённого кода.

❌ Не наглядный и неконсистентный синтаксис.

❌ Слабая поддержка IDE.

✅ Наглядная иерархия расположения.

✅ Простой парсинг стандартными средствами.

{
	"$my_app": {
		"_extends": "$mol_page",
		"head": {
			"_array": null,
			"_value": [
				{ "_expand": true },
				{
					"_bind": "Filter",
					"_extends": "$mol_string",
					"hint": {
						"_locale": true,
						"_value": "Filter tasks or Add one"
					},
					"value": {
						"_bidi": "filter_query",
						"_value": ""
					}
				},
				{
					"_bind": "Add",
					"_extends": "$mol_button_major",
					"title": {
						"_locale": true,
						"_value": "Add this task"
					},
					"enabled": {
						"_bind": "add_allowed",
						"_value": false
					},
					"click": {
						"_bidi": "add",
						"_value": null
					}
				}
			]
		},
		"body": {
			"_array": null,
			"_value": [
				{
					"_bind": "Task_rows",
					"_extends": "$mol_list",
					"rows": {
						"_bind": "task_rows",
						"_array": "$my_task_row",
						"_value": [
							{
								"_bind": "Task_row",
								"_extends": "$my_task_row",
								"_key": "0",
								"Task": {
									"_bind": "Task",
									"_extends": "$my_task",
									"_key": null
								},
								"drop": {
									"_bidi": "task_drop",
									"_key": null,
									"_value": null
								}
							}
						]
					}
				}
			]
		}
	},
	"$my_task_row": {
		"_extends": "$mol_row",
		"Task": {
			"_extends": "$my_task",
			"title": {
				"alias": true,
				"_bidi": "title"
			}
		},
		"sub": {
			"_array": null,
			"_value": [
				{
					"_bind": "Title",
					"_extends": "$mol_string",
					"value": {
						"_bidi": "title"
					}
				},
				{
					"_bind": "Drop",
					"_extends": "$mol_button_minor",
					"title": {
						"_locale": true,
						"_value": "Drop"
					},
					"click": {
						"_bidi": "drop",
						"_value": null
					}
				}
			]
		}
	}
}

Подробно о JSON

view.tree

❌ Людям, привыкшим к SGML‑ и C‑подобным языкам может потребоваться привыкание.

❌ Слабая поддержка IDE.

✅ Лаконичный код.

✅ Наглядная иерархия расположения.

✅ Наглядное представление потоков данных.

✅ Простой синтаксис даёт удобную обработку.

$my_app $mol_page
	head /
		^
		<= Filter $mol_string
			hint @ \Filter tasks or Add one
			value? <=> filter? \
		<= Add $mol_button_major
			title @ \Add this task
			enabled <= add_allowed false 
			click? <=> add? null
	body /
		<= Task_rows $mol_list
			rows <= task_rows /$my_task_row
				<= Task_row* $my_task_row
					Task <= Task* $my_task
					drop? <=> task_drop*? null

$my_task_row $mol_row
	Task $my_task
		title? => title?
	sub /
		<= Title $mol_string
			value? <=> title?
		<= Drop $mol_button_minor
			title @ \Drop
			click? <=> drop? null

Этот код транслируется в тот страшный код на TS из начала статьи.

Подробно о view.tree

Выводы

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

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

Направления роста


Актуальный оригинал на $hyoo_page.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Если бы от вас зависела судьба человечества, то какой язык композиции компонент вы бы выбрали?
33.73% TS28
1.2% xJS1
31.33% JSX26
36.14% HTML30
12.05% JSON10
13.25% view.tree11
Проголосовали 83 пользователя. Воздержались 28 пользователей.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 25: ↑9 и ↓16-7
Комментарии40

Публикации

Истории

Работа

Ближайшие события