LINUX.ORG.RU

паттерны для правильного (и типизированного) JavaScript

 ,


0

3

Навеяно горячими стримами Мурыча. Я вот задумался: а ведь действительно, можно обойтись без TypeScript, если придумать удобные паттерны для JavaScript. Не хватает двух вещей:

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

Без остального сахара можно обойтись.

Первое можно решить с помощью optional, как в Java. Можно написать один обработчик для всех типов (с методами getString, getInt и т. д.), или разные. Привязать к синглтону, чтобы мочь глобально отключать проверки в рантайме (например, по флагу в env). Так мы получаем удобные подсказки в редакторе и работающую проверку типов.

Вот с интерфейсами для Object / Array / Set / Map сложнее. Думаю, нужно поэкспериментировать с optional, чтобы на выходе тоже дёргались типизированные методы.

А чтобы получить типизированные интерфейсы для классов, просто наследуемся от типизированного родителя: где на входе и выходе методов optional, а тело просто делает throw new Error('not implemented').

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

Хочется изобрести рабочую методологию для написания больших и запутанных проектов, а не использовать костыли вроде TypeScript. Это не кажется невозможным. Или может она уже есть, а я о ней не знаю? Из известного нравится подход Тимура Шемсединова: чистый JS с *.d.ts декларациями. Но это не совсем то.

★★★★

Последнее исправление: InterVi (всего исправлений: 2)

действительно, можно обойтись без TypeScript

Надо сначала понять, зачем тебе типизация вообще. Чтобы в разработке помогала, ошибки дурацкие подсвечивала и имена дополняла (а когда не надо, под ногами не мешалась) — то чем тебя тупоскрипт не устраивает?

Если же ты хочешь в рантайме что-то валидировать, то для этого тоже есть инструменты, Ajv тот же, который использует JSON Schema или JSON Type Definition для описания данных и может генерировать парсеры/сериализаторы/валидаторы из этих описаний.

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

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

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

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

то чем тебя тупоскрипт не устраивает?

  • избыточность: разработка превращается в приведение типов там, где это совсем не надо
  • кривизна: специфические баги (например, другой контекст), не оптимизированный код
  • нет гарантий: TS что-то «гарантирует» только на этапе трансляции, а рантайм-то без типов

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

А я описал что-то вопиюще сложное? Этот Optional примитивный и строк на 10, я его часто использую для конфигов. Так что этот комментарий верен для TypeScript, а не для моего предложения, которое реализуется простейшими и понятнейшими конструкциями.

Забыл ещё про линтеры написать. Настроенный eslint уже сам по себе делает большую часть преимуществ TS не нужными.

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

избыточность: разработка превращается в приведение типов там, где это совсем не надо

Это же не жаба — типизация опциональная. Не хочешь приводить типы — пишешь any/unknown и вуаля, компилятор не делает тебе беременную голову. (Её тебе сделают QA или девопсы, у которых всё скукожилось прямо на проде, чуть попозже.)

кривизна: специфические баги

Ну в твоём-то костыле багов не будет, ясное дело.

нет гарантий: TS что-то «гарантирует» только на этапе трансляции, а рантайм-то без типов

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

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

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)

Простая демонстрация паттерна для JS, которую легко применять в своих проектах:

class IntValue {
  _intValue

  /**
   *
   * @param {Number} value int value
   * @throws {Error} if value in not int
   */
  constructor(value) {
    if (!Number.isInteger(value)) {
      throw new Error(`${value} is not int`)
    }
    this._intValue = value
  }

  /**
   * get int value
   * @returns {Number} int value
   */
  getInt() {
    return this._intValue
  }
}

/**
 *
 * @param {function} target
 * @returns {IntValue}
 * @throws {Error} if return is not IntValue
 */
function intValueDecorator(target) {
  return function(...args) {
    const result = target(...args)
    if (!Object.prototype.hasOwnProperty.call(result, '_intValue')) {
      throw new Error('return is not type of IntValue')
    }
    return result
  }
}

// example usage

const multiple = intValueDecorator(function(a) {
  return new IntValue(a.getInt() *2)
})

console.log(multiple(new IntValue(2)).getInt())

Входящие аргументы примитивно проверяютя вызовом getInt(), а возвращаемый результат контролируется декоратором intValueDecorator.

Продвинутая демонстрация как можно написать библиотеку:

// in real world it must be use singleton
let overhead = true

class IntOptional {
  _success
  _error
  _intValue

