Как стать автором
Обновить

Комментарии 38

хотя фундаментальные принципы в основе лежат те же.

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

Получается, вспоминая Чехова, вы в начале повесили ружье, но оно так и не выстрелило на протяжении статьи.

Но возможно это только мне не понятно.

Фундаментальный принцип состоит в том, что семантика вызова специальной формы как-то отличается от семантики вызова функции, а если говорить ещё более общим языком – не всё, что выглядит как функция, является ею. Для ленивого вызова, применяемого в качестве основного механизма, просто могут быть совершенно другие примеры, а не условные выражения.

Я просто отметил, что материал статьи не относится к ленивым вызовам.

Странно конечно называть фундаментальным принципом. Ну и слово "специальная" подразумевает, что оно отличается от основного случая, то бишь в данном случае - вызова функции.

Очень люблю статьи по теории программирования, но не зашло.

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

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

Спасибо, уточнил двусмысленную формулировку.

Они не оперируют таблицей истинности, хотя их результат совпадает с соответствующими функциями, когда те применимы

Можно вот этот момент разъяснить? Если результат совпадает, то это значит, что они оперируют таблицей истинности, разве нет? Или что здесь имеется ввиду?

Они ещё содержат в себе control flow.

Да это понятно. Просто фраза "не оперируют таблицей истинности" звучит странно (но может быть я не прав), как будто бы при вычислении она не используется, хотя она используется. И то что первый или второй операнды могут не вычисляется, это (опять, как мне кажется) не говорит, о том, что таблица истинности не используется. Это лишь говорит, что при вычислении выражения используются свойства логических операций, позволяющие не выполнять ненужных операций.

Каким образом они его в себе содержат? Если я сделаю a && b в каком-нибудь С, то насколько я знаю, пока я не скормлю это в фильтр/цикл оно ровно никак не повлияет на поток исполнения. Ну и таблицы истинности языка фактически содержатся внутри процессора, так что утверждение про отсутвие оных тоже неверно.

Внутри процессора, конечно, содержится много всякой всячины, в том числе и таблицы истинности, но это никак не влияет на тот факт, что вычисление && проводится не по таблице. И не инструкцией AND процессора.

a&&b – это, фактически, сокращённая запись условной операции a?bool(b):0 (кстати, в некоторых языках она и возвращает значение b, а не 1).

И не инструкцией AND процессора.

Как минимум на Intel это вревращается в инструкцию test, которая делает честное AND через железные логические гейты (читай таблицу истинности) в некоторой временной переменной и потом взводит какие-то доп флаги в зависимости от результата. Так что нет, это не сокращённый тернарник. Хотя бы по той простой причине, что тернарник жрал бы больше тактов и мешал кэшам. Учитывая, что логические операции - основа всех процессоров, едва ли на остальных архитектурах имеются какие-то принципиальные отличия для логических операций. Поэтому как семантика использования, так и железная имплементация говорят, что нет там никаких скрытых control flow, если вы их намеренно туда не добавляли.

Второй аргумент && гарантированно не вычисляется, если первый нулевой. Поэтому AND делать не с чем. Этим специальная форма && отличается от операции &.

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

#include <stdio.h>
#include <stdbool.h>

bool a (void) {
  printf ("a\n");
  return false;
}

bool b (void) {
  printf ("b\n");
  return false;
}

int main (void) {
  printf ("%d\n", a()&&b());
  return 0;
}
a
0

a&&b – это, фактически, сокращённая запись условной операции a?bool(b):0 (кстати, в некоторых языках она и возвращает значение b, а не 1).

не знаю как в других языках, а в С/С++ мне кажется точнее будет:

(bool)a? (bool)b:0 

и оказывается что преобразование типов это то же операция, что-то вроде:

(bool)a надо рассматривать как typeToBool(a).

И все таки интересно если вы рассматриваете чисто симантически кодовые конструкции языка, почему вы С-шные дефайны исключаете из рассмотрения, какая разница каким образом обрабатывается конструкция языка с семантической точки зрения?

