LINUX.ORG.RU

Ant: создание среды выполнения JavaScript за один месяц

 , , , ,


1

4

Это перевод поста building a javascript runtime in one month от второго января 2026 года.

TLDR: Я создал Ant, небольшую (2 МБ) среду выполнения JavaScript. Полный исходный код, а также тесты и документацию можно найти на моем github.

Когда я начал этот проект в начале ноября, у меня была простая идея: а что, если я смогу создать движок JavaScript, достаточно маленький, чтобы его можно было встроить в программу на C, но достаточно полный, чтобы на нём можно было запускать реальный код? Что-то, что можно было бы распространять, не таская с собой сотни мегабайт V8 или Node. Я уже пробовал это раньше с минимальными копиями Deno, но этого было недостаточно.

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

Первая неделя была режимом выживания

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

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

Знаете ли вы, что автоматическая вставка точки с запятой является фактической частью спецификации, с которой вам приходится иметь дело?

Знаете ли вы, что биндинг this меняется в зависимости от контекста, и что предварительное объявление означает, что переменные, объявленные с помощью var, существуют до того, как им присваивается значение? Даже window.window.window является действительным…

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

typedef uint64_t jsval_t;

Каждое значение JavaScript в среде выполнения представляет собой одно 64-разрядное целое число. NaN-боксирование. Спецификация IEEE 754 с плавающей запятой имеет странный пробел: существует 2^53 возможных значений NaN, и большинство из них никогда не используются. Поэтому я их украл :3.

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

Проверки на этапе компиляции доказывают это:

_Static_assert(sizeof(double) == 8, "NaN-boxing requires 64-bit IEEE 754 doubles");
_Static_assert(sizeof(uint64_t) == 8, "NaN-boxing requires 64-bit integers");
_Static_assert(sizeof(double) == sizeof(uint64_t), "double and uint64_t must have same size");

Это стало основой того, как среда выполнения представляет каждое отдельное значение. Каждое число, каждый объект, каждая строка, каждая функция, каждое promise, каждый аргумент, каждая область действия. Всё является одним из них. Никаких дискриминируемых объединений, никаких vtable, никаких выделений кучи для метаданных. Только биты. Потребовались дни итераций, чтобы дойти до этого, но как только это заработало, всё остальное стало быстрее. Значения NaN и Infinity имели свои собственные проблемы, но небольшая перестановка боксирования помогла их исправить.

Примерно на четвёртый день я заставил работать переменные. На пятый день – функции. На шестой день я смог запустить циклы. Первые коммиты разбросаны. Вскоре после этого я добавил стрелочные функции, iife, опциональное цепочечное соединение и даже операторы nullish coalescing. Я перескакивал между функциями, потому что просто читал MDN, добавляя вещи по мере того, как вспоминал об их существовании.

Катастрофа со сборкой мусора

И тогда это случилось: управление памятью.

Среда выполнения JavaScript требует сборки мусора. Нельзя просить пользователя вручную освобождать объекты. Поэтому примерно на второй неделе я попытался реализовать собственную сборку мусора. Это был кошмар. Я добавлял функции, но нарушал работу сборки мусора, или создавал сборку мусора, которая снижала производительность, или пытался интегрировать чужую сборку мусора, но это было слишком сложно.

Я очень мучился на этом этапе. Ручной free-list GC отключался и включался сотни раз, и каждая попытка ломала ещё одну важную часть. Были дни, когда я был явно разочарован. В 3 часа ночи я занимался отладкой, пытаясь понять, почему стеки сопрограмм не защищались должным образом? Почему происходила утечка памяти? Почему все ломалось, когда я добавлял поддержку json?

Поворотным моментом стал отказ от ручного сбора мусора и переход на BDWGC. Это настоящий сборщик мусора производственного уровня, который используется в других языках, в сочетании с моей собственной разработкой уплотнения памяти с отслеживанием прямых ссылок. Он может маркировать биты, создавать хеш-таблицы для пересылки и выполнять всё то, что делают производственные сборщики мусора. После его интеграции проблемы с памятью в основном исчезли. Мой прогресс изменил тон. Всё начало работать, я добавил модуль процессов, улучшил сообщения об ошибках. Это было началом ускорения темпа.

Promises были совсем другим делом

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

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