  /**
   *
   * @param {Number} value int number
   * @param {Error|null} error pass Error obj if not success
   */
  constructor(value, error) {
    this._success = false
    this._error = null
    this._intValue = 0
    if (error) {
      this._error = error
    }
    this._intValue = value
    if (overhead) {
      if (!Number.isInteger(this._intValue)) {
        this._error = new Error('value is not int')
      } else if (!error) {
        this._success = true
      }
    } else if (!error) {
      this._success = true
    }
  }

  /**
   * get int value
   * @returns {Number} int value
   * @throws {Error} if not success or wrong type value
   */
  value() {
    if (overhead) {
      if (this._error) {
        throw this._error
      }
      if (!Number.isInteger(this._intValue)) {
        throw new Error('value is not int')
      }
    }
    return this._intValue
  }

  /**
   * check for error on wrong type value
   * @returns {boolean} true if success and false if not (error or wrong type)
   */
  isSuccess() {
    return this._success
  }

  /**
   * get error
   * @returns {Error|null} get error
   */
  error() {
    return this._error
  }

  /**
   * for pretty print
   * @returns {string}
   */
  toString() {
    return this.isSuccess() ? `IntValue: ${this.value()}` : 'IntValue: fail'
  }

  /**
   * check value is {IntOptional} instance of not
   * @param {*} value for checking
   * @returns {boolean}
   */
  static isIntOptional(value) {
    if (!overhead) return true
    if (typeof value !== 'object') return false
    for (const key of [ 'value', 'isSuccess', 'error' ]) {
      if (typeof value[key] !== 'function') {
        return false
      }
    }
    //if (!Number.isInteger(value.value())) return false
    if (typeof value.isSuccess() !== 'boolean') return false
    if (typeof value.error() !== 'object') return false
    return true
  }

  /**
   * check success IntOptional obg
   * @param {*} value
   * @returns {boolean} if value is IntOptional will return result of value.isSuccess()
   */
  static success(value) {
    if (!IntOptional.isIntOptional(value)) return false
    return value.isSuccess()
  }
}

/**
 * decorate function: all of args and return must be IntOptional,
 * will return only {IntOptional} and not throw exceptions
 * @param {function} target function for decorate
 * @returns {IntOptional}
 */
function intOptionalDecorator(target) {
  return function(...args) {
    try {
      for (const arg of args) {
        if (!IntOptional.isIntOptional(arg)) {
          return new IntOptional(0, new Error(`"${arg}" is not IntOptional`))
        }
      }
      const result = target(...args)
      if (IntOptional.isIntOptional(result)) {
        return result
      } else {
        return new IntOptional(0, new Error('a is not IntOptional'))
      }
    } catch (err) {
      return new IntOptional(0, err)
    }
  }
}

// example usage

const plus = intOptionalDecorator(function(a) {
  return new IntOptional(a.value() + 1)
})

console.log('plus example')
console.log(plus(new IntOptional(1)).toString())
console.log(plus('blahblah').toString())
console.log(plus(new IntOptional(1.1)).toString())
console.log(plus(new IntOptional('blahblah')).toString())

function minus(a) {
  if (!IntOptional.success(a)) {
    return new IntOptional(0, new Error('a is not IntOptional'))
  }
  return new IntOptional(a.value() - 1)
}

console.log('minus example')
console.log(minus(new IntOptional(2)).toString())
console.log(minus('blahblah').toString())
console.log(minus(new IntOptional(1.1)).toString())
console.log(minus(new IntOptional('blahblah')).toString())
InterVi ★★★★
() автор топика
Последнее исправление: InterVi (всего исправлений: 1)
Ответ на: комментарий от Nervous

пишешь any/unknown

Во-первых, это не решает остальных проблем, а во-вторых это говнокод.

Ну в твоём-то костыле багов не будет, ясное дело.

Посмотри пример выше. 38 строк кода против монстра TypeScript — где статистически вероятнее нарваться на ошибку?

Ты хочешь от инструмента того, для чего он не предназначен.

Я предельно ясно написал чего хочу и это не имеет отношения к подмене парадигмы.

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

А как я могу писать на Java под браузер? Да и NodeJS тоже удобный стек. Проекты хотя бы без тайп-хинтинга трудно поддерживать в приличном состоянии, это спровоцировало его появление в Python и TypeScript для JS. Но TypeScript это плохое решение, а JSDoc — недостаточное.

Я настраиваю eslint, он покрывает многие вещи. А паттерн optional его дополняет. В результате всё те же удобные подсказки редактора и предсказуемые типы.

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

это говнокод

С точки зрения адептов непременной насильственной статической типизации всего и вся — несомненно. Почему человек не может игнорировать типы там, где от этого нет пользы?

38 строк кода против монстра TypeScript