А если нет разницы, то дефайн для присваивания Аssign(x,y)

#define Аssign(x,y)assignFunc(&x, y)

можно рассматривать как функцию, то есть и в чистом С можно так написать, и то что под этим в конечном итоге будет выполняться будет присваиванием. Какая разница какими средствами языка это реализовано? Мы же рассуждаем о том как, в принципе, можно написать, а не о том как НУЖНО или не нужно писать.

В первом случае преобразование типа не нужно, потому что ? сам конвертирует в bool. Во втором случае я написал условно, так как дело там сложнее, чем просто преобразование типа - любые ненулевые значения заменяются в Си на true.

Что касается макросов, то мы же обсуждаем семантику, а не синтаксис. По синтаксису-то можно подогнать, но семантически это всё равно не станет вызовом функции.

В первом случае преобразование типа не нужно, потому что ? сам конвертирует в bool

насколько я понимаю мы перечисляем операции которые выполняются под оператором &&,

так как дело там сложнее, чем просто преобразование типа - любые ненулевые значения заменяются в Си на true.

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

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

Да, всё верно, я ошибочно подумал, что кастинг из int в bool выполняется формально, как reinterpret_cast.

В том-то и дело, что, кроме результата, есть ещё порядок вычисления.

Чтобы оперировать таблицей истинности, нужно вычислить все аргументы, чего эти специальные формы не делают. И посмотреть на число в соответствующей строчке и столбике таблицы.

То что есть порядок вычислений и не все считается, не говорит, о том, что таблица истинности не используется. Вот, например, есть выражение a && b && c. Мы вычислили значение a и оно равно false. Воспользовавшись таблицей истинности и тем свойством, что считать все остальное не нужно вернули результат сразу. Оперировать таблицей в общем случае, это не значит, что нужно посчитать сначала все операнды. И вот здесь не соответствие между не оперирует, хотя на самом деле вполне оперирует. Мне кажется без таблицы истинности (явно или не явно) нельзя реализовать эти операторы, т.к. таблица истинности это и есть определение операции, просто в табличной форме, разве нет?

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

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

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

То есть грубо говоря && нельзя каррировать в отличие от функции - это вы пытаетесь сказать?

&& формально нельзя каррировать, потому что ей не соответствует никакое выражение в лямбда-исчислении. Но, рассуждая менее строго, её можно "каррировать" по первому аргументу. И даже можно сказать, что фактически именно "каррированием" по первому аргументу и занимается транслятор.

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

А где сформулированный вопрос?

Да вроде в статье один вопросительный знак в качестве знака препинания.

Считайте это синтаксическим сахаром

Это не сахар, это проявляется истинная природа вычислений. Для императивных языков, конечно, можно обойтись без специальных форм (да и без функций вообще, как в ассемблере), но в Лисп-подобных языках специальные формы являются частью самой основы языка (и его формализма в лямбда-исчислении).

Если говорить о языке Си, то операция присваивания тоже является специальной формой. А также, например, функция sizeof.

Кстати, спасибо, что напомнили, дописал.

можно обойтись без специальных форм (да и без функций вообще, как в ассемблере),

кто же в ассемблере без функций обходится, если, конечно, у нас программа не 20 инструкций? Инструкция call это разве не вызов функции? если у вас программа достаточно короткая вы практически в любом языке можете обойтись без функций, насколько я знаю.

операция присваивания тоже является специальной формой

интересно "специальной формой" чего? У меня как-то не получается проследить где вводятся определения терминов которые вы используете.

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

Вы интересную тему подняли, интересно понять к чему вы нас хотите подвести с ней.

можно обойтись без специальных форм (да и без функций вообще, как в ассемблере), 

кто же в ассемблере без функций обходится, если, конечно, у нас программа не 20 инструкций? Инструкция call это разве не вызов функции?

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

операция присваивания тоже является специальной формой 

интересно "специальной формой" чего?