Если посмотреть, то в коммитах видно, как это было сложно: «async promise pushback», «segfault when event loop empty», «prevent dead task from blocking». Это проблемы, которые невозможно предвидеть, пока не дойдёшь до середины реализации.

Сложность заключается в том, что promises JavaScript не могут быть простыми. Они должны обрабатывать цепочки .then(), они должны правильно отклонять, они должны работать с асинхронными функциями, которые являются всего лишь синтаксическим сахаром над генераторами, которые являются всего лишь синтаксическим сахаром над promises и обратными вызовами.

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

typedef struct coroutine {
  struct js *js;
  coroutine_type_t type;
  jsval_t scope;
  jsval_t this_val;
  jsval_t awaited_promise;
  jsval_t result;
  jsval_t async_func;
  jsval_t *args;
  int nargs;
  bool is_settled;
  bool is_error;
  bool is_done;
  jsoff_t resume_point;
  jsval_t yield_value;
  struct coroutine *prev;
  struct coroutine *next;
  mco_coro* mco;
  bool mco_started;
  bool is_ready;
} coroutine_t;

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

Как только у меня появились сопрограммы, promises стали реальностью. Цепочка .then() заработала, await действительно приостанавливала выполнение и возобновляла его позже, асинхронная сторона среды выполнения начала складываться. Я начал добавлять надлежащую обработку promises, и все встроенные функции Promise появились после этого, и они появились быстро, потому что сложная часть была решена.

Странные крайние случаи JavaScript

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

Я решаю их одну за другой. Обработка замороженных/запечатанных объектов, добавление поддержки неконфигурируемых свойств, исправление деструктуризации в десятый раз, добавление поддержки доступа к свойствам с помощью getter и setter. Каждый день сталкиваюсь с новыми крайними случаями. Иногда приходится исправлять несколько ошибок за день, потому что я что-то реализую, запускаю тесты на соответствие, нахожу три ошибки, исправляю их, а потом нахожу еще пять ошибок, о которых я не знал.

Знаете ли вы, сколько способов доступа к цепочке прототипов предоставляет JavaScript? __proto__, Object.getPrototypeOf(), Object.setPrototypeOf() и внутренний слот [[Prototype]]. Вы должны обрабатывать все из них, и они должны правильно взаимодействовать друг с другом. Обманчиво короткий коммит под названием «use descriptor tables for getters/setters/properties» представляет собой недели работы, чтобы правильно обработать это.

Деструктуризация казалась простой: const [a, b] = arr. Но как быть с разрежёнными массивами? С перечисляемыми свойствами объектов? С вложенной деструктуризацией? Со значениями по умолчанию? Параметрами ...rest? Каждое исправление, кажется, приводит к появлению другого, и каждое из них – это «кроличья нора» крайних случаев.

Тесты на соответствие были жестокими в лучшем смысле этого слова. Каждый раз, когда я их запускал, они выявляли ошибки, о которых я даже не подозревал. Затем я исправлял их и переходил к следующему пункту. Этот цикл повторялся десятки раз.

Во второй половине всё стало странным

Ко второй неделе у меня была рабочая среда выполнения JavaScript, которая могла выполнять код. Она не была завершена, но была реальной. Затем я начал добавлять всё то, что делает её полезной.

Операции с файловой системой, утилиты для работы с путями файлов, модуль url и всеми любимый встроенный http-сервер, который сделал Bun популярным. Внезапно с Ant стали работать настоящие программы. Вы могли бы написать веб-сервер, используя только это:

import { join } from 'ant:path';
import { readFile } from 'ant:fs';
import { createRouter, addRoute, findRoute } from 'rou3';

const router = createRouter();

addRoute(router, 'GET', '/status/:id', async c => {
  await new Promise(resolve => setTimeout(resolve, 1000));

  const result = await Promise.resolve('Hello');
  const name = await readFile(join(import.meta.dirname, 'name.txt'));

  const base = '{{name}} {{version}} server is responding with';
  const data = { name, version: Ant.version() };

  return c.res.body(`${base.template(data)} ${result} ${c.params.id}!`);
});

async function handleRequest(c) {
  console.log('request:', c.req.method, c.req.uri);
  const result = findRoute(router, c.req.method, c.req.uri);

  if (result?.data) {
    c.params = result.params;
    return await result.data(c);
  }

  c.res.body('not found: ' + c.req.uri, 404);
}

console.log('started on http://localhost:8000');
Ant.serve(8000, handleRequest);
$ ant examples/server/server.js
started on http://localhost:8000

$ curl http://localhost:8000/status/world
Ant 0.3.2.6 server is responding with Hello world!

Это настоящий JavaScript, работающий в Ant: async/await, ввод-вывод файлов, HTTP, маршрутизация с параметрами, cетевые операции, операции со строками.

Темп работы увеличился. С каждым днём я становился всё более уверенным, работал быстрее, исправлял больше ошибок, добавлял больше функций. Затем наступила «неясная, но необходимая фаза». Я добавил поддержку прокси, рефлексию, символы и даже частные поля и методы в классах. Каждая из этих функций является функцией ECMA6+, которую почти никто не использует, но спецификация гласит, что они существуют, поэтому они должны работать.

Одна из моих любимых – атомарные операции:

const sharedBuffer = new SharedArrayBuffer(256);

const int32View = new Int32Array(sharedBuffer);
Atomics.store(int32View, 0, 42);
const value = Atomics.load(int32View, 0);
console.log('stored 42, loaded:', value);

Atomics.store(int32View, 1, 10);
const oldValue = Atomics.add(int32View, 1, 5);
console.log('old value:', oldValue);

Atomics.store(int32View, 2, 100);
const result = Atomics.compareExchange(int32View, 2, 100, 200);
console.log('exchanged, new value:', Atomics.load(int32View, 2));
$ ant examples/atomics.js
stored 42, loaded: 42
old value: 10
exchanged, new value: 200
Последняя неделя была просто домино

Как только ядро Ant заработало, сборка мусора стала стабильной, а функции promises заработали, всё остальное стало на свои места: незначительные проблемы были устранены, добавлены недостающие методы, решены крайние случаи.

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

К 28-му дню я дорабатывал то, что действительно работало. Полная среда выполнения JavaScript с поддержкой async/await, надлежащим управлением памятью, сетевыми функциями, вводом-выводом файлов и полной совместимостью со спецификациями ES1-ES5, а также случайным набором ещё более новых функций.

Я даже «вспомнил» включить оптимизацию LTO и другие флаги компилятора после того, как мне напомнили 😅.

Что случилось на самом деле

Один месяц. Ant теперь существует как среда выполнения JavaScript, которая:

  • Проходит все тесты на соответствие ES1–ES5 из набора тестов javascript-zoo. Полное соответствие 25-тилетним спецификациям JavaScript.
  • Реализует async/await с правильной обработкой promises и микрозадачами.
  • Имеет сборщик мусора, который действительно работает без утечек памяти.
  • Может запускать веб-серверы с использованием libuv (так же, как Node) для работы c сетью.
  • Может вызывать библиотеки через ffi следующим образом:
import { dlopen, suffix, FFIType } from 'ant:ffi';

const sqlite3 = dlopen(`libsqlite3.${suffix}`);

sqlite3.define('sqlite3_libversion', {
  args: [],
  returns: FFIType.string
});

console.log(`version: ${sqlite3.sqlite3_libversion()}`);
$ ant examples/ffi/basic/sqlite.js
version: 3.43.2
  • Может читать и записывать файлы, а также выполнять асинхронный ввод-вывод.
  • Имеет правильную работу областей видимости, предварительного объявления и затенения переменных.
  • Поддерживает классы, стрелочные функции, деструктуризацию, операторы распространения, шаблонные литералы, опциональное цепочечное соединение.
  • Обрабатывает необычные крайние случаи, о которых большинство людей не задумывается: присвоение __proto__, дескрипторы свойств, неконфигурируемые свойства, замороженные и запечатанные объекты. См. tests/proto.js.
  • Реализует систему модулей ES, которая может импортировать и экспортировать код.
  • Поддерживает символы, прокси, рефлексию, weakmaps, weaksets, maps, sets.
  • Поддерживает разделяемую память и атомарные операции для параллельного программирования. Прочитав это, вы понимаете, что перед вами полноценная среда выполнения JavaScript, а не просто игрушка.
Цена

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

В некоторые дни я работал более 10 часов подряд. В некоторые дни было более 20 коммитов. Проект не замедляется. Он ускоряется. С каждым днем я становлюсь более уверенным, двигаюсь быстрее, исправляю больше ошибок, добавляю больше функций.

