Почему в Kotlin нет явной типизации?

Мне любопытно, почему дизайнеры kotlin считают хорошей идеей отказаться от явной типизации в Kotlin?

Для меня явная типизация не является «болью» при написании на Java (или любом другом строго типизированном языке): все IDE могут помочь мне автоматически ввести мою переменную.

Это добавляет понимания кода (поэтому я не люблю слаботипизированные языки, понятия не имею, с какими переменными имею дело).

Кроме того, и это моя основная проблема, это делает код более подверженным ошибкам.

Пример :
Java: легко идентифицируется как строка, все хорошо

String myPrice = someRepository.getPrice(); // Returns a String
myTextView.setText(myPrice); 

Java: легко идентифицируется как int, запах кода с помощью setText()

int myPrice = someRepository.getPrice(); // Returns an int, code smell !!
myTextView.setText(String.valueOf(myPrice)); // Problem avoided

Котлин: ????

val myPrice = someRepository.getPrice(); // What does it return ?
myTextView.setText(myPrice); // Possible hidden bug with setText(@StringRes int) instead of setText(String) !!

Отсутствие явной типизации в Kotlin — самый большой недостаток Kotlin imo. Я пытаюсь понять этот дизайнерский выбор.

Я на самом деле не ищу «заплатки», чтобы исправить пример / избежать запаха представленного кода, я пытаюсь понять основную причину удаления явной типизации. Это должно быть больше, чем «меньше печатать / легче читать». Он удаляет всего пару символов (один все равно должен написать val / var), а явный ввод в Kotlin все равно добавляет некоторые символы...

Статическая типизация без явной типизации для меня является наихудшим сценарием для работы со скрытыми ошибками / ошибками спагетти: если один класс (скажем, «Репозиторий») меняет тип возвращаемого значения (например, с String на int). При явной типизации компиляция завершится ошибкой в ​​классе, вызывающем «Репозиторий». Без явной типизации компиляция может не завершиться ошибкой, а переменная неправильного типа может «путешествовать» по классам и изменять поведение классов из-за своего типа. Это опасно и незаметно.

Исправить легко; явно введите переменную. Но мы говорим о Kotlin, языке, созданном для игроков в гольф: люди не будут явно вводить свои переменные, так как в kotlin это занимает даже больше времени, чем в Turtle-Java. Ой!

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

fjc 05.03.2019 16:48

Вы можете написать так: val myValue: String = someRepository.getValue()

Alex Shevelev 05.03.2019 16:54

На самом деле это не вина Котлинза, что кто-то подумал, что getValue — это полезное имя метода. Kotlin делает это (и Java добавил аналогичную поддержку), потому что вам в основном не нужен тип в дополнение к тому, как вы инициализируете свою переменную. Если способ инициализации переменной не ясен или неоднозначен, сначала поработайте над ним, так как он может быть и в других местах. И если это не подходит, используйте явный тип, такой как @fjc и предложенный @AlexShevelev: val myValue: String = .....

Tom 05.03.2019 17:00

@Tom Моя проблема с «необязательной явной типизацией» заключается в том, что люди не будут этого делать.

Nino DELCEY 05.03.2019 17:03

Если это действительно плохо для вас, вы можете включить запрос типов переменных в своей среде IDE. Если нет, меняйте IDE.

Cililing 05.03.2019 17:13

Есть функция IDE, которая может вам помочь: подсказки типов. Ctrl/Cmd + Shift + A → Внешний вид: показать подсказки к имени параметра. Это также покажет подсказку типа для каждой локальной переменной, которая не имеет явного типа.

hotkey 05.03.2019 17:48

Если вы не уверены в том, с чем работаете, вам нужны лучшие соглашения об именах методов/переменных.

EpicPandaForce 06.03.2019 02:06

о, Стокгольмский синдром Java-разработчиков. Почти каждый другой язык со статической типизацией имеет эту функцию в течение многих лет, даже динозавр C++.

Mateusz Stefek 06.03.2019 07:18

@GhostCat Хотя я действительно ценю ваш ответ, в вашем ответе нет настоящего «аргумента» или «причины», объясняющей этот преднамеренный выбор. Я до сих пор не знаю, почему Kotlin разрешает неявную типизацию, за исключением того, что «в C++ это есть, поэтому это должно быть в Kotlin». Над дизайном Kotlin было приложено много усилий, чтобы удалить любой неоднозначный код (нулевая безопасность, когда безопасность с перечислением и т. д.), мне кажется нелепым, что они разрешают неоднозначную типизацию (не имеет значения, статическая типизация или нет, в конце дня, даже если компилятор знает, что происходит, это разработчики пишут код).

Nino DELCEY 07.03.2019 14:44

@NinoLenoska, если вам нужны типы в вашем коде, чтобы понять его 9/10 раз, это означает, что вы неправильно называете вещи. В вашем вопросе "getPrice(); // Что он возвращает?" он возвращает цену, это все, что нам нужно знать. Этот метод не должен возвращать int или строку, вот в чем проблема. Если вам нужно строковое представление цены, вы должны вызвать ToString() или что-то подобное.

abc667 09.03.2019 18:32

@abc667 Вы не понимаете. В Android TextView.setText(String) полностью отличается от TextView.setText(int). В java этого легко избежать, потому что несмотря ни на что, вы должны объявить свой тип. В kotlin это необязательно, и люди хотят использовать его для гольфа, поэтому они не будут этого делать.