Когда 38 строк кода научатся всему, что может TypeScript, их станет далеко не 38 %)

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

Почему человек не может игнорировать типы там, где от этого нет пользы?

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

Когда 38 строк кода научатся всему, что может TypeScript, их станет далеко не 38 %)

Такой задачи нет. Следовательно, это флуд и намеренная дезинформация.

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

class IntValue

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

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

это провоцирует писать такой же небезопасный код, как на JS

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

нужны удобные паттерны, которые я предложил

Что лучше — плясать вокруг типов или плясать вокруг паттернов? Я даже прям и не знаю.

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

Там 1 проверка в конструкторе, так трудно прочитать дальше 1 строчки? Наверное, я больше не буду реагировать на такой бред.

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

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

InterVi ★★★★
() автор топика

Еще можно глянуть на дарт, например. Но с ним подзатрахаешься мне кажется. ts - де факто стандарт для ноды. Так что для дарта, наверное придется уйти на flutter.

special-k ★★★
()
Ответ на: комментарий от InterVi

Можно написать декоратор

Учитывай, что в сторонних либах это все работать не будет.

Или идти через @types

А вот это надо чекнуть - ты меня навел на мысль.

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

На беке, предположим, прочтется во время запроса

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

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

Конечно, выразительность что у JSON Schema, что у JSON Type Definition так себе — например, произвольные предикаты в схему не втулить (скажем, чтобы ограничить возраст неотрицательными числами, не большими 123). Вот что-то вроде clojure.spec запилить для жопаскрипта — это было бы круто.

Зато JSON Schema — практически стандарт, много где используется. Кроме Ajv, в OpenAPI/Swagger видел.

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

class IntOptional

Честно говоря, я бы лучше что-то подобное запилил именно для валидации, во имя простоты и открытости (в смысле O в SOLID):

/**
 * Throws an error if the value does not satisfy the given predicate.
 * If environment variable MODE is not 'dev', does nothing.
 * @param {function} predicate
 * @param {*} value
 */
const assertType = (predicate, value) => {
    if (process?.env?.MODE !== 'dev') return;
    if (!predicate(value)) {
        throw new Error(
            `type assertion error: value '${value}' does not satisfy type predicate '${predicate}'`
        );
    }
};

// usage example (use `MODE=dev node assert.mjs`)

console.log('mode:', process.env.MODE);

// basic usage

const isNumber = x => !isNaN(x);
const isPositive = x => x > 0;
const isOdd = x => x % 2 !== 0;
const isSmall = x => x < 123;
const isSmallPositiveOddNumber = x =>
    isNumber(x) && isPositive(x) && isOdd(x) && isSmall(x);

assertType(isSmallPositiveOddNumber, 19);
console.log('19 is a small positive odd number');

try {
    assertType(isSmallPositiveOddNumber, 12);
    console.log('12 is a small positive odd number');
} catch (e) {
    console.log('12 is NOT a small positive odd number:');
    console.log(e.message);
}

// dynamic validation of function arguments and return value
// (in development mode only)

/**
 * Returns the sum of two small positive odd numbers, that
 * should itself be small. In dev mode, throws if the arguments
 * or the return value do not satisfy type constraints.
 * @param {number} x
 * @param {number} y
 */
const myAdd = (x, y) => {
    assertType(isSmallPositiveOddNumber, x);
    assertType(isSmallPositiveOddNumber, y);
    const sum = x + y;
    assertType(isSmall, sum);
    return sum;
};

console.log('adding 11 and 39:');
console.log(myAdd(11, 39));
try {
    console.log('adding 12 and 99:');
    console.log(myAdd(12, 99));
} catch (e) {
    console.log(e.message);
}
try {
    console.log('adding 55 and 77:');
    console.log(myAdd(55, 77));
} catch (e) {
    console.log(e.message);
}
$ MODE=dev node assert.mjs

mode: dev

19 is a small positive odd number
12 is NOT a small positive odd number:
type assertion error: value '12' does not satisfy type predicate 'x =>
    isNumber(x) && isPositive(x) && isOdd(x) && isSmall(x)'

adding 11 and 39:
50

adding 12 and 99:
type assertion error: value '12' does not satisfy type predicate 'x =>
    isNumber(x) && isPositive(x) && isOdd(x) && isSmall(x)'

adding 55 and 77:
type assertion error: value '132' does not satisfy type predicate 'x => x < 123'
Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 3)
Ответ на: комментарий от Nervous

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

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

то есть typescript рулит… хотя я к вебщине не имею ни малейшего отношения.

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

вы хотите чтобы ваш JS обламывался в браузере домохозяйки