В конце я сталкиваюсь с крайними случаями, которые требуют чтения спецификациё ECMAScript, понимания того, что делает V8, проверки того, как другие движки обрабатывают некоторые странные крайние случаи. Улучшение подсчета символов, оптимизация классов и даже перенос внутренних свойств в слоты (как это делает V8). Это оптимизация производительности и улучшения архитектуры, которые должны происходить только после стабилизации кодовой базы. За исключением того, что всё это происходит на прошлой неделе, потому что основа прочная, и у меня есть пропускная способность.

После выпуска: этап оптимизации

Первый релиз состоялся 26 ноября. Затем наступила тишина. Обычная тишина, которая наступает после выпуска чего-либо. Затем, около 20 декабря, работа возобновилась.

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

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

Затем я перешёл на использование таблиц дескрипторов для геттеров/сеттеров/свойств. Вместо отдельных выделений для каждого дескриптора свойства я сгруппировал их в таблицы. Меньше выделений, меньше преследований указателей.

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

Я люблю таблицы диспетчеризации. Я перешел на использование вычисляемого goto для ffi, json и других. Это позволяет процессору переходить непосредственно к нужному обработчику. Устраняется ветвление, устраняется поиск.

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

Где-то здесь появилось сравнение с Node. Запустить те же тесты. Как справляется Ant? Результаты стали выглядеть хорошо. Очень хорошо. Настолько хорошо, что начинаешь задаваться вопросом, смогу ли я действительно превзойти Node в чём-то?

Оптимизация Ant создавала снимки рабочих версий. Если я что-то ломал при оптимизации, у меня была точка, к которой я мог вернуться. Постепенный прогресс. Каждая фиксация была немного быстрее предыдущей. Некоторые оптимизации работали, некоторые — нет. Но схема оставалась прежней: профилирование, оптимизация, измерение, коммит.

Затем последовали улучшения в работе сборщика мусора. Интеграция BDWGC работала в течение первого месяца, но где-то на этапе оптимизации она была отключена. Среда выполнения просто теряла память. Я снова добавил отложенную сборку мусора и раскомментировал большую часть старого GC.

Но не по-старому, это был компактный сборщик мусора с маркировкой-копированием, который фактически дефрагментировал память. Старый сборщик мусора работал в неподходящее время, вызывая задержки в «горячих точках». Поэтому я сделал его откладываемым, собирающим между логическими единицами работы, с отслеживанием прямых ссылок, чтобы объекты могли перемещаться в памяти без нарушения указателей. Таким образом, сборщик мусора вернулся, но стал умнее. Он откладывает сборку до тех пор, пока не станет безопасно сделать паузу, а когда запускается, он уплотняет кучу.

Почему это случилось

Честно говоря, я не знаю. Возможно, из злости. Из потребности что-то доказать. Из чистой одержимости. Из того состояния, когда ты работаешь над чем-то и вдруг обнаруживаешь, что прошло восемь часов, уже 4 утра, и ты просто фиксируешь код и завтра делаешь то же самое.

Проект существует, потому что что-то во мне решило, что он должен существовать, и не остановилось, пока это не произошло.

Он не идеален. В коде, вероятно, есть ошибки, которые я не обнаружил. Вероятно, есть проблемы с производительностью, которые я не оптимизировал. Вероятно, я пропустил некоторые требования спецификации. Но он работает. Вы можете писать настоящий JavaScript, и он будет выполняться. Вы можете использовать async/await. Вы можете создавать серверы. Вы можете использовать его для реальных задач.

Если вы когда-нибудь задумывались, что может сделать один человек, обладающий достаточной решимостью и не имеющий графика сна, то ответ – совместимый движок JavaScript. Вот что возможно.

★★★★★

Проверено: maxcom ()
Последнее исправление: dataman (всего исправлений: 1)
Ответ на: комментарий от firkax

Даже более того, ты можешь из своего примера ниже убрать (int) благодаря этому, или вообще не знать чем '25' отличается от 25. Или писать что-то, что оказывается в итоге '123;f;xyz;7' > '11qwe', подразумевая сравнение 123 и 11 но не тратя время на парсинг этих строк, и оно сработает. Правда, есть неожиданность: '123;f;xyz;7' > '11e4' вернёт false, но в целом пофиг.

