Гибкие записи в статике
Feb. 21st, 2013 02:32 pmРебе
metaclass уже не первый раз поднимает эту тему: можно ли иметь строгую типизацию записей, но не объявляя их заранее? Вот пара примеров из используемых мною языков.
Первый пример: Haxe.
Язык со статической типизацией. Где типы не указаны, компилятор сам их выводит. Тут функция makeData производит значение анонимного типа с парой полей типа Int и String (тоже выведены). Другая функция использует данные из такой записи. Напрямую одна другую даже не вызывает. Все компиляется и работает гладко. Теперь если заменить строчку
var y:String = data.b;
на
var y:String = data.c;
то компилятор скажет:
C:\vc\player\src/Main.hx:876: characters 10-11 : { b : String, a : Int } should be { c : String, a : Int }
C:\vc\player\src/Main.hx:876: characters 10-11 : { b : String, a : Int } has no field c
C:\vc\player\src/Main.hx:876: characters 10-11 : For function argument 'data'
А если там поставить
var y:Bool = data.b;
то скажет
C:\vc\player\src/Main.hx:876: characters 10-11 : { b : String, a : Int } should be { b : Bool, a : Int }
C:\vc\player\src/Main.hx:876: characters 10-11 : Invalid type for field b :
C:\vc\player\src/Main.hx:876: characters 10-11 : String should be Bool
C:\vc\player\src/Main.hx:876: characters 10-11 : For function argument 'data'
Т.е. как раз мечтаемое: тип заранее не описываем, он выводится на месте описания данных, и все статически проверяется при использовании.
Второй пример: D.
Аналогичный код:
Чуть длиннее, но ненамного. Причем вместо tuple(99... можно было сразу писать data.a = 99;... Кстати, здесь конструктор типов Tuple и функция tuple - не ключевые слова языка, а чисто библиотечные штуки, входят в стандартную библиотеку.
В таком виде все компиляется гладко. Меняем
string y = data.b;
на
string y = data.с;
Компилятор отвечает "Error: no property 'c' for tuple '(int, string)'".
Если же поставить
bool y = data.b;
то говорит "Error: cannot implicitly convert expression (data._field_field_1) of type string to bool.
В общем, в приличных языках счастье есть, и даже без макросов. Ну а динамическая типизация для таких задач не нужна и даже вредна.
Первый пример: Haxe.
function makeData()
{
return { a: 99, b : " bottles of beer" };
}
function useData(data):Void
{
var x:Int = data.a;
var y:String = data.b;
trace(x, y);
}
...
var t = makeData();
useData(t);Язык со статической типизацией. Где типы не указаны, компилятор сам их выводит. Тут функция makeData производит значение анонимного типа с парой полей типа Int и String (тоже выведены). Другая функция использует данные из такой записи. Напрямую одна другую даже не вызывает. Все компиляется и работает гладко. Теперь если заменить строчку
var y:String = data.b;
на
var y:String = data.c;
то компилятор скажет:
C:\vc\player\src/Main.hx:876: characters 10-11 : { b : String, a : Int } should be { c : String, a : Int }
C:\vc\player\src/Main.hx:876: characters 10-11 : { b : String, a : Int } has no field c
C:\vc\player\src/Main.hx:876: characters 10-11 : For function argument 'data'
А если там поставить
var y:Bool = data.b;
то скажет
C:\vc\player\src/Main.hx:876: characters 10-11 : { b : String, a : Int } should be { b : Bool, a : Int }
C:\vc\player\src/Main.hx:876: characters 10-11 : Invalid type for field b :
C:\vc\player\src/Main.hx:876: characters 10-11 : String should be Bool
C:\vc\player\src/Main.hx:876: characters 10-11 : For function argument 'data'
Т.е. как раз мечтаемое: тип заранее не описываем, он выводится на месте описания данных, и все статически проверяется при использовании.
Второй пример: D.
Аналогичный код:
auto makeData()
{
Tuple!(int, "a", string, "b") data = tuple(99, " bottles of beer");
return data;
}
void useData(T)(T data)
{
int x = data.a;
string y = data.b;
writeln(x,y);
}
...
auto t = makeData();
useData(t);Чуть длиннее, но ненамного. Причем вместо tuple(99... можно было сразу писать data.a = 99;... Кстати, здесь конструктор типов Tuple и функция tuple - не ключевые слова языка, а чисто библиотечные штуки, входят в стандартную библиотеку.
В таком виде все компиляется гладко. Меняем
string y = data.b;
на
string y = data.с;
Компилятор отвечает "Error: no property 'c' for tuple '(int, string)'".
Если же поставить
bool y = data.b;
то говорит "Error: cannot implicitly convert expression (data._field_field_1) of type string to bool.
В общем, в приличных языках счастье есть, и даже без макросов. Ну а динамическая типизация для таких задач не нужна и даже вредна.
no subject
Date: 2013-02-21 07:41 am (UTC)no subject
Date: 2013-02-21 08:13 am (UTC)Конвертироваться как обычно - копируя данные. В случае D при желании можно кастить туплы к эквивалентной структуре, но касты это потенциальные грабли при изменении кастуемых.
no subject
Date: 2013-02-21 08:19 am (UTC)no subject
Date: 2013-02-21 08:56 am (UTC)object structural_subtyping_example { def mkData = new { val a = 99 val b = " bottles of beer" } def useData[D <: { val a: Int; val b: String }](data: D) = { println(data.a.toString + data.b) } } object main extends App { import structural_subtyping_example._ useData(mkData) }У metaclass, насколько я понял, речь больше о вытаскивании через IO имён полей из схемы базы в компайл-тайме.
no subject
Date: 2013-02-21 09:02 am (UTC)Контекст -- правильно подсказывают -- такой, что БД может вернуть совершенно произвольную запись и чем компайл-тайп ошибка тогда отличается от рантайм?
no subject
Date: 2013-02-21 09:39 am (UTC)Вот мне тоже интересно.
Может быть, я просто не разбираюсь.
Но вот что если хотя бы второй индус сделает по заданию голоса свыше нечто вроде
function makeData2()
{
return { a: 99.99d, b : " bottles of beer" }; // а еще лучше MAX_LONG
}
...
auto t = makeData2();
useData(t);
t = makeData();
useData(t);
Тут и без вывода типов концов не сыщешь. А уж с такой свободой и подавно.
no subject
Date: 2013-02-21 10:46 am (UTC)no subject
Date: 2013-02-21 10:47 am (UTC)no subject
Date: 2013-02-21 10:52 am (UTC)"я могу в любое место описания строк-колонок-ячеек отчета (условно говоря, Excel-like таблица фиксированной формы, заполняемая автоматом из проводок) в любой момент добавить атрибут типа :skip-in-vat true и обработать его. При этом мне нужно это изменить ровно в двух местах - описание документа ("данные") и расчетный алгоритм ("код")."
Удобно для таких одноразовых штук - тут быстро слепили набор именованных полей, там его задействовали, плодить кучу классов и править их описания каждый раз не нужно.
Про БД и компайл-тайм это совсем другая история уже, я ее не затрагиваю.
no subject
Date: 2013-02-21 10:55 am (UTC)no subject
Date: 2013-02-21 10:58 am (UTC)no subject
Date: 2013-02-21 11:06 am (UTC)no subject
Date: 2013-02-21 11:10 am (UTC)Если хочется отловить ошибку не в глубине дерева вызовов, а повыше/пораньше, можно в функции выше задать желаемые свойства типа. Например:
void intermediate(T)(T data) if (is(typeof(data.a)==int) && is(typeof(data.b)==string)) { useData(data); }Здесь if за пределами тела функции, это статическая проверка времени компиляции. Если у Т не будет правильных полей, вызов этой функции не скомпилируется. Это D, в Haxe сейчас быстро не скажу аналог.
no subject
Date: 2013-02-21 12:26 pm (UTC)no subject
Date: 2013-02-21 12:46 pm (UTC)и после этого в некоторой функции foo принимать только типы, котрые Iterator:
Если в foo мы передаём какой-то левый тип, то ошибка компиляции случится в месте вызова foo, а не непонятно где внутри foo (при анонимных структурах), где выяснится, что не хватает каких-то полей у типа. И вообще непонятно, как делать нормальный оверлоадинг с такими анонимными типами (когда внутри foo происходит вызов различных функций hasNext() в зависимости от типа итератора).
Т. е. статическая типизация без явного создания типов действительно работает только для простых записей — пар ключ-значение.
no subject
Date: 2013-02-21 01:22 pm (UTC)no subject
Date: 2013-02-21 03:42 pm (UTC)no subject
Date: 2013-02-23 08:53 pm (UTC)no subject
Date: 2013-02-23 08:56 pm (UTC)no subject
Date: 2013-02-24 06:13 am (UTC)no subject
Date: 2013-02-24 06:13 am (UTC)no subject
Date: 2013-02-24 07:48 am (UTC)no subject
Date: 2013-02-24 08:12 am (UTC)no subject
Date: 2013-02-24 08:54 am (UTC)no subject
Date: 2013-02-24 08:57 am (UTC)no subject
Date: 2013-02-24 06:46 pm (UTC)no subject
Date: 2013-02-24 07:11 pm (UTC)no subject
Date: 2013-02-24 08:45 pm (UTC)no subject
Date: 2013-02-25 03:56 am (UTC)Скажу больше: известность полей при компиляции - это как раз норма. Единственный софт, где поля при компиляции могут быть неизвестны, это обслуживающие БД утилиты вроде PhpMyAdmin или SQL Management Studio. В прикладном софте поля должны быть известны, иначе это хаос, ад, коровники, стыд и скрам.
no subject
Date: 2013-02-25 04:10 am (UTC)Спорное утверждение. Как минимум я бы предпочёл не перезапускать свой серверный софт на каждое изменение схемы.
no subject
Date: 2013-02-25 08:11 am (UTC)Имхо, схему следует считать кодом, а не данными. Так же как типы мы считаем частью кода. БД следует считать одним из модулей приложения. Если меняется схема, это эквивалентно тому, что в программе поменялся один из модулей, изменились типы в нем. Обычным следстивем должна быть пересборка и перепроверка всех модулей, зависящих от изменившегося.
no subject
Date: 2013-02-25 09:46 am (UTC)no subject
Date: 2013-02-25 10:31 am (UTC)no subject
Date: 2013-02-25 08:15 pm (UTC)Приблизительно в этот момент програмисты перестанут быть нужны, я полагаю.
no subject
Date: 2013-03-07 09:46 am (UTC)no subject
Date: 2013-03-07 09:53 am (UTC)Но напрашивается существование сильного утверждения о полиморфизме такого кода - сами сериализаторы-десериализаторы не могут зависеть от ими десериализуемой структуры.