LINUX.ORG.RU

D, Go и Rust, взлетит ли что-нибудь?

 , , , ,


4

8

Привет, LOR. На данный момент в окружающее пространство уже некоторое время накатывает следующая мысль: «Разработчикам прикладного ПО, использующим в своей практике Си и C++, крайне необходимо облегчить жизнь, избавив от ошибок с памятью и предоставив удобные механизмы для параллельного программирования». Одни адепты, этакие Базаровы от программирования, предлагают воплощать задумку с помощью новых языков: D, Go и Rust. Другие же, коих пока явно больше, всячески не желают выходить из своей зоны комфорта, предлагая включать необходимое в новые стандарты уже используемых инструментов.

Как думаешь, садиться ли уже сейчас за изучение одного из убийц Си/C++, чтобы через 5 лет не оказаться на обочине индустрии, или же все продолжит идти в старом русле с незначительными вливаниями новшеств?

Ответ на: комментарий от anonymous

Но я могу не обработать ошибку и продолжить вычисления?

Если не используешь возвращаемое значение - да, конечно.

tailgunner ★★★★★
()
Ответ на: комментарий от forCe

То, что было до введения исключений - коды возврата. Решение rust мало чем от них отличается, к сожалению.

Отличается хотя бы тем, что это общепринятое решение для всего кода. А синаксический сахар, это еще и удобство. И это честное zero cost abstraction, а не как в плюсах, где оно zero, при условии, что эксепшены не кидаются, тут хоть обкидайся.

dizza ★★★★★
()
Ответ на: комментарий от eao197

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

что мешает пергруженному оператору проверять аргументы?

anonymous
()
Ответ на: комментарий от tailgunner

Решение Rust отличается от кодов возврата принципиально. Ты просто не можешь случайно использовать код возврата в роли значения и, при минимально разумном конструировании API, ты не можешь использовать неинициализированно возвращаемое значение.

не отличается. просто кодов возврата всего 2.

anonymous
()
Ответ на: комментарий от anonymous

что мешает пергруженному оператору проверять аргументы?

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

let (|) = function | Some x as arg -> (function f -> f arg)
                          | None -> function _ -> None;;

смысл надеюсь понятен.

anonymous
()
Ответ на: комментарий от eao197

Это у вас None будет информировать об ошибке-то?

а почему нет? если у вас operator| принимает сущность IPipe с функцией bool valid() const то что ему мешает проверять аргументы на валидность? если же он у вас всеяден, то да только эксепшены.

anonymous
()
Ответ на: комментарий от anonymous

В плюсах вообще не обязательно иметь IPipe с функцией bool valid(). Это может быть объект типа L с R operator()(M&), где L — это анонимный тип, сгенерированный самим компилятором.

eao197 ★★★★★
()
Ответ на: комментарий от dizza

исключения то же zero cost abstraction, даже по вашему определению этого термина.

тут хоть обкидайся.

Тут ничего не кидается. Соответственно и ловить нечего. Можно пропустить ошибку точно так же, как и в случае кодов возврата. Исключения более надежны.

anonymous
()
Ответ на: комментарий от eao197

В плюсах вообще не обязательно иметь IPipe с функцией bool valid(). Это может быть объект типа L с R operator()(M&), где L — это анонимный тип, сгенерированный самим компилятором.

тем не менее суть моего предложения, надеюсь, понятна.

anonymous
()
Ответ на: комментарий от tailgunner

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

Исключения являются хорошим средством, не понимаю, ради чего от них отказываться.

anonymous
()
Ответ на: комментарий от anonymous

Значит это такой же отстой, как и коды возврата.

Значит, по сравнению с кодами возврата Си, это щастье с большой буквы Щ.

Кстати, в Rust скорее type 'a 'b ret = Ok of 'a | Error of 'b

tailgunner ★★★★★
()

Как думаешь, садиться ли уже сейчас за изучение одного из убийц Си/C++, чтобы через 5 лет не оказаться на обочине индустрии

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

или же все продолжит идти в старом русле с незначительными вливаниями новшеств?

И это тоже. Потому что ленивых каждые 5 лет переобучаться — ты не один такой, вас ленивых много. Отсюда популярность С++ растёт, из-за старпёров, которым лень учиться новому. А новые языки в первую очередь, более технологичны, во-вторую: всё-таки главные принципы, модели памяти и т.п. переосмысливаются и ограничения потихоньку устраняются. Просто надо «выйти из зоны комфорта», да. А ещё лучше — выбрать не сорта г braces languages, а учить принципиально новый язык каждые полгода, и писать на нём учебный проектик — дабы дурь и фишка видна была. Чем более языков ты знаешь — тем более ты человек. А чем более ЯП знаешь — тем более тыжпрограммист.