Краткий экскурс на тему того, почему на классическом PHP нельзя написал надёжную логику. Я лично сталкивался с тем, что бекендер мне возвращает чёрт знает что — причём, он сам не знает, что он мне возвращает.

open FSharp.Data

Что это, зачем? За какой аспект логики работы отвечает эта строчка? Ни за какой, но она требуется.

Ctrl+C/Ctrl+V. Это примерно как «#!env python3» или «<?php» — строчка означает «дальше идёт скриптовуха».

Зачем мы два раза пишем о том что хотим загрузить users.csv? Чем Users отличается от users?

CSV является табличным представлением, потому у него есть схема. Столбец может быть int, decimal (точное десятичное вещественное число), float, DateTime, ну и в конце-концов string.
CsvProvider<«users.csv»> говорит, что схему мы автоопределяем из файла.

users.Rows |> Seq.filter (fun r -> r.Age > 25) |> Seq.iter (printfn «%s»)

Откуда взялось какое-то seq? Тут куча лишних букв, которые зачем-то надо помнить и писать

А вот это вот из какого языка и насколько оно лаконично?

array_diff_ukey
array_fill
array_fill_keys
array_filter
array_find
array_find_key
array_first

users.Rows |> filter (Age > 25) |> iter (printfn «%s»)

Кстати, насчёт этого seq. Вот есть «system.out.println» в джаве, а есть «echo» в пхп. Тут та же история с огромным преимуществом пхп, ты ему пишешь «печатай» - он печатает. В джаве же «напечатать строку в поток out который часть системного чего-то там». Зачем там уточнять про системность, зачем там уточнять что печатаем в «out»?

Что такое «Age»? Вот как раз для Seq.iter (printfn «%s») не нужно явного объявления функции, потому что колбэк уже является функцией. Но если ты берёшь параметр, то тебе нужно этот параметр правильно обработать. Ну хотя бы ублюдочным this, аля JS — хотя тут сразу возникнет проблема с двумя аргументами.

В общем, я не вижу пока серьёзных аргументов за PHP, пока это выглядит как «я привык писать на PHP».

byko3y ★★★★
()
Ответ на: комментарий от byko3y

Краткий экскурс на тему того, почему на классическом PHP нельзя написал надёжную логику. Я лично сталкивался с тем, что бекендер мне возвращает чёрт знает что — причём, он сам не знает, что он мне возвращает.

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

У меня вот есть одна штука, написанная давно на пхп и время от времени в неё что-то дописывается. Местами в ней находятся всякие «подсунули не массив туда где нужен массив» (и этот не-массив даже не всегда bool false), часть её вывода, вероятно, из-за этого некорректная, но зато всё остальное работает. Да, она работает в 100+ раз медленнее чем если б была написана на Си, страницы открываются местами по минуте (внутри число- и структуро-графо-дролбилка например среди прочего, или рисование графиков с парсингом текстовых «таблиц» на лету). Планирую переписать на Си и аккуратно, с продуманным специализированным хранилищем для данных итд, но уже несколько лет находятся другие, более интересные, дела. А на написание пхп-реализации зато потрачено в 100 раз меньше времени чем если б она была на Си.

говорит, что схему мы автоопределяем из файла.

Ну вот. Почему нельзя просто написать «прочитай csv». Вот просто «прочитай», и не донимай меня всякими схемами, подробностями о которых я не хочу даже думать? Пхп так может, в пхп так везде. Это плюс для скриптов.

А вот это вот из какого языка и насколько оно лаконично?

Это всё одиночные слова. Хоть и с подчёркиваниями в середине. А seq - дополнительный токен.

Что такое «Age»?

Ну слева ж есть массив users.rows, который мы фильтруем, очевидно age это параметр фильтрации. И не надо говорить будто невозможно сделать так, чтобы интерпретатор сам это понял, без синтаксических уточнений. Просто его авторам это показалось некрасивым, они хотят строгий код. Но это плохо для скриптов. Единственное, тут пожалуй может выйти неожиданный эффект от конфликтов внешнего пространства имён и пространства имён полей структуры. Ну, можно как-нить их отличать, но «fun r» для этого писать незачем.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Сколько времени было сэкономлено на остальных разах, когда всё работало хорошо? В первую очередь - скорость разработки при более-менее работе, и только во вторую - фикс оставшихся проблем.

