All streams
Search
Write a publication
Pull to refresh
0
@adjachenkoread⁠-⁠only

User

Send message

Да нормальный рабочий код, только мне опять не понятно зачем здесь лет. Он НИЧЕМ не помогает ни мне ни компилятору. Уберите его и просто посмотрите на код вы никапли не потеряете в читабельности. Ваш пример имел бы больший вес в этой дискуссии если бы второй лет хайдил первый лет. Но как я уже писал выше это была бы локальная переменная изменение которой не влияет на выше объявленную если вы явно это не укажите в двух местах. Если вы посмотрите на мой пример, то увидите что то о чем я говорю приводит к ещё более явному коду чем то что сейчас в расте — чтобы поменять значение переменной верхнего скоупа нужно явно вернуть значение из вложенного скоупа и переопределить переменную верхнего скоупа, тогда как сейчас в расте и других не функциональных языках достаточно внутри цикла присвоить новое значение не локальной переменной == воспользоваться побочным эффектом. По сути то о чем я говорю технически запрещает часть побочных эффектов связанных с присваиванием. Ещё раз, замена присваивания на переопределение технически устраняет часть побочных эффектов как невыразимую тем самым уменьшая возможность ошибиться, но меняет семантику делая ее не ПРИВЫЧНОЙ для тех кто привык использовать побочные эффекты — почти всех программистов на земле. Да я вижу что в силу привычек вы не можете увидеть сейчас преимущества, но для меня оно совершенно очевидно. Я уверен, что мы с вами застанем новые промышленные языки которые сделают присваивание не выразимым, только (пере)определение.

Нет, это не присваивание. Это часть раздельного объявления. Скажите мне пожалуйста какого типа переменная let x; и какое в ней значение ДО присваивания и не забудьте про адрес памяти где она лежит?
нет, оно становится цельным во время компиляции, замените 12 или 13 на строковый литерал или на не целое число и получите ошибку что тип переменной нельзя однозначно вывести.
Ну тут выше был приведён контр пример
let x;
if cond1 {
  x=12;
} else {
  return;
};

может полный компилятор и знает разницу здесь, но вот какой нибудь кастрированный интелисенс/человек уже не может так однозначно сказать, что это здесь присваивание или все же хитромудровыебанная раздельная инициализация.
покажите пожалуйста пример где компилятор(во вселенной где нет разницы в синтаксисе присваивания и объявления) не сможет распознать этой ошибки. Ну т.е. просто двух строчек не достаточно, нужно что бы из примера было явно видно как ожидаемое поведение расходится с реальностью и НЕ является артефактом использования побочных эффектов. за разъяснениями что я имею ввиду смотри habr.com/ru/post/532660/?reply_to=22488148#comment_22488470
Очень интересно как вы выдаёте пример раздельного ОБЪЯВЛЕНИЯ за пример присваивания. let x; НЕ является законченным объявлением, но образует объявление вместе с последующим синтаксисом присваивания.
Я понимаю на что вы намекаете и вашу опечатку заметил, но этот пример мимо. Вы не можете объявить переменную оператором &= это просто не имеет смысла. Т.е. этот пример не вызывает никаких проблем с определением ошибки у компилятора (во вселенной где нет разницы между присваиванием и объявлением). Это просто не валидный код в обоих вселенных.

По мотивам вашего примера можно представить такой код
mask = 0xff;
for x in xs {
  if cond {
    mask = mask & x;
    debug("New mask is {}", mask);
  }
}


Да в моем мире такой код будет иметь иную семантику т.к. mask
в if скоупе будет локальной перменной и на каждом проходе цикла будет битово эндится с неизмненной mask объявленной до цикла. Т.е. в моей вселенной такой пример будет достигаться следующим кодом на расте
mask = 0xff;
for x in xs {
  if cond {
    let localMask = mask & x;
    debug("New mask is {}", localMask);
  }
}


Что бы написать код в моей вселенной с такой же семантикой как у раста придется писать что то типа

mask = 0xff;
mask = for x in xs {
 mask = if cond {
    mask = mask & x; // нет, компилятор не спутает правый mask с левым в этом объявлении - рекурсивного объявления не будет
    debug("New mask is {}", mask);
    mask
  } else { mask }
  // если нужно пробросить локальную версию mask на родительский скоуп то нужно явно об этом попросить и соотвественно в том скоупе явно ПЕРЕОПРЕДЕЛИТЬ
  mask
}