anonymous
()
Ответ на: комментарий от anonymous

Вот у нас есть некая функция, которая нужна ради своих побочных эффектов(ввод/вывод какой-нибудь)

И почему она не может вернуть результат, помеченный warn_unused?

Исключения являются хорошим средством, не понимаю, ради чего от них отказываться.

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

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

Исключения являются хорошим средством, не понимаю, ради чего от них отказываться.

Я вот предположу, что и в Go с возвратом нескольких, и в Rust с Maybe/Just от них отказываются вынужденно. Потому что скрестить исключения с асинхронностью и сопрограммами довольно сложно. А вот сопрограммы/корутины, конечные автоматы, CPS, асинхронность, и например рестарты в стиле CL вместо исключений — довольно запросто (первые три вообще реализуются друг через друга, асинхронность с нормальной моделью памяти и panic который нерестартуемый тоже довольно просто, рестарты с разделением стратегии обработки ошибок и места обработки — тоже можно реализовать поверх этого. Потому что исключения становятся контролируемыми, в духе рестартов — разделения на стратегию и реализацию.

Ошибки это значения, ага.

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

anonymous
()
Ответ на: комментарий от anonymous

тем не менее суть моего предложения, надеюсь, понятна.

Понятна. Это ничем не отличается от того, что я сказал выше: «Это всего лишь будет означать перемещение всех if(r.error) из одного места конструирования конвейера в разные места.» Просто «разные места» вы объединили в один оператор|.

Однако, после того, как этот operator| отработает, его возвращаемое значение ведь все равно нужно будет обработать. Так что в случае, если ошибок не было, то проход по всем if-ам состоится в обязательном порядке.

И это не говоря уже о том, что если в месте конструирования pipeline мы не можем обработать ошибку, то нам нужно будет экземпляр Maybe/Either отдавать наверх. Т.е. писать что-то вроде:

either<ok, error> process_input(input_stream input)
{
  auto r = input.get_message_stream() | filter(...) | ...;
  match(r) {
    case pipeline :
      return pipeline.run();
  }
  return r;
}
Вместо:
void process_input(input_stream input)
{
  auto pipeline = input.get_message_stream() | filter(...) | ...;
  pipeline.run();
}

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

исключения то же zero cost abstraction,

Нет. Исключения, если их кидать - имеют стоимость. Возврат кода вместе с результатом в принципе тоже имеет стоимость, но с практической точки зрения она около нуля.

Можно пропустить ошибку точно так же, как и в случае кодов возврата. Исключения более надежны.

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

dizza ★★★★★
()
Ответ на: комментарий от anonymous

Так где обработка ошибки? Мы же её просто замалчивать будем в этом случае.

какого плана обработка ошибок нужна? если ты просто применил x | f и оно вернуло None, то и обрабатывать нечего. если нужно внутри каскада x | f | g обрабатывать ошибку, то что мешает сделать еще проще?

let (|>) x f = f x

let result = 
    x
 |> f
 |> function | None -> begin print_endline "f() failed"; None end
             | x -> x
 |> g

anonymous
()
Ответ на: комментарий от anonymous

Я вот предположу, что и в Go с возвратом нескольких, и в Rust с Maybe/Just от них отказываются вынужденно. Потому что скрестить исключения с асинхронностью и сопрограммами довольно сложно.

По крайней мере в Rust о такой причине я не слышал. Да насчет Go сильно сомневаюсь - там тоже нормальный CSP, а не эта недоделанная асинхронность.

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

А хаскель zero cost abstractions не обещает )) Вообще труъ парни юзают спец. типы. Например в Scala есть доставшиеся от богомерзкой жабы исключения, но труъ используют Scalaz и дизьюнкцию Error \/ Result для результата.

dizza ★★★★★
()
Ответ на: комментарий от dizza

Вы это серьезно? Записывать математические выражения вот так:

// if `div` "fails", then `DivisionByZero` will be `return`ed
let ratio = try!(div(x, y));

// if `ln` "fails", then `NegativeLogarithm` will be `return`ed
let ln = try!(ln(ratio));

sqrt(ln)

Вместо нормального:

sqrt( ln( div(x, y) ) );

???

С вычислительными задачами кода-нибудь дело приходилось иметь?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Вы это серьезно?

Это учебный пример.

С вычислительными задачами кода-нибудь дело приходилось иметь?

Библиотеки для вычислительных задач будут выглядеть как-то так: http://nalgebra.org/ Но да, там внутри panic!

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

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