Это просто устоявшийся в функциональном программировании термин. Можно сказать, что специальной формой семантики языка, синтаксически совпадающей с вызовом функции (или, в данном случае, с двуместной инфиксной операцией).

У меня как-то не получается проследить где вводятся определения терминов которые вы используете.

Это справедливое замечание, но, если мы здесь начнём давать определения, статья из “простого” уровня сразу провалится в “сложный”. Функция здесь наиболее общо понимается в математическом смысле, определение можно посмотреть в математической энциклопедии (Общее понятие функции). По-простому говоря, это отображение (морфизм) множества аргументов в множество результатов. Мы имеем право в данном случае для наших целей рассматривать функции и гораздо более узко, а именно как те функции, которые можно определить средствами используемого языка программирования, то есть написать реализующий их код на данном языке.

А специальная форма – это некая конструкция, которая при поверхностном рассмотрении выглядит, как функция, но на самом деле функцией не является по тем или иным причинам. Поэтому, в частности, невозможно написать реализующий её код, используя средства описания функций в языке программирования.

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

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

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

Операцию присваивания в языке Си (являющуюся специальной формой) невозможно переписать в виде вызова функции. Прежде всего потому, что в Си (не C++) все параметры функции передаются по значению, и левую часть присваивания поэтому невозможно передать в виде параметра. Ну и присваивание и в Си, и в C++ имеет много специальной семантики использования своих операндов – приведение типов и т.д.

Исходя из этого, свою функцию Аssign(x,y), формально заменяющую операцию x=y, в Си написать не получится. Даже не трогая полиморфизм.

Исходя из этого, свою функцию Аssign(x,y), формально заменяющую операцию x=y, в Си написать не получится

но можно же написать функцию Аssign(&x,y), формально заменяющую операцию x=y

Интересно почему вы отбрасываете этот вариант, на основании чего? Нельзя считать что это добавленное & это как бы часть имени функции? Или

если идти до конца можно написать:

Аssign(Ref(x),y)

Мне интересно понять зачем нам делать так чтобы все было функцией, какой в этом смысл?

Даже так у вас возникнут проблемы с implicit conversions, non-expression initializer clause и т.п.

Но лежащая на поверхности проблема здесь в том, что вы просто заменили специальную форму = на комбинацию функции Assign и специальной формы & или гипотетической специальной формы Ref (функцию Ref написать невозможно).

Мне интересно понять зачем нам делать так чтобы все было функцией, какой в этом смысл?

Смысла никакого нет, кроме упрощения изложения. Можно ту же самую семантику изложить два раза, в синтаксисе обычной функции и в синтаксисе операции.

Можно ли в принципе в каком-нибудь языке осуществлять присваивание обычной функцией? Можно. Например, в языке Паскаль или Фортран можно первый аргумент нашей функции Assign передавать по ссылке. А в языке Лисп есть функция set, в которой значение первого аргумента вычисляется в имя переменной. Это показывает, что в каких-то языках определённое поведение требует использования специальной формы, а в каких-то нет.

функцию Ref написать невозможно

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

Но задуматься об этом никакому программисту не помешает, тут я согласен.

интересно "специальной формой" чего? 

Ср. квадратичная форма, билинейная форма и т.д.

Синтаксическая конструкция, семантика которой удовлетворяет тайпклассу

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

Это легко заменяется на более длинную цепочку операторов с использованием if else

Всё верно. If-else сам является своего рода специальной формой среди операторов.

В Лисп-подобных языках, где нет операторов, а есть только функции – условные функции представляют собой самые натуральные специальные формы.

операции (operators)

И таки operators это вполне себе операторы. Оператор сложения/вычитания/декартова произведения и т.п.

В математике – да, но в программировании, особенно применительно к императивным языкам, слово operator переводится, как операция, а оператором называется statement. Иное является ошибкой.

В c# для реализации подобных вещей специально ввели operator true и operator false.

Так что всё-таки это синтаксический сахар

Я не знаток C#, но, насколько я понимаю, true и false – это самые обычные функции-предикаты над объектами, используемые при автоматическом преобразовании типа.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории