Иногда бывают такие моменты истины. Они происходят совершенно неожиданно, например, когда я читаю этот твит:
Дэвид является автором менее известного, но отнюдь не менее интересного языка программирования whiley, языка , в котором встроена большая статическая проверка типов. Одной из наиболее интересных особенностей языка Whiley является чувствительная к потоку типизация (иногда также называемая просто типизацией по типу потока), которая наиболее полезна при использовании вместе с объединенными типами. Пример из руководства по началу работы
function indexOf(string str, char c) => null|int: function split(string str, char c) => [string]: var idx = indexOf(str,c) // idx has type null|int if idx is int: // idx now has type int string below = str[0..idx] string above = str[idx..] return [below,above] else: // idx now has type null return [str] // no occurrence
Помните, что другие языки, такие как Ceylon, также знают типизацию , чувствительную к потоку , и даже Java в некоторой степени знает, потому что в Java тоже есть типы объединения!
try { ... } catch (SQLException | IOException e) { if (e instanceof SQLException) doSomething((SQLException) e); else doSomethingElse((IOException) e); }
Конечно, Java-чувствительная типизация является явной и многословной. Мы можем ожидать, что компилятор Java выведет все типы. Следующее должно также проверить тип и скомпилировать:
try { ... } catch (SQLException | IOException e) { if (e instanceof SQLException) // e is guaranteed to be of type SQLException doSomething(e); else // e is guaranteed to be of type IOException doSomethingElse(e); }
Потоковая типизация или чувствительная к потоку типизация означает, что компилятор может вывести единственно возможный тип из потока управления окружающей программы. Это относительно новая концепция в современных языках, таких как Цейлон , и она делает статическую типизацию чрезвычайно мощной, особенно если язык также поддерживает сложный вывод типов через var
или val
ключевые слова!
Статическая типизация JavaScript с помощью Flow
Давайте вернемся к твиту Дэвида и посмотрим, что говорится в статье о Flow:
http://sitr.us/2014/11/21/flow-is-the-javascript-type-checker-i-have-been-waiting-for.html
Наличие использования
length
сnull
аргументом сообщает Flow, чтоnull
в этой функции должна быть проверка. Эта версия делает проверку типа:function length(x) { if (x) { return x.length; } else { return 0; } } var total = length('Hello') + length(null);Поток способен сделать вывод, что
x
не может бытьnull
внутриif
тела.
Это довольно хитро. Подобную предстоящую особенность можно наблюдать в Microsoft TypeScript . Но Flow отличается (или утверждает, что он отличается) от TypeScript. Суть Facebook Flow можно увидеть в этом абзаце из официального объявления Flow :
Проверка типов в Flow включена — вам не нужно проверять весь код сразу. Однако в основе дизайна Flow лежит предположение, что большая часть кода JavaScript неявно статически типизирована; даже если типы не могут появиться где-либо в коде, они думают разработчик как способ обосновать правильность кода. Поток автоматически выводит эти типы везде, где это возможно, что означает, что он может находить ошибки типов, не требуя каких-либо изменений в коде вообще. С другой стороны, некоторые JavaScript-коды, особенно фреймворки, интенсивно используют рефлексию, о которой зачастую сложно статически рассуждать. Для такого динамического кода проверка типов была бы слишком неточной, поэтому Flow предоставляет простой способ явно доверять такому коду и двигаться дальше. Этот дизайн подтвержден нашей огромной базой кода JavaScript на Facebook:Большая часть нашего кода относится к неявно статически типизированной категории, где разработчики могут проверять свой код на наличие ошибок типов без необходимости явно аннотировать этот код типами.
Пусть этот тонет в
большая часть кода JavaScript неявно статически типизирована
еще раз
Код JavaScript неявно статически типизирован
Да!
Программисты любят системы типов. Программисты любят формально рассуждать о своих типах данных и накладывают на них узкие ограничения, чтобы убедиться в правильности программы. В этом вся суть статической типизации: меньше ошибок из-за хорошо спроектированных структур данных.
Люди также любят размещать свои структуры данных в хорошо спроектированных формах в базах данных, поэтому SQL так популярен, и базы данных «без схемы» не получат большую долю рынка. Потому что на самом деле это та же история. У вас все еще есть схема в базе данных «без схемы» , она просто не проверяется на тип и, таким образом, оставляет вам все бремя гарантии правильности.
На заметку: очевидно, что некоторые поставщики NoSQL продолжают писать эти нелепые посты в блоге, чтобы отчаянно позиционировать свои продукты, утверждая, что вам на самом деле не нужна никакая схема, но через эту маркетинговую шутку легко увидеть. Истинная потребность в бесхарактерности так же редка, как истинная потребность в динамической типизации. Другими словами, когда вы в последний раз писали программу на Java и вызывали каждый метод через отражение? В точку…
Но есть одна вещь, которой статически типизированные языки не имели в прошлом, и что у динамически типизированных языков были: средства обхода многословия. Потому что, в то время как программисты любят системы типов и проверку типов, программисты не любят печатать (как печатать на клавиатуре).
Многословие убийца. Нестатическая печать
Рассмотрим эволюцию Java:
Java 4
List list = new ArrayList(); list.add("abc"); list.add("xyz"); // Eek. Why do I even need this Iterator? Iterator iterator = list.iterator(); while (iterator.hasNext()) { // Gee, I *know* I only have strings. Why cast? String value = (String) iterator.next(); // [...] }
Java 5
// Agh, I have to declare the generic type twice! List<String> list = new ArrayList<String>(); list.add("abc"); list.add("xyz"); // Much better, but I have to write String again? for (String value : list) { // [...] }
Java 7
// Better, but I still need to write down two // times the "same" List type List<String> list = new ArrayList<>(); list.add("abc"); list.add("xyz"); for (String value : list) { // [...] }
Java 8
// We're now getting there, slowly Stream.of("abc", "xyz").forEach(value -> { // [...] });
Кстати, да, вы могли бы использовать Arrays.asList()
все время.
Java 8 все еще далека от совершенства, но все становится все лучше и лучше. Тот факт, что мне, наконец, больше не нужно объявлять тип в лямбда-списке аргументов, потому что он может быть выведен компилятором, является чем-то действительно важным для производительности и принятия.
Рассмотрим эквивалент лямбды до Java 8 (если раньше у нас был Streams):
// Yes, it's a Consumer, fine. And yes it takes Strings Stream.of("abc", "xyz").forEach(new Consumer<String>(){ // And yes, the method is called accept (who cares) // And yes, it takes Strings (I already say so!?) @Override public void accept(String value) { // [...] } });
Теперь, если мы сравниваем версию Java 8 с версией JavaScript:
["abc", "xyz"].forEach(function(value) { // [...] });
Мы почти достигли столь же многословия, как и функциональный динамически типизированный язык JavaScript ( я действительно не возражаю против этих пропущенных литералов списков и карт в Java ), с той лишь разницей, что мы (и компилятор) знаем, что value
это тип String
, И мы знаем, что forEach()
метод существует. И мы знаем, что forEach()
принимает функцию с одним аргументом.
В конце концов, все сводится к следующему:
Динамически типизированные языки, такие как JavaScript и PHP, стали популярны, главным образом, потому что они «просто запускались». Вам не нужно было изучать весь «тяжелый» синтаксис, который требовался для классических статически типизированных языков (только подумайте об Ada и PL / SQL!). Вы можете просто начать писать свою программу. Программисты « знали », что переменные будут содержать строки, нет необходимости записывать их. И это правда, нет необходимости записывать все!
Рассмотрим Scala (или C #, Ceylon, почти любой современный язык):
val value = "abc"
Что еще это может быть, кроме String
?
val list = List("abc", "xyz")
Что еще это может быть, кроме List[String]
?
Обратите внимание, что вы все равно можете явно ввести свои переменные, если необходимо — всегда есть такие крайние случаи:
val list : List[String] = List[String]("abc", "xyz")
Но большая часть синтаксиса является «opt-in» и может быть выведена компилятором.
Динамически типизированные языки мертвы
Вывод из всего этого заключается в том, что после удаления синтаксического многословия и трения из языков со статической типизацией использование языка с динамической типизацией абсолютно не дает никаких преимуществ. Компиляторы очень быстрые, развертывание также может быть быстрым, если вы используете правильные инструменты, и выгода от статической проверки типов огромна. ( не верите? прочитайте эту статью )
Например, SQL также является статически типизированным языком, где большая часть трений все еще создается синтаксисом. Тем не менее, многие люди считают, что это динамически типизированный язык, потому что они получают доступ к SQL через JDBC, то есть через конкатенационные строки SQL-операторов без типов. Если бы вы писали PL / SQL, Transact-SQL или встроенный SQL в Java с помощью jOOQ , вы бы не подумали о SQL таким образом, и вы бы сразу оценили тот факт, что ваш PL / SQL, Transact-SQL или Java Компилятор проверит все ваши операторы SQL.
Итак, давайте оставим этот беспорядок, который мы создали, потому что нам лень набирать все типы (каламбур). Удачного набора текста!
И если вы читаете это, члены группы экспертов по языку Java, пожалуйста, добавьте var
и val
, а также чувствительную к потоку типизацию к языку Java. Мы будем любить тебя всегда за это, обещали!