Статьи

Неудобная правда о динамической и статической типизации

Иногда бывают такие моменты истины. Они происходят совершенно неожиданно, например, когда я читаю этот твит:

Дэвид является автором менее известного, но отнюдь не менее интересного  языка программирования 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. Мы будем любить тебя всегда за это, обещали!