Конечно вы можете сказать, что я сошел сума и так писать не удобно/не правильно и т.д., но это всего лишь ваши привычки. Я лишь показываю что нет НИКАКОЙ концептуальной разницы между присваиванием и объявлением, а все гипотетические сложности которые вы здесь приводите это следствие побочных эффектов которые уже давным давно побежденны в фукнциональных языках. И мне кстати в связи с этим очень странно видеть 0xd34df00d на одной стороне с вами по этому вопросу. Мало того в моей вселенной я могу читать любой код вырванный из любого контекста и ТОЧНО понимать что в нем происходит, а в расте(да что уж почти во всех языках) если я вырву кусок кода из контекста (схлопну цикл в IDE) то я понятия не имею что внутри цикла мой mask меняется — это ИМХО затрудняет понимание кода — так называемая локальность кода/семантики нарушается. Мне нужно видеть больше чем минимум что бы адекватно мыслить о том как этот код работает.
С моим видением дизайна ЯП/компилятора вам НЕ потребуется писать ничего лишнего если вы игнорите возвращаемое значение и в зависимости от ваших предпочтений вы можете видеть или НЕ видеть этот синтаксис при чтении кода как пожелаете. Т.е. для меня это было бы так:
набираю в IDE: foo(message)
IDE авто заменяет на: = foo(message)
я такой, о точно забыл обработать значение, и дописываю
x = foo(message)
или да все правильно мне пох в этом случае на возврат значения
Я думаю вы согласитесь что нет никакой разницы (для результата вызова pint(a)) объявление это или присваивание. Теперь что насчет кода который включает этот скоуп? Если это объявление то все хорошо — никаких проблем, а если это присваивание то это побочный эффект по отношению к локальному скоупу if

Я лично предпочел бы явное разделение побочного эффекта что то типа
a = 10
a = if cond {
   a = 50
   print(a) // нужно быть марсианином что бы ожидать что вывод будет 10
   a
} else { a }
print(a) // результат вывода зависит от cond точно так же как и в вашем оригинальном примере (если заменить b на а)


В этом случае мне не важно была а объявлена до этого момента и это присваивание или это объявление а т.к. любой код который использует а увидит то что я ожидаю — 50 в случае если cond истинна и 10 иначе. Вы даже можете убрать первичное объявление а и заменить его на 10 в else ветке и ничего не поменяется.
Ну, а теперь пожалуйста если не затруднит, покажи мне на КОНЦЕПТУАЛЬНОМ уровне в чем же разница. Если это действительно так, то должен существовать простой, часто используемый шаблонный контр пример, который с лёгкостью демонстрирует разницу.

Я еще раз уточню — присваивание это пере использование ресурсов аллоцированных/ассоциированных с именем для другого «значение» что бы это(значение) не значило.

Объявление это ассоциация/аллокация ресурсов (значения) с именем.

Когда вы присваиваете что то левому операнду, то правый уже существует. Я думаю вы согласитесь что на концептуальном уровне
auto value = Type{xxx};
auto anotherValue = Type{yyy};
value = move(anotherValue);
use(value);

тоже самое что и
auto value = Type{yyy};
use(value);


Я не могу представить программу где бы была невозможна замена ВСЕХ присваиваний на объявления и сохранение поведения программы кроме эффективности использования ресурсов.

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

Т.е. я так и не увидел почему объявление нужно отличать от присваивания. И да я не считаю что шедовинг это проблема, я как раз искренне не понимаю почему это проблема для других людей.
Я вообще и начал с ответа на вопрос почему это проблема, т.к. в с/с++ всем все понятно почему так написано, и что такой код ничего не делает на с/с++ и НЕ МЕНЯЕТ СЕМАНТИКУ это буквально хак который заставляет компилятор заткнутся и не ругаться, что переменная не используется, т.к. она формально используется.

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

Да, я вижу люди утверждают, что такой код на расте плохой/не идиоматичный, другие добавляют, что он и на с/с++ тоже смысла не имеет. Вот с этим спорю уже я — на с/с++ он имеет смысл и встречается довольно часто, т.к. альтернатива синтаксически ещё хуже. На расте я лично тоже вижу в нем смысл и предпочел бы его явному дропу, потому что так короче — это моё персональное предпочтение чем короче тем почти всегда лучше.

Если уж у меня была возможность это поменять я бы смотрел в сторону оператора — destructive move. Ну например как то так
= message

читается как переместить содержимое message в пустую переменную == дропнуть месседж. С таким синтаксисом было бы сложнее сделать ошибку вида
// что то возвращает и дропается
foo(message)

Вместо того что бы втихую игнорить возвращаемое значение компилятор бы ругался, требуя либо объявить переменную
x = foo(message)
либо явно дропнуть ее
= foo(message)