Для браузера можно тоже только в development/testing mode это всё включать.

то есть typescript рулит

Так он в рантайме не работает жи. Получишь ты с бекэнда нормальные данные или говняшку — он знать не может.

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

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

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

ассерты смотрятся лучше декораторов и кода вышло меньше.

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

С сообщениями об ошибках тоже надо чего-то сделать — непонятно, в каком именно месте составного предиката фейлится и по какой причине. Это уже требует, наверное, движения в сторону спецификаций (as in *.spec), собираемых комбинированием отдельных предикатов/других спецификаций разными способами (or, and, map, sequence, regex, tuple, …), с включением метаданных для генерации понятных сообщений, единым интерфейсом для валидации/приведения/генерации данных и т.д.

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

Старый интернет про него тоже знает, в том числе ЛОР. В видосах он очень много говорит про спецификацию, что крайне полезно, потому что если эту самую спецификацию не знать — придётся её выстрадать опытным путём. JS сильно отличается от других языков, он только кажется простым, а на самом деле такой же сложный, как Си. Благодаря тому, что макаки таких вещей не понимают, мы имеем SPA с потреблением всех ресурсов машины (например, поделия Atlassin и фейсбука).

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

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

куда ни тыкал в «почему ооп умирает»… так и не нашел «почему ооп умирает», а все какая-то бодяга про стандарт js, и эпические битвы вокруг терминологии.

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

языки програмирования это с++, java, ада и все такое.

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

функция скриптового языка - расширение или специализация уже готовой программной системы.

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

если коротоко - язык общего применения рассчитан(или должен быть рассчитан) на командную разработку систем в N миллионов строк, эффективных по затратам ресурсов.

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

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

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

https://262.ecma-international.org/14.0/

This Ecma Standard defines the ECMAScript 2023 Language. It is the fourteenth edition of the ECMAScript Language Specification. Since publication of the first edition in 1997, ECMAScript has grown to be one of the world’s most widely used general-purpose programming languages. It is best known as the language embedded in web browsers but has also been widely adopted for server and embedded applications.

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

функция скриптового языка - расширение или специализация уже готовой программной системы.

Когда веб-сервер пишется на Node.js — это расширение готовой программной системы?

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

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

если коротоко - язык общего применения рассчитан(или должен быть рассчитан) на командную разработку систем в N миллионов строк, эффективных по затратам ресурсов.

Где можно увидеть пруф на этот критерий? Язык общего назначения согласно своему названию должен позволять писать программы для разных предметных областей.

быстро намахать некий алгоритм или прототип в одиночку, не особо считаясь с ресурсами и эффективностью

Это можно сделать на любом языке программирования. Алгоритмы по большей части ортогональны императивным языкам.

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

Когда веб-сервер пишется на Node.js — это расширение готовой программной системы?

«вебсервера» пишутся на всем. особенно когда это «все» вызывает сишный или плюсовый код из своего рантайма или либ.

сходите на github node js - и увидите что там 21 процент c++ кода. покажите хоть один плюсовый фреймворк, где есть 21 процент кода на жабаскрипте.

это и показывает что есть скрипт, а что есть реальный язык програмироания общего применения.

давайте просто спросим - на чем написан рантайм жабоскрипта?

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

сходите на github node js - и увидите что там 21 процент c++ кода

OpenJDK на 13,7% состоит из плюсов и на 7,2% из си. Почему же вы считаете джаву «языком программирования»?

Рантайм .NET состоит из плюсов на 10,6% и на 8% из сишечки.

Хаскелевский GHC на 9% состоит из сишного кода. Он более «язык программирования», чем джава и шарп?

Даже Steel Bank Common Lisp на 7,6% — сишечка.

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

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

Почему же вы считаете джаву «языком программирования»?

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

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

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

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

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

То есть до сегодняшнего дня вы и не подозревали, что виртуальная машина джавы не написана на джаве?

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

это не очевидный, а высосанный из пальца тезис

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

это не очевидный, а высосанный из пальца тезис

вы до сих пор будете называть языки, вся требуха которых написана на другом языке и не может быть в принципе написана на нем самом - «языком общего применения»?

тогда примените его к своему же рантайму. или как-то «альтернативно» толкуйте слово «общий».

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

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

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

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

вы до сих пор будете называть языки, вся требуха которых написана на другом языке и не может быть в принципе написана на нем самом - «языком общего применения»?

Почему нет-то. Тут критерий — что на нём можно писать, а не на чём написан он сам. Ключевое слово «применения».

Nervous ★★★★★
()
Последнее исправление: Nervous (всего исправлений: 1)