2019-11-17 00:41:11
В ClojureScript есть type inference, хотя язык динамический, который используется для генерирования externs, вместо того чтобы писать их руками, и для генерирования оптимального кода.
Тип можно определить вручную, с помощью type hints, которые добавляют значение в поле :tag в метаданных этого значения
(def ^number x 1)
Благодаря type inference часто явный type hint не нужен. Компилятор сам распознает и добавит тип значения.
Помимо тегов значений есть теги возвращаемых значений из функции
(defn ^js/Promise http-get [url]
(js/Promise. ...))
Для (.then (http-get url) on-ok) компилятор использует информацию о возвращаемоем типе чтобы сгенерировать extern Promise.prototype.then, который позже будет использован в Closure Compiler для генерирования корректного интероп кода в процессе оптимизации.
Типы возвращаемых значений тоже могут быть автоматически распознаны компилятором. В примере ниже функции f будет присвоен тип string.
(defn f [] "123")
Благодаря тому, что возвращаемые типы пробрасываются через функции, например имея в кодовой базе одну функцию которая возвращает Promise, автоматический externs inference покроет все использования этой функции и компилятор сгенерирует корректный интероп код.
Однако такая цепочка пробрасывания типов может быть прервана выражениями, возвращаемый тип которых компилятор неспособен определить.
Например известно, что (str 1 2) вернёт string, но тип (apply str [1 2]) уже неизвестен. apply здесь — функция высшего порядка, которая вызовет str и тогда уже вернёт строку.
Поэтому я начал работать над патчем для return type inference в функциях высшего порядка. По факту это абстрактное выполнение кода на этапе компиляции, когда компилятор повторяет как конкретная функция отрабатывает в рантайме.
Например зная, что str всегда возвращает строку, можно утверждать, что (apply str xs) тоже вернёт строку.
Интереснее становиться, когда у multiarity функций методы имеют разные типы возвращаемых значений.
(defn f
([a b] (+ a b))
([a b c] (str a b c))
В примере выше f имеет тип #{number string}, но зная количество аргументов на этапе компиляции, можно определить конкретный тип.
Например для (apply f a b c xs) можно утверждать, что возвращаемый тип будет string, но никак не number.
Эту информацию можно использовать чтобы определить несовпадение количества аргументов с арностью методов. Для (apply f a b c d xs) можно точно сказать, что f не имеет метода с арностью 4 или больше.
Type hints можно использовать, чтобы помочь компилятору сгенерировать оптимальный код. В целом разница будет заметна только для hot path кода.
Например в проверках на правдивость механизм приведения типов в ClojureScript отличается от JS. Поэтому тестовое выражение будет обернуто в вызов функции, которая приведет тип согласно этому механизму.
(when x true)
if (cljs.core._truth(x)) {
true
}
Или например аргументы в str будут обернуты в вызов функции которая приводит значение к строке
(str a b "s")
[str(b), str(a), "s"].join("")
Если в первом случае x действительно Boolean и во втором случае a и b строки, то об этом можно сообщить компилятору с помощью type hints и сгенерированный код будет более оптимальным.
(when ^boolean x true)
if (x) {
true
}
(str ^string a ^string b "s")
[a, b, "s"].join("")
331 viewsedited 21:41