Да, я признаю что это тоже не идеальное решение, т.к. другим людям может быть не важно игнорируется возвращаемое значение или нет, а вот лишний знак равно придётся писать. Но я там на верхнем уровне оставил комментарий, что есть другой путь где и писать = не нужно, но при этом при чтении = будет явно присутствовать. Смотри habr.com/ru/post/532660/?reply_to=22488134#comment_22488090
а что насчёт MSVC/clang? у меня на работе все три в одном проекте используются. Да, конечно, это решение, но сравните сколько усилий требуется, что бы заглушить варнинг для 3 компиляторов с помощью прагм и просто написав имя переменной с точкой запятой.
Я понимаю что не так то просто привести хороший пример, но прошу вас постарайтесь. Я думаю вы знакомы с IIFE идиомой которая на с++ реализуется через лямбду. Т.е. ЛЮБОЙ блок кода заворачиваем в лямбду и тут же ее вызываем. Внутри лямбды скрываем/переопределяем переменную из контекста вызова и вуаля компилятор/программист счастлив — оба не против и не ругаются что в лямбде локальная переменная имеет тоже имя что и в вызывающем коде, но может быть другого типа и/или иметь другое значение.

Если убрать трюк с лямбдой то оба компилятор и конвенционный программист на с++ начинают выть что это ОЧЕНЬ плохо когда локальную переменную скрывают/подменяют в этом куске/блоке/скоупе, т.к
становится важно в блоке какого уровня переменная объявлена. Хочется читать код, а не разгадывать головоломки..
Опять же я хочу обратить ваше внимание на тот факт, что тот же самый код вместе с IIFE НЕ вызывает проблем (хотя я лично ваше мнение ещё не знаю, может для вас и вызывает).

Я специально так делаю на работе уже довольно продолжительное время и заметил что это не вызывает РЕАЛЬНЫХ(мне не известны случаи что бы это привело к багу или жалобе что читать такое сложно) проблем с пониманием у моих коллег. Мало того некоторые спустя несколько месяцев начинают делать так же.
любой цикл может быть заменен на рекурсию (цикл частный случай рекурсии)
void recursion(int n = 0)
{
    if (n >= 42) return;
    recursion(process(n));
}

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

есть такой атрибут [[nodiscard]]. И вот какой нибудь очень талантливый человек решил, что он точно знает, что вот ни в коем случае нельзя игнорировать возвращаемое значение, но вы с ним почему то не согласны. Как предлагаете решать такое противоречие в проекте где ворнинги это ошибка? Что насчёт условной компиляции, где для одной платформы переменная используется, а для другой — нет?

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


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


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


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


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


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

Вот я тоже не понимаю почему люди нуждаются в разделении объявления и присваивания. Если вас не затруднит можете обьяснить почему вы считаете что на концептуальном уровне присваивание не тоже самое что инициализация и одновременное сокрытие переменной с тем же именем объявленной ранее. Я считаю что разницы нет на КОНЦЕПТУАЛЬНОМ уровне, а тот факт что есть разница для ЯП это просто напросто "узаконенная" оптимизация. Эта операция происходит настолько часто что очень важно что бы она работала быстро, поэтому и происходит разделение позволяющее оптимизировать присваивание. Также я считаю, что компилятор мог бы и сам различать когда это оптимизация применима, а когда нет имея единый синтаксис объявления который выглядит как присваивание. Имея два синтаксиса компилятор перекладывает на меня бремя по идентификации мест где эта оптимизация применима. 50 лет назад это решение было рациональным — сейчас нет.

Я точно знаю, что вы отлично разбираетесь в с++, но в вашем примере я аналог message; не нашел вместо этого вы иллюстрируете побочный эффект мува что совершенно точно не тоже самое. Я думаю вы просто уже подзабыли из за долгово неиспользования с++ что единственная разумная причина такого кода на с++ это подавление ворнинга о неиспользуемой переменной. Есть 100500 макросов которые генерируют именно такой код — переменная; #define unused(x) x; Смысл в том что семантика раста в этом случае это дестрактив мув которого нет в с++, но это и есть проблема т.к. в с это просто ничего.

Ну это всего лишь ваше мнение, я прекрасно могу представить что такой код может появиться в раст для оптимизации, например закрыть файл раньше чем завершится скоуп где обьявленна файловая переменная. drop(file); банально сложнее набирать. У меня лично персональная ненависть к скобкам и я бы точно предпочел file;

Information

Rating
Does not participate
Registered
Activity