Где-то с тринадцатой версии платформы 1С каждый раз при ставке во временной таблице делает аналайз автоматом. Поэтому в online_analyze и online_analyze.table_type = 'temporary' нет никакого смысла.
list.map!q{ a.x }.filter!q{ a > 0 }.distinct.fold!q{ a + b }
Так вот, в идеальном языке должен быть тип Unit, который занимает 0 байт
Тут я не понял зачем такое нужно. Пример с HashSet — это особенность данной структуры, что какое-то материальное значение должно быть, чтобы отличать наличие значения от его отсутствия. Unit же нематериален. А минимальное материальное значение — тот самый bool. А если нужна максимально плотная упаковка, то есть BitArray:
bool[100500] hashset;
Этот тип должен быть полноценным типом, и тогда компилятор станет проще, а дизайн языка красивее и логичнее.
Ну как полноценный. Ссылку на него не получить, соответственно везде придётся вставлять проверки в духе "если это особый пустой тип".
Сигнатура функции должна описываться как T => U, где T и U — какие-то типы. Возможно, кто-то из них Unit, возможо, кортеж.
Тут есть один нюанс. Иногда кортеж надо передать как один аргумент, без разворачивания. В D для этого есть разделение на тип Tuple и Compile-time Sequence.
auto foo( Args... )( int a , Args args ) // take argument and sequence
{
writeln( a + args[0] + args[1] ); // 6
return args.tuple; // pack sequence to tuple
}
auto bar( int a ) // take argument
{
return tuple( a , 2 , 3 ); // can't return sequence but can tuple
}
void main()
{
foo(bar(1).expand); // unpack tuple to sequence
}
любая конструкция что-то возвращала
Идея, конечно, красивая, и в D такого нет, но такой код меня смущает:
val b = {
val secondsInDay = 24 * 60 * 60
val daysCount = 10
daysCount + secondsInDay
daysCount * secondsInDay
}
Умножение просто висит в воздухе. Сложение тоже висит, но уже ничего не делает.
Кроме того, оказываются очень удобными типы-перечисления, которые могут содержать значения.
Для этого есть шаблоны и их специализация.
alias Result( Res ) = Algebraic!( Res , Exception ); // alias for simple usage
void dump( Result!int res )
{
// handle all variants
auto prefix = res.visit!(
( int val )=> "Value: " ,
( Exception error )=> "Error: " ,
);
writeln( prefix , res );
}
void main()
{
Result!int( 1 ).dump; // Value: 1
Result!int( new Exception( "bad news" ) ).dump; // Error: bad news
Result!int( "wrong value" ).dump; // static assert
}
типы-суммы (Union)
Всё же есть Union (когда одно значение соответствует разным типам одновременно) и Tagged Union (когда единовременно хранится значение какого-то конкретного типа из множества). Тип сумма — это второе.
изменяемая переменная
int foo;
переменная, которую "мы" не можем менять, но вообще-то она изменяемая
const int foo;
Причём менять мы не можем не только само значение, но и любые значения полученные через него. То есть компилятор сам добавляет к получаемым типам атрибут "const".
class Foo { Bar bar; }
class Bar {}
void main()
{
const Foo foo;
Bar bar = foo.bar; // cannot implicitly convert expression foo.bar of type const(Bar) to Bar
}
переменная, которую инициализировали и она больше не изменится.
Тут то же самое, но атрибут immutable:
class Foo { Bar bar; }
class Bar { int pup; }
void main()
{
immutable Foo foo;
foo.bar.pup = 1; // cannot modify immutable expression foo.bar.pup
}
Соответственно, иммутабельные значения компилятор без дополнительных объяснений разрешает передавать между потоками.
На мой взгляд, самый красивый подход используется в D. Пишется что-то вроде static value = func(42); и самая обычная функция явно вычисляется при компиляции.
Cтатики выполняются в рантайме, а для исполнения на этапе компиляции нужно использовать, внезапно, enum.
auto func( int a ) { return a + 1; }
enum value1 = func(42);
static value2 = func(42);
pragma( msg , value1 ); // 43
pragma( msg , value2 ); // static variable value2 cannot be read at compile time
Можно сказать, что это всё синтаксический сахар и вместо этого писать так: но это же некрасиво!
Зато понятно, где имя из области вызова, а где из области объекта. В D такого, конечно, нет. Тут наоборот, язык старается кидать ошибку в случае неоднозначности. Например, в случае обращения по короткому имени, которое объявлено в разных импортированных модулях.
Аналогично extension методы. Синтаксический сахар, но довольно удобный.
Причём вызывать так можно любую функцию.
struct Foo {}
auto type( Val )( Val val )
{
return typeid( val );
}
void main()
{
import std.stdio : dump = writeln;
Foo().type.dump; // Foo
}
Call-by-name семантика
Для этого есть модификатор lazy
void log( Val )( lazy Val val )
{
if( !log_enabled ) return;
// prints different values
val.writeln;
val.writeln;
}
void main()
{
log( Clock.currTime );
}
Фактически компилятор понижает этот код до передачи замыкания:
void log( Val )( Val val )
{
if( !log_enabled ) return;
// prints different values
val().writeln;
val().writeln;
}
void main()
{
log( ()=> Clock.currTime );
}
Ко-контр-ин-нон-вариантность шаблонных параметров
Тут похоже у всех языков всё ещё плохо, и D не искючение.
Имхо, в идеале язык должен позволять явно разрешать неявные преобразования, чтобы они использовались осознанно и только там где необходимо. Например, то же преобразование из int в long.
Этого очень не хватает, да. У компилятора есть свои правила безопасных неявных преобразований, типа int->long, но расширять он их не даёт. Поэтому приходится обкладываться перегрузками операторов, шаблонами и делегированием в духе:
void foo( int a ) {}
struct Bar {
auto pup() { return 1; }; // define pup as getter
alias pup this; // use pup field for implicitly convert to int
}
void main()
{
foo( Bar() );
}
Но хоть и заявлено множественное делегирование, но поддерживается сейчас только одиночное.
Лично мне кажется более интересным третий подход, когда тип (указатель на табличку с методами) хранится прямо в указателе.
Теоретически можно 3 байта отвести на тип, что позволит иметь в рантайме 16М типов. И 5 на смещение, что позволит адресовать 8TB памяти с учётом выравнивания. Пока что должно хватить, но насчёт будущего не уверен.
примитивные типы "притворяются" объектами, так что всё с чем мы работаем выглядит однообразно
Более того, сами типы могут выглядеть как объекты.
long.sizeof.writeln; // 8
есть поля класса, проперти и методы. 3 сущности! Они друг с другом не очень сочетаются, можно объявить несколько методов с одним именем, но разной сигнатурой, но почему-то нельзя объявить проперти с тем же именем.
В D проперти — не более чем методы с 0/1 аргументом.
struct Foo
{
protected int _bar; // field
auto bar() { return _bar; } // getter
auto bar( int next ) { _bar = next; } // setter
}
а те же inline могут быть аннотациями, которые не влияют на логику исполнения кода и лишь помогают компилятору.
Так и всякие "inline, forced_inline__, noinline, crossinline" тоже не более чем аннотации, помогающие компилятору и не влияющие на логику. Только проблема в том, что программисты не умеют ими пользоваться и компилятору приходится на них забивать, так что помощи от них по итогу никакой. Впрочем, в D есть 3 стратегии, которые программист может переключать через pragma:
По умолчанию на откуп компилятору.
Не инлайнить.
Попытаться заинлайнить, а если не получится — выдать ошибку.
В языке должны быть макросы, которые принимают на вход синтаксическое дерево и преобразуют его.
Этого в D нет, но есть в его идейном последователе — Nim. Однако, в D есть мощная система шаблонов, позволяющая покрыть большую часть потребностей. И возможность генерировать код на этапе компиляции, правда в виде строки, что не очень удобно и годится лишь для мелочей. В принципе, трансформеры аст не так уж сложно реализовать библиотекой, ведь на этапе компиляции можно прочитать исходник, распарсить его, прогнать через трансформер, сериализовать и выполнить. Но видио это никому ещё не было нужно. Синтаксис D не самый простой, но и не то чтобы сильно сложный.
разрешить объявлять внутри функций какие-то локальные функции или классы
Это всё можно, даже импортировать другие модули можно локально.
список из не менее чем одного элемента или число, которое делится на 5, но не делится на 3, но я плохо представляю, как подобное можно доказывать в достаточно сложной программе.
Более нативная поддержка этого на уровне языка была бы очень кстати. Особенно не хватает ограничений на диапазоны. Тогда можно было бы ловить переполнения инта на этапе компиляции. А то сейчас компилятор думает, что программист проверит. А программист даже не думает, что у него может быть переполнение.
Сейчас состояние файла лежит на совести программиста, хотя действия с ним (теоретически) можно запихнуть в систему типов и избавиться от части ошибок.
В D для этого есть структура scoped, создающая объект на стеке и обеспечивающая вызов деструктора при выходе из скоупа.
{
auto foo = scoped!Foo();
}
// foo is destructed here
Неоднозначные особенности типа наличия/отсутствия сборщика мусора я не рассматривал, потому что в жизни нужны языки и со сборщиком, и без него.
Поэтому в D можно работать как с ним, так и без него. Многие приятности стандартной библиотеки, правда, зависят от GC.
Где-то с тринадцатой версии платформы 1С каждый раз при ставке во временной таблице делает аналайз автоматом. Поэтому в online_analyze и online_analyze.table_type = 'temporary' нет никакого смысла.
Похоже вы описали чуть улучшенный язык D :-)
Тут я не понял зачем такое нужно. Пример с HashSet — это особенность данной структуры, что какое-то материальное значение должно быть, чтобы отличать наличие значения от его отсутствия. Unit же нематериален. А минимальное материальное значение — тот самый
bool
. А если нужна максимально плотная упаковка, то есть BitArray:Ну как полноценный. Ссылку на него не получить, соответственно везде придётся вставлять проверки в духе "если это особый пустой тип".
Тут есть один нюанс. Иногда кортеж надо передать как один аргумент, без разворачивания. В D для этого есть разделение на тип Tuple и Compile-time Sequence.
Идея, конечно, красивая, и в D такого нет, но такой код меня смущает:
Умножение просто висит в воздухе. Сложение тоже висит, но уже ничего не делает.
Для этого есть шаблоны и их специализация.
Всё же есть Union (когда одно значение соответствует разным типам одновременно) и Tagged Union (когда единовременно хранится значение какого-то конкретного типа из множества). Тип сумма — это второе.
Причём менять мы не можем не только само значение, но и любые значения полученные через него. То есть компилятор сам добавляет к получаемым типам атрибут "const".
Тут то же самое, но атрибут immutable:
Соответственно, иммутабельные значения компилятор без дополнительных объяснений разрешает передавать между потоками.
Cтатики выполняются в рантайме, а для исполнения на этапе компиляции нужно использовать, внезапно, enum.
Зато понятно, где имя из области вызова, а где из области объекта. В D такого, конечно, нет. Тут наоборот, язык старается кидать ошибку в случае неоднозначности. Например, в случае обращения по короткому имени, которое объявлено в разных импортированных модулях.
Причём вызывать так можно любую функцию.
Для этого есть модификатор lazy
Фактически компилятор понижает этот код до передачи замыкания:
Тут похоже у всех языков всё ещё плохо, и D не искючение.
Этого очень не хватает, да. У компилятора есть свои правила безопасных неявных преобразований, типа int->long, но расширять он их не даёт. Поэтому приходится обкладываться перегрузками операторов, шаблонами и делегированием в духе:
Но хоть и заявлено множественное делегирование, но поддерживается сейчас только одиночное.
Теоретически можно 3 байта отвести на тип, что позволит иметь в рантайме 16М типов. И 5 на смещение, что позволит адресовать 8TB памяти с учётом выравнивания. Пока что должно хватить, но насчёт будущего не уверен.
Более того, сами типы могут выглядеть как объекты.
В D проперти — не более чем методы с 0/1 аргументом.
Так и всякие "inline, forced_inline__, noinline, crossinline" тоже не более чем аннотации, помогающие компилятору и не влияющие на логику. Только проблема в том, что программисты не умеют ими пользоваться и компилятору приходится на них забивать, так что помощи от них по итогу никакой. Впрочем, в D есть 3 стратегии, которые программист может переключать через pragma:
Этого в D нет, но есть в его идейном последователе — Nim. Однако, в D есть мощная система шаблонов, позволяющая покрыть большую часть потребностей. И возможность генерировать код на этапе компиляции, правда в виде строки, что не очень удобно и годится лишь для мелочей. В принципе, трансформеры аст не так уж сложно реализовать библиотекой, ведь на этапе компиляции можно прочитать исходник, распарсить его, прогнать через трансформер, сериализовать и выполнить. Но видио это никому ещё не было нужно. Синтаксис D не самый простой, но и не то чтобы сильно сложный.
Это всё можно, даже импортировать другие модули можно локально.
Вот у меня тут есть пара примеров:
Более нативная поддержка этого на уровне языка была бы очень кстати. Особенно не хватает ограничений на диапазоны. Тогда можно было бы ловить переполнения инта на этапе компиляции. А то сейчас компилятор думает, что программист проверит. А программист даже не думает, что у него может быть переполнение.
В D для этого есть структура scoped, создающая объект на стеке и обеспечивающая вызов деструктора при выходе из скоупа.
Поэтому в D можно работать как с ним, так и без него. Многие приятности стандартной библиотеки, правда, зависят от GC.