Nino DELCEY 13.03.2019 09:28

@NinoLenoska ты читал мой комментарий? setText() здесь не проблема. getPrice() должен возвращать объект цены, и вы должны вызвать myTextView.setText(myPrice.toString()); или что-то похожее на toStringRepresentation()

abc667 13.03.2019 18:57

@abc667 abc667 Лично я бы сказал, что более серьезная проблема заключается в том, что Android использует эти целые числа, которые что-то означают в методе myTextView.setText(...). Похоже, они должны были использовать здесь простое перечисление и выполнить преобразование во время компиляции Android.

bradynpoulsen 28.04.2019 20:15
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
9
13
1 728
2

Ответы 2

Прежде всего: у Kotlin типизация статический. Компилятор точно знает, какой тип идет и куда приходит. И вы всегда можете записать это, например

fun sum(a: Int, b: Int): Int {
  val c: Int = 1

Дело в том, что вы можете написать много кода, который просто полагается на том факте, что все, что вы делаете, является статически типизированным, и тип проверяется тоже!

Вам действительно нужно знать, добавляете ли вы два двойных числа, два целых числа или два длинных числа, когда все, на что вы «заботитесь» смотреть, это a + b?

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

Конечно: если вы пишете код так, что люди постоянно обращаются к своей IDE, чтобы IDE сообщала им фактический предполагаемый тип чего-либо, то вы превращаете полезную функцию в проблему!

Настоящий ответ здесь таков: это зависит. Я был во многих обзорах кода, где люди C++ обсуждали использование ключевого слова auto. Было много ситуаций, когда использование auto действительно облегчало чтение и понимание кода, потому что вы могли сосредоточиться на том, «что происходит с переменной», вместо того, чтобы смотреть 1 минуту на ее объявление, пытаясь понять ее тип. Но были также случайные примеры, когда auto достигал прямо противоположного, и было легче следовать «полностью типизированному» объявлению.

И учитывая комментарий ОП: вы должны знать, что делаете в первую очередь. Значение: когда вы пишете код, вы не просто вызываете «любой» метод, который вы найдете для какого-либо объекта. Вы вызываете метод, потому что должны. Вы лучше знать что он делает и что он вам возвращает. Я согласен, когда кто-то торопится, вы быстро вар-назначаете, а затем передаете эту вещь, что мощь приводит к ошибкам. Но для каждой ситуации, когда var помогает создать ошибку, может быть 10 инцидентов, когда это помогает писать более читаемый код. Как говорится: жизнь заключается в балансировании.

Наконец: языки не должны добавлять функции без причины. А разработчики Kotlin тщательно уравновешивают добавляемые функции. Они не добавили вывод типа, потому что он есть в C++. Они добавили его, потому что тщательно изучили другие языки и сочли полезным быть частью этого языка. Возможно неправильное использование языковой функции Любой. Программист должен писать код, который легко читать и понимать, всегда. И когда ваши методы имеют сигнатуры неясно, поэтому простое «чтение» их имен не говорит вам, что происходит, тогда вините в этом имя метода, а не вывод типа!

Как кто-то может сосредоточиться на том, «что происходит с переменной», если он даже не знает, какого она типа, и, следовательно, как он может правильно с ней взаимодействовать?

Nino DELCEY 05.03.2019 17:06

@NinoLenoska знает компилятор, знает IDE, автозаполнение работает в большинстве из них.

fjc 05.03.2019 17:32

Чтобы процитировать Java Вывод типа локальной переменной JEP:

In a call chain like:

int maxWeight = blocks.stream()
                    .filter(b -> b.getColor() == BLUE)
                    .mapToInt(Block::getWeight)
                    .max();

no one is bothered (or even notices) that the intermediate types Stream<Block> and IntStream, as well as the type of the lambda formal b, do not appear explicitly in the source code.

Вас это беспокоит?

if one class (let's say "Repository") changes it return type (from String to int for example). With explicit typing, compilation would fail at the class calling "Repository".

Если в вашем примере есть такие перегрузки, как setText, то

Repository repository = ...;
myTextView.setText(repository.getFormerlyStringNowInt());

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

Stream<Block> stream1 = blocks.stream();
Predicate<Block> pred = b -> { 
    Color c = b.getColor();
    return c == BLUE;
};
Stream<Block> stream2 = stream1.filter(pred);
ToIntFunction<Block> getWeight = Block::getWeight;
IntStream stream3 = stream2.mapToInt(getWeight);
int maxWeight = stream3.max();

И в этот момент вы упрощаете ошибки только из-за снижения читабельности и шанса случайно использовать неправильную переменную.

Наконец, Kotlin создавался не в вакууме: разработчики могли видеть, что когда в C# в 2007 году появился локальный вывод типов, это не привело к серьезным проблемам. Или они могли бы взглянуть на Scala, которая была с самого начала в 2004 году; у него было (и есть) много жалоб пользователей, но локальный вывод типов не является одной из них.

Несколько отличных примеров!

GhostCat 06.03.2019 08:44

Цепные звонки - это совсем другая тема

Nino DELCEY 13.03.2019 09:21

@NinoLenoska Дело в том, что они могут создавать точно те же проблемы, что и вывод локального типа. Так что, если они приемлемы, это также должно быть приемлемо. В вашем примере проблема вызвана не локальным выводом типа, а плохо спроектированными setText перегрузками.

Alexey Romanov 13.03.2019 09:35

Другие вопросы по теме