А если серьезно, то это парадокс блаба, да. И я не знаю, Как вам показать пример без реального приложения, так, чтобы это не выглядело как "да я могу то же самое сделать с помощью Х"
Ну вот в F# есть этот Generic Math. Причём с самого начала он есть, хоть и реализован м.б. и не так красиво как в Haskell или Rust. И никогда ни потребности ни желания его использовать не возникало.
>> и у него не будет вылупливания глаз "ээ что это за язык такой?"
На собесе можно же примерно так спросить: "Тут у нас Haskell кое-где, не слишком часто, но встречается. Обычно я сам его пилю, но мало ли, а вдруг. Я вижу, что у тебя в резюме он не указан. Ты как, непротив посмотреть его на досуге? Ну или не на досуге. Очень интересная штука на самом деле". И если человек не идёт в отказ, то значит проблема решена. А если идёт, то тут уже повод задуматься, а чего это он так незнакомых технологий боится? А если ему, например, PostgreSQL на MongoDB заменить, то также испугается? А стоит ли его вообще брать?
>> Нужно обоснование, почему Х а не Y и так далее А в чём проблема дать такое обоснование, если начальство адекватно? И не обязательно же прям всё писать на Haskell, вызывая ощущение, что вы занимаетесь Job Insurance. Можно же написать только тот кусок, который реально получает выгоду от его системы типов. А потом привязать его при помощи FFI. Так и обосновать проще. Например, у меня большая часть написана на C#, но есть и библиотеки на F#, и никто голову мне за это не открутил (пока).
>> По сути это предложение дать экстешн-методам возможность объявляь виртуальные методы. Почему это воспринимается как "обожемывсеумрем" — ну, не знаю. Как я уже говорил, то же про var говорили, и про многие другие фичи. Видимо, такой естественный ход вещей.
Когда программист с чем то незнаком, то он и не может придумать примеры, зачем бы ему это понадобилось в его текущей работе. Например, я позавчера сидел и думал, зачем бы мне понадобился Generic Math, описанный в этой статье как мегакрутая фишка. И так и не смог придумать ни одного алгоритма, кот. бы его требовал. Все сортировки обходятся без него. А складывать, умножать и делить нужно конкретные типы. Поэтому это всё воспринимается как очередной ненужный висяк на голову, синтаксический трюк, кот. жизнь не облегчает, никогда использовать не будешь, но выучить всё равно будет надо, хотя бы ради собесов.
Мне кажется, что вы уже начали нервничать, раз отвечаете 2-м разным людям в одном комменте :).
Что касается того, как проксировать IList статически, то, думаю, на C# можно воспользоваться генераторами, а на F# - провайдерами типов. А если можно динамически, то через Reflection.Emit или Mono.Cecil, возможно, есть и другие способы, напр. DLR или DispatchProxy, но тут я не специалист. Но, как я понимаю, вопрос не про это. И вы, конечно, можете начать меня сейчас гонять по ограничениям системы типов C#, задавая задачки, например, из мануала Idris'а. И, понятное дело, что я сольюсь, т.к. их возможности несравнимы. Вопрос в другом, нужно ли это всё обычному Васе при написании его сайта или утилиты или игры или чего-там ещё? А если действительно такая потребность возникла, то почему тогда он не перейдёт на более продвинутый инструмент, а продолжает ругать такой плохой C#? ИМО, язык, написанный одновременно для всех - это язык ни для кого, поэтому всегда будут разные языки под разные задачки и интеллектуальные способности программиста, решающего эти задачки.
В текущей парадигме C#, как я понимаю, это не проблема. Реализуется IEnumerable<T>, а как сравнивать коллекции, все уже давно знают: list1.SequenceEquals(list2);
Ну, или если это не сравнение, а что-то более другое, тогда:
public static class EnumerableExtensions { public static bool AreMyWishesFullfilled<T>(this IEnumerable<T>) {...} }
А если всё же хочется через интерфейс, тогда нужен отдельный class MyListEqualityComparer<T> : IEqualityComparer<MyList<T>>
Ну визуально он не очень похож на C#, скорее уж на своего непосредственного родителя - OCaml. Те же Rust или Nemerle на C# гораздо более похожи.
Мне кажется, там проблемы с финансированием группы разработки. Раньше, лет 15 назад, во времена F# 2.0, он на мегапарсеки превосходил C#, и был самым передовым на .NET. Но за все эти 15 лет C# натырил практически всё полезное из F# (LINQ, async, pattern matching...), и в результате последний растратил почти всё своё преимущество. Кое-что осталось, но уже не суть. И теперь началась обратная гонка F# -> C#. И Дон Сайм пытается тянуть разработку вперёд лично, но его сил, кажется, недостаточно.
Например, приведённый пропозал практически единственный хоть сколько-то интересный в следующей версии (хотя и для C# верно то же самое с Generic Math).
>> Т.е. "Мы вам фичу дадим, но у нее куча минусов так шо вы там смотрите осторожно, мб не юзайте вообще"
Ну, тут надо понимать, что имеется в виду под минусами. Насколько я понимаю, это то что оно работает только для inline-функций, никак не поддерживается в .NET или в C#, и несколько загадочный синтаксис вызова функций через SRTP. А не то что оно будет как-то криво работать. Так что для кого-то, это м.б. и минусы, а кому-то и норм. А полноценная поддержка шейпов отложена до появления подобной в C#.
Ну, на .NET есть ещё F#. Там до сих пор не было правда из коробки traits, но в следующей версии собираются запилить вот эту фичу https://github.com/dotnet/fsharp/pull/6805 (сам Don Syme - автор языка, её пилит). Что, как я понимаю, будет фактически реализацией traits, правда только для inline-функций и с упоротым синтаксисом.
Хаскель изначально ресерч язык, хотя с прицелом на индустриальное использование. ИСЧХ все же используют, хотя до популярности обычного оопэ далеко, хоть в двадцатку и входит. А вот раст другое дело — это максимально прагматичный яп, совсем не для энтузиастов, а "рабочая лошадка."
Haskell смущает своей обязательной ленивостью, кот. ИМХО не особо-то на практике и нужна, зато может приводить к утечкам памяти в неожиданных ситуациях. А вот Rust, да, имеет шансы, но пока видел его только в разработке микросервисов для банкинга и HFT. Но всё же он, со своими лайфтаймами, сложнее C#, а основная масса программистов идёт по пути наименьшего сопротивления. И поэтому там, где доп. производительность, предоставляемая Rust'ом, не критична, легче использовать язык со сборкой мусора.
Ну тогда почти всё уже готово. Дело за немногим - довести стандарт до уровня C# 6.0, добавив попутно полезные фичи, нужные всем, типа null checking. Затем популяризовать среди производителей ПО и ввести отдельную сертификацию по нему (экзамены).
И это работает. Проблема с C# только в том, что его стандарт ECMA-2 очень устарел и не актуален. Надо бы обновить. И добавить возможность при необходимости компилятором включать отдельные расширения, как это сделано, например, в Haskell.
Ну не новый язык, а 2 ветки старого. Одна развивается с нормальной скоростью, а в другую только изредка делаются бэкпорты. Это всё же намного легче, чем поддерживать 2 разных языка. Пример из обычных языков: English и Simple English. Последний нужен, например, тем, кто не планирует читать Шекспира или Байрона в оригинале, но иногда смотрит в документацию, чтобы узнать, как вызвать нужную функцию.
Возможно стоило бы разделить C# на 2 ветки c раздельной сертификацией: С# Standard - это где-то уровень C# 6.0 + м.б. какие-то синтаксические плюшки из более старших версий. А вот скажем честно: все эти Span<T>, ref readonly struct, pattern matching не особо и нужны. И вносить потом изменения в этот диалект крайне редко и только самые нужные.
C# Advanced - а вот на нём уже можно экспериментировать сколько угодно, догоняя Haskell по сложности. Тут вам и dependent types и HKT и GADT, и что душа пожелает.
То что эти U начинают плодиться как ненормальные. У вас спокойно может быть 10-20 генерик аргументов из-за этого) Причем которые нужно тянуть с самого верха приложения.
Ну тогда м.б. как-то так: var serializer = IMyDeserializable<T>.Create(); или var serializer = DiContainer.Create<IMyDeserializable<T>>();
а потом вызываем наш метод:
public static T DeserializeFromConsole<T>
(IMyDeserializable<T> serializer) =>
serializer.FromString(Console.ReadLine());
так:
var data = DeserializeFromConsole<T>(serializer);
>> P.S. у меня рендер хабра сожрал почти весь ваш код, пришлось смотреть в консоли F12 :D
В таком случае прошу прощения, у меня код показывается нормально (на FF).
В общем мне стало понятно, что решение с шейпами (трейтами, тайпклассами) тут будет элегантнее, но языки типа Rust и Haskell изначально на них построены, а C# - нет, у него дизайн гораздо ближе к классическому ООП. И тут объясним скепсис разработчиков компилятора - внедрять в язык фичу, ортогональную текущим возможностям, создавая при этом доп. когнитивную нагрузку на мозг программистов, особенно Junior, ну такое себе. Вон выше люди даже по поводу этой фичи из статьи голосуют: "Хватит делать С++", хотя она и проще. Да и в общем наверное правильно: нужно оно редко, обойтись без него можно, а вот изучать все новые фишки языка всё равно придётся, т.к. где-то да встретится, хотя бы на интервью.
А для энтузиастов и так уже есть Haskell, Rust, Idris и что там ещё сейчас популярно... C# всё-таки задумывался как мейнстримный язык и язык-клей. И ИМХО предназначен он не для самых сильных и увлечённых, а скорее для таких, которые будут копать отсюда и до обеда :).
А что мешает написать, например, так: public static T DeserializeFromConsole<T,U>() where U : IMyDeserializable<T> => U.FromString(Console.ReadLine());
То есть, если вам нужны доп. стат. гарантии для работы метода, то вы их явно и указываете в сигнатуре метода.
ИМХО, тут надо плясать от задачи. Если вы разрабатываете технически сложную программу, то на первый план выходят именно hard skills. Если же это очередное CRUD-поделие с 5-ю активными пользователями в час, тогда верно — разработчику будет достаточно минимальных технических навыков, чтобы разобраться и участвовать, и вот тогда уже на первый план выходят другие качества — и бизнес и человеческие, м.б. даже умение веселить коллектив или метко бросать дартс.
Просто по вашей изначальной фразе можно подумать, что пожелания бизнеса важнее, он платит деньги и соответственно диктует что тут и как. И, соответсвенно программисты должны подстраиваться под эти хотелки. Но на практике-то видится совсем другая картина: именно бизнес бегает за программистами, сбиваясь с ног, за любые деньги, обещая немереные плюшки. Среди которых м.б. и творческий процесс в том числе. Так что не всё так плохо, как кажется. Это пока ещё наши программисты научились требовать только денег. А скоро, как только привыкнут к достатку, и этого покажется мало. Тогда среди требований появится и нескучная работа, вот увидите.
Хороший инженеринг, это когда пишется модуль и его может поддерживать специалист любой квалификации, знающий язык программирования
Если модуль выполняет сложную задачу, то и код его будет сложным, и его не сможет поддержать специалист любой квалификации. Такого, чтобы вы могли заткнуть любым школьником дыру после ухода сеньора, не будет, это розовые фантазии работодателя. Инженерный подход тут состоит в том, чтобы простые вещи писать просто, а сложные — на адекватном уровне сложности.
Код это не произведение исскуства, вся суть кода в том чтобы удовлетворить потребность пользователя, пусть даже низменную. А программисты это всего лишь высокоплачиваемый персонал, который обслуживает потребность пользователя. Пользователь с самыми низменными потребностями куда важней программиста.
Вы слишком принижаете эту профессию, полностью отказываясь от её творческого компонента. Дело ваше, но так делать вовсе не обязательно. Для бизнеса важнее функциональные свойства кода, для программиста — м.б. и эстетические. Тут нужно найти взаимоприемлимый баланс между практичностью и красотой, не ударяясь в крайности. И все будут довольны.
У бизнеса свои чаяния, а у программистов — свои. В чём-то они могут совпадать, а в чём-то — отличаться. И тут нельзя сказать, что интересы одной стороны однозначно важнее другой — это будет чрезмерным упрощением. Должен быть некоторый взаимовыгодный баланс. А так-то можно дойти до того, что бизнес вообще был бы не против, чтобы программист работал 24/7 и желательно бесплатно, но такого к счастью не бывает.
Ну вот в F# есть этот Generic Math. Причём с самого начала он есть, хоть и реализован м.б. и не так красиво как в Haskell или Rust. И никогда ни потребности ни желания его использовать не возникало.
Ладно, потом погуглю ещё эту тему :).
>> и у него не будет вылупливания глаз "ээ что это за язык такой?"
На собесе можно же примерно так спросить: "Тут у нас Haskell кое-где, не слишком часто, но встречается. Обычно я сам его пилю, но мало ли, а вдруг. Я вижу, что у тебя в резюме он не указан. Ты как, непротив посмотреть его на досуге? Ну или не на досуге. Очень интересная штука на самом деле". И если человек не идёт в отказ, то значит проблема решена. А если идёт, то тут уже повод задуматься, а чего это он так незнакомых технологий боится? А если ему, например, PostgreSQL на MongoDB заменить, то также испугается? А стоит ли его вообще брать?
А за статью спасибо, почитаю :)
>> Нужно обоснование, почему Х а не Y и так далее
А в чём проблема дать такое обоснование, если начальство адекватно?
И не обязательно же прям всё писать на Haskell, вызывая ощущение, что вы занимаетесь Job Insurance. Можно же написать только тот кусок, который реально получает выгоду от его системы типов. А потом привязать его при помощи FFI. Так и обосновать проще.
Например, у меня большая часть написана на C#, но есть и библиотеки на F#, и никто голову мне за это не открутил (пока).
>> По сути это предложение дать экстешн-методам возможность объявляь виртуальные методы. Почему это воспринимается как "обожемывсеумрем" — ну, не знаю. Как я уже говорил, то же про var говорили, и про многие другие фичи. Видимо, такой естественный ход вещей.
Когда программист с чем то незнаком, то он и не может придумать примеры, зачем бы ему это понадобилось в его текущей работе. Например, я позавчера сидел и думал, зачем бы мне понадобился Generic Math, описанный в этой статье как мегакрутая фишка. И так и не смог придумать ни одного алгоритма, кот. бы его требовал. Все сортировки обходятся без него. А складывать, умножать и делить нужно конкретные типы. Поэтому это всё воспринимается как очередной ненужный висяк на голову, синтаксический трюк, кот. жизнь не облегчает, никогда использовать не будешь, но выучить всё равно будет надо, хотя бы ради собесов.
Мне кажется, что вы уже начали нервничать, раз отвечаете 2-м разным людям в одном комменте :).
Что касается того, как проксировать IList статически, то, думаю, на C# можно воспользоваться генераторами, а на F# - провайдерами типов. А если можно динамически, то через Reflection.Emit или Mono.Cecil, возможно, есть и другие способы, напр. DLR или DispatchProxy, но тут я не специалист. Но, как я понимаю, вопрос не про это.
И вы, конечно, можете начать меня сейчас гонять по ограничениям системы типов C#, задавая задачки, например, из мануала Idris'а. И, понятное дело, что я сольюсь, т.к. их возможности несравнимы. Вопрос в другом, нужно ли это всё обычному Васе при написании его сайта или утилиты или игры или чего-там ещё? А если действительно такая потребность возникла, то почему тогда он не перейдёт на более продвинутый инструмент, а продолжает ругать такой плохой C#? ИМО, язык, написанный одновременно для всех - это язык ни для кого, поэтому всегда будут разные языки под разные задачки и интеллектуальные способности программиста, решающего эти задачки.
В текущей парадигме C#, как я понимаю, это не проблема. Реализуется IEnumerable<T>, а как сравнивать коллекции, все уже давно знают:
list1.SequenceEquals(list2);
Ну, или если это не сравнение, а что-то более другое, тогда:
public static class EnumerableExtensions {
public static bool AreMyWishesFullfilled<T>(this IEnumerable<T>) {...}
}
А если всё же хочется через интерфейс, тогда нужен отдельный
class MyListEqualityComparer<T> : IEqualityComparer<MyList<T>>
where T : IEquatable<T> {...}
Ну визуально он не очень похож на C#, скорее уж на своего непосредственного родителя - OCaml. Те же Rust или Nemerle на C# гораздо более похожи.
Мне кажется, там проблемы с финансированием группы разработки. Раньше, лет 15 назад, во времена F# 2.0, он на мегапарсеки превосходил C#, и был самым передовым на .NET. Но за все эти 15 лет C# натырил практически всё полезное из F# (LINQ, async, pattern matching...), и в результате последний растратил почти всё своё преимущество. Кое-что осталось, но уже не суть. И теперь началась обратная гонка F# -> C#. И Дон Сайм пытается тянуть разработку вперёд лично, но его сил, кажется, недостаточно.
Например, приведённый пропозал практически единственный хоть сколько-то интересный в следующей версии (хотя и для C# верно то же самое с Generic Math).
>> Т.е. "Мы вам фичу дадим, но у нее куча минусов так шо вы там смотрите осторожно, мб не юзайте вообще"
Ну, тут надо понимать, что имеется в виду под минусами. Насколько я понимаю, это то что оно работает только для inline-функций, никак не поддерживается в .NET или в C#, и несколько загадочный синтаксис вызова функций через SRTP. А не то что оно будет как-то криво работать.
Так что для кого-то, это м.б. и минусы, а кому-то и норм. А полноценная поддержка шейпов отложена до появления подобной в C#.
Ну, на .NET есть ещё F#. Там до сих пор не было правда из коробки traits, но в следующей версии собираются запилить вот эту фичу https://github.com/dotnet/fsharp/pull/6805 (сам Don Syme - автор языка, её пилит).
Что, как я понимаю, будет фактически реализацией traits, правда только для inline-функций и с упоротым синтаксисом.
Haskell смущает своей обязательной ленивостью, кот. ИМХО не особо-то на практике и нужна, зато может приводить к утечкам памяти в неожиданных ситуациях.
А вот Rust, да, имеет шансы, но пока видел его только в разработке микросервисов для банкинга и HFT. Но всё же он, со своими лайфтаймами, сложнее C#, а основная масса программистов идёт по пути наименьшего сопротивления. И поэтому там, где доп. производительность, предоставляемая Rust'ом, не критична, легче использовать язык со сборкой мусора.
Ну тогда почти всё уже готово. Дело за немногим - довести стандарт до уровня C# 6.0, добавив попутно полезные фичи, нужные всем, типа null checking. Затем популяризовать среди производителей ПО и ввести отдельную сертификацию по нему (экзамены).
Во многих языках присутствуют стандарты:
C99
C++2x
Fortran88
да миллион их
И это работает. Проблема с C# только в том, что его стандарт ECMA-2 очень устарел и не актуален. Надо бы обновить. И добавить возможность при необходимости компилятором включать отдельные расширения, как это сделано, например, в Haskell.
Ну не новый язык, а 2 ветки старого. Одна развивается с нормальной скоростью, а в другую только изредка делаются бэкпорты. Это всё же намного легче, чем поддерживать 2 разных языка.
Пример из обычных языков: English и Simple English. Последний нужен, например, тем, кто не планирует читать Шекспира или Байрона в оригинале, но иногда смотрит в документацию, чтобы узнать, как вызвать нужную функцию.
Возможно стоило бы разделить C# на 2 ветки c раздельной сертификацией:
С# Standard - это где-то уровень C# 6.0 + м.б. какие-то синтаксические плюшки из более старших версий. А вот скажем честно: все эти Span<T>, ref readonly struct, pattern matching не особо и нужны. И вносить потом изменения в этот диалект крайне редко и только самые нужные.
C# Advanced - а вот на нём уже можно экспериментировать сколько угодно, догоняя Haskell по сложности. Тут вам и dependent types и HKT и GADT, и что душа пожелает.
Ну тогда м.б. как-то так:
var serializer = IMyDeserializable<T>.Create();
или
var serializer = DiContainer.Create<IMyDeserializable<T>>();
а потом вызываем наш метод:
public static T DeserializeFromConsole<T>
(IMyDeserializable<T> serializer) =>
serializer.FromString(Console.ReadLine());
так:
var data = DeserializeFromConsole<T>(serializer);
>> P.S. у меня рендер хабра сожрал почти весь ваш код, пришлось смотреть в консоли F12 :D
В таком случае прошу прощения, у меня код показывается нормально (на FF).
В общем мне стало понятно, что решение с шейпами (трейтами, тайпклассами) тут будет элегантнее, но языки типа Rust и Haskell изначально на них построены, а C# - нет, у него дизайн гораздо ближе к классическому ООП. И тут объясним скепсис разработчиков компилятора - внедрять в язык фичу, ортогональную текущим возможностям, создавая при этом доп. когнитивную нагрузку на мозг программистов, особенно Junior, ну такое себе. Вон выше люди даже по поводу этой фичи из статьи голосуют: "Хватит делать С++", хотя она и проще. Да и в общем наверное правильно: нужно оно редко, обойтись без него можно, а вот изучать все новые фишки языка всё равно придётся, т.к. где-то да встретится, хотя бы на интервью.
А для энтузиастов и так уже есть Haskell, Rust, Idris и что там ещё сейчас популярно... C# всё-таки задумывался как мейнстримный язык и язык-клей. И ИМХО предназначен он не для самых сильных и увлечённых, а скорее для таких, которые будут копать отсюда и до обеда :).
А что мешает написать, например, так:
public static T DeserializeFromConsole<T,U>() where U : IMyDeserializable<T> => U.FromString(Console.ReadLine());
То есть, если вам нужны доп. стат. гарантии для работы метода, то вы их явно и указываете в сигнатуре метода.
Если модуль выполняет сложную задачу, то и код его будет сложным, и его не сможет поддержать специалист любой квалификации. Такого, чтобы вы могли заткнуть любым школьником дыру после ухода сеньора, не будет, это розовые фантазии работодателя. Инженерный подход тут состоит в том, чтобы простые вещи писать просто, а сложные — на адекватном уровне сложности.
Вы слишком принижаете эту профессию, полностью отказываясь от её творческого компонента. Дело ваше, но так делать вовсе не обязательно. Для бизнеса важнее функциональные свойства кода, для программиста — м.б. и эстетические. Тут нужно найти взаимоприемлимый баланс между практичностью и красотой, не ударяясь в крайности. И все будут довольны.