Сэкономлено по сравнению с чем? С Rust? С C/C++? Даже Python такого треша не разрешает — и не особо жалуются, заметь. Pandas тебе ошибку выдаст, если ты наносекунды в милисекунды преобразуешь. Это совсем не про скорость, а про «писатель кода имеет хотя бы малейшее представление, что куда он суёт».

А на написание пхп-реализации зато потрачено в 100 раз меньше времени чем если б она была на Си.

Других ЯП не существует в индустрии?

Это всё одиночные слова. Хоть и с подчёркиваниями в середине. А seq - дополнительный токен.

Ты что, ИИ, чтобы слова по токенам разбивать и жаловаться «а вот тут лишний токен»?

Ну вот. Почему нельзя просто написать «прочитай csv». Вот просто «прочитай», и не донимай меня всякими схемами, подробностями о которых я не хочу даже думать? Пхп так может, в пхп так везде. Это плюс для скриптов.

Именно это оно и делает. А ты как собрался операцию Age > 25 делать? «Авось пронесёт»?

Ну слева ж есть массив users.rows, который мы фильтруем, очевидно age это параметр фильтрации

Фильтрации чего? У массива нет атрибута age. А если у нас массив чисел ­— ты как предлагаешь фильтрацию делать? filter(> 25)?

byko3y ★★★★
()
Ответ на: комментарий от vbr

поиск по QuickJS ничего не нашёл, чем не устроило?

Вероятно у него есть фатальный недостаток.

Tanger ★★★★★
()
Ответ на: комментарий от byko3y

Даже Python такого треша не разрешает

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

и не особо жалуются

Просто те, кому это надо, не пишут на питоне.

Других ЯП не существует в индустрии?

Существуют. Возможно мой пример немного недообъяснён. Да, я не сравнивал написание пхп с написанием на питоне - потому что на питоне я ничего не писал. Я пытался объяснить достоинства пхп сами по себе - в том аспекте, что это именно достоинства, а не недостатки. В питон я смотрел (в чужие скрипты), но не увидел в нём лёгкости пхп.

Ты что, ИИ, чтобы слова по токенам разбивать и жаловаться «а вот тут лишний токен»?

И всё-таки это существенный аспект, не только для ИИ. Конечно, это не единственный критерий. Опять же кстати, в последних версиях пхп начали устраивать всякие (пример условный, я точно не помню с какими конкретно функциями такое видел) mysql::query вместо mysql_query, это плохо с их стороны.

Именно это оно и делает

Нет не именно это, ты аж две строки написал вместо одной, и имя файла упомянул два раза вместо одного. На «просто прочитай этот файл» никак не тянет.

Фильтрации чего? У массива нет атрибута age

Ну мы же фильтруем не массив целиком а строки из него.

А если у нас массив чисел ­— ты как предлагаешь фильтрацию делать? filter(> 25)?

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

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

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

Да, ублюдочная перловая типизация была фундаментальным изъяном PHP и JavaScript. Если в Perl это было оправдано фундаментальными мощными строковыми функциями, то в остальных ЯП это ничем не оправдано — просто плохим дизайном. Заметь, что в питоне многие функции могут принимать разные типы аргументов, но никакие функции не позволяют передавать чёрт знает что чёрт знает куда, да ещё и не выдавать при этом никаких ошибок.

Знаменитая «корректная логика» на PHP:

<?php
$stored_hash = "0e123456789012345678901234567890";
$user_input  = "password123"; 

$submitted_hash = "0e888888888888888888888888888888"; 

if ($submitted_hash == $stored_hash) {
    echo "Access Granted! Welcome, Admin.";
    // Procedural code continues here...
} else {
    echo "Access Denied.";
}
?>

Нет не именно это, ты аж две строки написал вместо одной, и имя файла упомянул два раза вместо одного. На «просто прочитай этот файл» никак не тянет.

— А мозоли на руках у тебя откуда? — CSV файлы в F# открывал.

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

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

byko3y ★★★★
()
Ответ на: комментарий от byko3y

то в остальных ЯП это ничем не оправдано

Верно. Но ты не учёл того, что пхп - не ЯП. Не надо его в такой роли воспринимать, и претензии испарятся. Зато скрипты писать удобнее.

firkax ★★★★★
()
Ответ на: комментарий от firkax

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

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

bread
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.