Я более-менее согласен с подходом умных чуваков из битсквида: http://bitsquid.blogspot.ru/2012/01/sensible-error-handling-part-1.html. И в ржавчине этот подход еще проще использовать.

ozkriff
()
Последнее исправление: ozkriff (всего исправлений: 1)
Ответ на: комментарий от ozkriff

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

Ну все, пипец, истина в последней инстанции проглаголена.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Это у вас None будет информировать об ошибке-то?

Кто мешает вместо None использовать объект (структуру) любой сложности?

ovk48 ★★★
()
Ответ на: комментарий от hobbit

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

Дык он развивается до сих пор, новые стандарты разрабатываюся

http://en.wikipedia.org/wiki/Fortran#Fortran_2015

annulen ★★★★★
()
Ответ на: комментарий от ovk48

Кто мешает вместо None использовать объект (структуру) любой сложности?

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

eao197 ★★★★★
()
Ответ на: комментарий от ozkriff

Просто не нужно исключения везде совать. А то тонны говнокода с

while(true) {
    try {
    ...
    } catch( blabla ) { blabla; }
}

А потом жалуются, что исключения тормозят. Разумеется тормозят. Исключения - это исключительные ситуации (с) КЭП. Если в программе всегда исключительные ситуации, то что-то в консерватории не так. Если человек этого не понимает, что он на любом языке и любым способом будет говнокодить.

anonymous
()
Ответ на: комментарий от eao197

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

вот только в том же окамловском Async'e ловить исключения нужно специальным костылем. в других языках думаю ситуация похожая.

anonymous
()
Ответ на: комментарий от anonymous

вот только в том же окамловском Async'e ловить исключения нужно специальным костылем. в других языках думаю ситуация похожая.

А можно раскрыть подробнее?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

И не нужно протаскивать эти структуры через сигнатуры функций.

Дык это не просто протаскивание структур. Either, как и все монады, обладает семантикой «последовательных вычислений с контекстом». Т.е.:

- все вычисления, связанные друг с другом в «конвейер», гарантированно выполнятся, нет нарушений или прерываний control flow;

- если вместо тупого None / Left <error> чуть усложнить контекст, то появляется возможность неявно накапливать информацию с каждого шага «конвейера». Например, если ошибки возможны на каждом этапе некоторого вычисления, то есть возможность собрать их все в одну коллекцию без того, чтобы городить вложенные try/catch и т.д.

ovk48 ★★★
()
Ответ на: комментарий от dizza

А если рассматривать Rust как замену Си, чем он не соответвует архитектурным особенностям условной железки?

только немного трогал палочкой Rust, поэтому не то чтобы специалист говорить за него

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

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

ну и из личного опыта, если надо быстро набросать что-то небольшое, что умеет тормошить железку, как правило Си использовать удобнее всего

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

shty ★★★★★
()
Ответ на: комментарий от eao197

А можно раскрыть подробнее?

функции в Async'e шедулятся, а не напрямую вызываются. например:

g >>= f >>= h

запланирует g, когда результат ее вычисления будет доступен то будет запланирована f с аргументом равному возвращенному значению из g и т.д. так вот, try g >>= f >>= h with _ -> ... тут не отработает, т.к. экспешен бросится внутри шедулера. поэтому в async'e есть костыль в виде try_with который протащит эксепшн из шедулера.

anonymous
()
Ответ на: комментарий от shty

тащат за собой рантайм?

у go и ocaml он не такой уж и жирный вроде (если не для встраиваимых систем их рассматривать).

anonymous
()
Ответ на: комментарий от eao197

Ну все, пипец, истина в последней инстанции проглаголена.

Я разве претендую? Вроде специально даже обратное написал, для особо умных. Чего ты наезжаешь?

Кстати

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

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

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

ozkriff
()
Ответ на: комментарий от ovk48

все вычисления, связанные друг с другом в «конвейер», гарантированно выполнятся, нет нарушений или прерываний control flow;

Кем это гарантируется? Вот, например, фрагмент только что написанного кода:

while( m_active_requests.size() < requests )
{
	auto id = ++m_last_id;
	m_active_requests[ id ] = request_smart_ptr_t(
		new request( so_direct_mbox(), id, random( 10, 500 ) );
}
В run-time здесь только по bad_alloc можно вылететь, как минимум, в двух местах. А если еще и тип m_last_id будет посложнее или за random-ом будет скрываться какая-то хитрая механика, то и другие исключения в run-time возможны.

Так кто сможет гарантировать отсутствие хотя бы bad_alloc-а в run-time?

- если вместо тупого None / Left <error> чуть усложнить контекст, то появляется возможность неявно накапливать информацию с каждого шага «конвейера».

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

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

eao197 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.