Статьи

Руководство для начинающих по типу принуждения: что такое принуждение?

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

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

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

Согласно Википедии, принуждение определяется следующим образом:

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

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

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

Давайте посмотрим на это более подробно.

Допустим, например, что у вас есть строка с именем example и ее значение равно '5' . В статически типизированных языках вы можете набрать приведение этого захвата к значению строки и преобразовать его в int помощью ряда различных методов.

Предположим, что у нас есть объект Integer с методом parseInt . Метод принимает строку и возвращает значение строки в целочисленном типе данных. Код для этого может выглядеть примерно так:

1
2
3
4
5
6
7
8
string example = ‘5’;
 
Integer myInt = new Integer();
int intExample = myInt.parseInt( example );
 
/* intExample now has the value of 5 (not ‘5’)
 * and example still refers to the string ‘5’
 */

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

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

1
int myInt = (int)example;

Вообще говоря, приведение типов обычно выполняется путем помещения типа, в который вы хотите преобразовать переменную, в скобки перед самой переменной. В приведенном выше примере myInt теперь будет содержать 5 , а не '5' а в example прежнему будет '5' .

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

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

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

В качестве простого примера скажем, что в JavaScript у нас есть две переменные — sName , iAge — где sName относится к имени человека, а iAge — к возрасту человека. Переменные, например, используют венгерскую нотацию просто для обозначения того, что один хранит строку, а другой хранит целое число.

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

Итак, мы продолжим и определим наши переменные и их значения:

1
2
var sName = ‘John Doe’;
var iAge = 32;

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

  1. Сравнивая число с логическим
  2. Конкатенация строки и числа

Давайте посмотрим на пример каждого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
 * Comparing a number to a boolean
 * will result in the boolean value
 * of ‘false’.
 */
var result = iAge == true;
 
/**
 * Concatenation strings and numbers will
 * coerce the number to a string.
 *
 * «John Doe is 32 years old.»
 */
var bio = sName + ‘ is ‘ + iAge + ‘ years old.’;

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

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

Это приведение типа: когда вы берете переменную одного типа и конвертируете ее значение в другой тип при выполнении операции или оценки.

Дело в том, что оба эти примера очень упрощены. Давайте рассмотрим еще несколько, чтобы продемонстрировать, как работает принуждение, по крайней мере в JavaScript, при выполнении операций конкатенации:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
var one, two, result;
 
// one and two refer to string values of ‘1’ and ‘2’
one = ‘1’;
two = ‘2’;
 
// result will contain the string ’12’;
result = one + two;
 
// redefine two to equal the number ‘2’
two = 2;
 
// concatenating a string and a number results in a string
// result will contain ’12’;
result = one + two;
 
// redefine one as a number
one = 1;
 
// then concatenate (or sum) the two values
// result will be 3
result = one + two;

Следует отметить две важные вещи:

  1. Оператор + перегружен. Это означает, что когда он работает со строками, он объединяет их вместе, но когда он работает с числами, он складывает их вместе.
  2. Один тип всегда приведен к другому, и обычно существует иерархия того, как это происходит. Хотя каждый язык отличается, обратите внимание, что во втором примере, когда мы объединяем строку и число, результатом является строка. Это потому, что число приведено в строку.

Чтобы продвинуть пример еще на шаг, давайте добавим еще одну переменную, приоритетный набор операций, а затем рассмотрим результат:

01
02
03
04
05
06
07
08
09
10
var one, two, tree, result;
one = ‘1’;
two = 2;
three = 3;
 
// result is ‘123’
result = one + two + three;
 
// result is ’15’
result = one + (two + three);

Обратите внимание, что во втором примере two и three добавляются вместе, потому что они оба числа, а затем результат объединяется с one потому что это строка.

Ранее мы упоминали, что существует один особый случай для чисел и логических значений, по крайней мере, в JavaScript. И так как это язык, который мы использовали для изучения приведения типов, и так как этот язык часто используется в современной веб-разработке, давайте взглянем.

В случае JavaScript обратите внимание, что 1 считается «истинным» значением, а 0 — «ложным». Эти слова выбраны как таковые, потому что значения могут служить числами, но также будут оцениваться как true или false при выполнении сравнения.

Давайте посмотрим на некоторые основные примеры:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
var bTrue, bFalse, iZero, iOne, result;
 
bTrue = true;
bFalse = false;
iZero = 0;
iOne = 1;
 
// result holds the boolean value of false
result = bTrue == iZero;
 
// result holds the boolean value of true
result = bTrue == iOne;
 
// result holds the boolean value of false
result = bFalse == iOne;
 
// result holds the boolean value of true
result = bFalse == iZero;

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

Но что произойдет, если мы сравним логическое значение true или false со строковым значением one или zero ?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
var bTrue, bFalse, sTrue, sFalse, result;
bTrue = true;
bFalse = false;
sTrue = ‘1’;
sFalse = ‘0’;
 
// result holds true
result = bTrue == sTrue;
 
// result holds false
result = bTrue == sFalse;
 
// result holds false;
result = bFalse == sTrue;
 
// result holds true
result = bFalse == sFalse;

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

Есть смысл? Мы рассмотрим это более подробно в следующей статье, но я хотел бы вначале представить основы этого.

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

Кроме того, некоторые динамически типизированные языки также содержат значения для undefined и для null . Они также поддерживают «истинные» и «ложные» значения, которые, в свою очередь, влияют на то, как мы имеем дело с сравнениями.

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

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