thedeemon: (office)
[personal profile] thedeemon
Ребе [livejournal.com profile] metaclass уже не первый раз поднимает эту тему: можно ли иметь строгую типизацию записей, но не объявляя их заранее? Вот пара примеров из используемых мною языков.

Первый пример: 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.

В общем, в приличных языках счастье есть, и даже без макросов. Ну а динамическая типизация для таких задач не нужна и даже вредна.

Date: 2013-02-21 07:41 am (UTC)
From: [identity profile] xeno-by.livejournal.com
А как с сообщениями об ошибках? И еще можно ли конвертироваться из таких структурных типов в эквивалентные номинальные и обратно?

Date: 2013-02-21 08:13 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Я ж привел примеры сообщений об ошибках.

Конвертироваться как обычно - копируя данные. В случае D при желании можно кастить туплы к эквивалентной структуре, но касты это потенциальные грабли при изменении кастуемых.
Edited Date: 2013-02-21 08:15 am (UTC)

Date: 2013-02-21 08:19 am (UTC)
From: [identity profile] dmzlj.livejournal.com
чем-то Haxe неуловимо похож на Бип

Date: 2013-02-21 08:56 am (UTC)
From: [identity profile] isorecursive.livejournal.com
Вот на Scala:
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 имён полей из схемы базы в компайл-тайме.
Edited Date: 2013-02-21 08:57 am (UTC)

Date: 2013-02-21 09:02 am (UTC)
From: [identity profile] volodymir-k.livejournal.com
Я бы попросил начать с объяснения, в чём программисту от этого польза. Чем это удобнее, чем иметь структуру записи с описанием типов полей?

Контекст -- правильно подсказывают -- такой, что БД может вернуть совершенно произвольную запись и чем компайл-тайп ошибка тогда отличается от рантайм?
Edited Date: 2013-02-21 09:03 am (UTC)

Date: 2013-02-21 09:39 am (UTC)
From: [identity profile] guamoka.livejournal.com
Я бы попросил начать с объяснения, в чём программисту от этого польза.

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

function makeData2()
{
return { a: 99.99d, b : " bottles of beer" }; // а еще лучше MAX_LONG
}
...
auto t = makeData2();
useData(t);
t = makeData();
useData(t);

Тут и без вывода типов концов не сыщешь. А уж с такой свободой и подавно.

Date: 2013-02-21 10:46 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Компиляторы обоих написаны на окамле. :)

Date: 2013-02-21 10:47 am (UTC)
From: [identity profile] xeno-by.livejournal.com
Если честно, у тебя примеры довольно игрушечные. Что если филдов больше, чем один-два? Что если дерево вызовов развесистое и сразу неочевидно, кто добавил поле с неправильным именем или неправильным типом?

Date: 2013-02-21 10:52 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Цитируя ребе:
"я могу в любое место описания строк-колонок-ячеек отчета (условно говоря, Excel-like таблица фиксированной формы, заполняемая автоматом из проводок) в любой момент добавить атрибут типа :skip-in-vat true и обработать его. При этом мне нужно это изменить ровно в двух местах - описание документа ("данные") и расчетный алгоритм ("код")."

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

Про БД и компайл-тайм это совсем другая история уже, я ее не затрагиваю.

Date: 2013-02-21 10:55 am (UTC)
From: [identity profile] thedeemon.livejournal.com
В такой ситуации компилятор совершенно четко скажет, что во втором вызове в useData передают неподходящий тип. Ошибка будет отловлена и указана. Вот без статической проверки типов и правда концов не сыщешь, за то динамику и ругаем.

Date: 2013-02-21 10:58 am (UTC)
From: [identity profile] http://users.livejournal.com/_zerg/
Оно не будет компилироваться для начала.

Date: 2013-02-21 11:06 am (UTC)
From: [identity profile] http://users.livejournal.com/_zerg/
Да, при развесистом дереве вызовом и если много нафигачил бывает сложно разобраться, где ошибся с типом.

Date: 2013-02-21 11:10 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Количество филдов неважно, компилятор говорит, с каким именно из них проблемы.

Если хочется отловить ошибку не в глубине дерева вызовов, а повыше/пораньше, можно в функции выше задать желаемые свойства типа. Например:
void intermediate(T)(T data)
if (is(typeof(data.a)==int) && is(typeof(data.b)==string))
{
  useData(data);
}

Здесь if за пределами тела функции, это статическая проверка времени компиляции. Если у Т не будет правильных полей, вызов этой функции не скомпилируется. Это D, в Haxe сейчас быстро не скажу аналог.

Date: 2013-02-21 12:26 pm (UTC)
wizzard: (Default)
From: [personal profile] wizzard
Юзкейсом он похож

Date: 2013-02-21 12:46 pm (UTC)
From: [identity profile] stepancheg.livejournal.com
Проблема в том, что такие типы строго-типизированы в runtime, но динамически типизированы в compile-time. Если говорить в терминах Clay, например, то для каждой структуры мы можем написать предикатов, например,

overload Iterator?(#MyIterator) = true;


и после этого в некоторой функции foo принимать только типы, котрые Iterator:

[I when Iterator?(I)]
foo(iterator: I) = ...


Если в foo мы передаём какой-то левый тип, то ошибка компиляции случится в месте вызова foo, а не непонятно где внутри foo (при анонимных структурах), где выяснится, что не хватает каких-то полей у типа. И вообще непонятно, как делать нормальный оверлоадинг с такими анонимными типами (когда внутри foo происходит вызов различных функций hasNext() в зависимости от типа итератора).

Т. е. статическая типизация без явного создания типов действительно работает только для простых записей — пар ключ-значение.

Date: 2013-02-21 01:22 pm (UTC)
From: [identity profile] dmzlj.livejournal.com
не, именно синтаксис местами идентичен.

Date: 2013-02-21 03:42 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Мне не кажется это проблемой. Предоставлен выбор: либо такие вот совсем анонимные типы, либо типы с предикатами, как у тебя в примере или у меня в комменте выше, либо именованные описанные отдельно типы. Для разных ситуаций можно выбрать подходящую степень строгости. Можно даже постепенно ее увеличивать по мере необходимости.

Date: 2013-02-23 08:53 pm (UTC)
From: [identity profile] si14.livejournal.com
Меня не покидает ощущения, что вы и metaclass говорите о немного разных проблемах. Его проблема — то, что термы летают непонятно как по программе и ошибка может произойти где-то в глубине колл-стека; решение этой проблемы может лежать в плоскости статических гарантий, но это лишь один из вариантов. Вы же априори считаете, что статические гарантии единственно правильный путь (или мне так кажется) и пытаетесь в рамках этой модели решить проблему metaclass'а. Есть подозрение, что она так не решается; пример с БД есть подтверждение этому. Более того, я не уверен, что для решения изначальной проблемы вообще нужен статический анализ; например, мы можем прицепить к каждому терму метадату с его «историей» (оставим пока вопросы реализации и потребления памяти); тогда в случае возникновения ошибки вида «достали из словаря поле foo и попытались сложить его с 3, но оно оказалось строкой» можно выяснить, в каком же именно месте в него положили строку (что-то такое было в Racket, если мне не изменяет память). Насколько я понимаю, это решило бы проблему metaclass'а, но не имело бы ничего общего с «выводим всё в компайлтайме».

Date: 2013-02-23 08:56 pm (UTC)
From: [identity profile] si14.livejournal.com
Там чуть выше ещё пробегала прекрасная мысль — типы с предикатами. Проблема только в том, что в идеале эти предикаты должны быть не над типами, а над термами; т.к. нормально в компайлтайме задача работы с этими предикатами не разрешима, стоило бы отложить её до рантайма и *попытаться* (хотя бы частично) что-то выводить в компайлтайме (вроде dialyzer'овского success typing). Но я не знаю таких конструкций в реальных языках.

Date: 2013-02-24 06:13 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Ребе много разных проблем упоминает, я тут не берусь решать все, а лишь одну, упоминавшуюся им и ранее: возможность, не объявляя по классу на каждый чих, слепить по месту запись (набор именованных и типизированных полей), а в другом месте ее использовать, при этом имея привычные статические гарантии, вроде того, что мы не обратимся к несуществующему полю и не получим массив вместо числа.

Date: 2013-02-24 06:13 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Предикат над термами и есть тип.

Date: 2013-02-24 07:48 am (UTC)
From: [identity profile] si14.livejournal.com
Ну, да. Однако то, что это можно назвать зависимыми типами, не меняет остальной части комментария.

Date: 2013-02-24 08:12 am (UTC)
From: [identity profile] si14.livejournal.com
Я правильно понимаю, что при этом имя поле не может быть произвольным термом?

Date: 2013-02-24 08:54 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Я не понял вопроса.

Date: 2013-02-24 08:57 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Если какие-то штуки в статике сделать слишком сложно или даже вовсе не получается, то их действительно можно и нужно делать в рантайме, я совсем не против. Однако если что-то делается в статических типах очень просто (как в посте), то я не вижу причин этим не пользоваться и откладывать обработку ошибок в рантайм.

Date: 2013-02-24 06:46 pm (UTC)
From: [identity profile] si14.livejournal.com
foo[getThisFieldNameFromDatabaseByAstralKey("bar")] = 1.

Date: 2013-02-24 07:11 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
А, да, имя поля просто идентификатор, известный во время компиляции. Забудьте уже про БД.

Date: 2013-02-24 08:45 pm (UTC)
From: [identity profile] si14.livejournal.com
Вот я про это и говорю — вы взяли конкретное подмножество проблемы и показали, как изящно решить его статическими типами. Однако это не решение проблемы в целом, с одной стороны, и даже не облегчение её в частности с другой — потому что гарантии идут вместе с этим самым требованием известности имён полей при компиляции.

Date: 2013-02-25 03:56 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Это не подмножество. Это вполне самостоятельная проблема, не связанная с тем, что вы пытаетесь додумывать.

Скажу больше: известность полей при компиляции - это как раз норма. Единственный софт, где поля при компиляции могут быть неизвестны, это обслуживающие БД утилиты вроде PhpMyAdmin или SQL Management Studio. В прикладном софте поля должны быть известны, иначе это хаос, ад, коровники, стыд и скрам.
Edited Date: 2013-02-25 04:02 am (UTC)

Date: 2013-02-25 04:10 am (UTC)
From: [identity profile] si14.livejournal.com
Я не пытаюсь додумать; я испытываю боль, схожую (как я понимаю) с болью metaclass и пытаюсь её формализовать :)

Спорное утверждение. Как минимум я бы предпочёл не перезапускать свой серверный софт на каждое изменение схемы.

Date: 2013-02-25 08:11 am (UTC)
From: [identity profile] thedeemon.livejournal.com
А я бы еще предпочел, чтобы он без перезапуска и моего вмешательства сам менялся под меняющиеся требования клиентов. :)

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

Date: 2013-02-25 09:46 am (UTC)
From: [identity profile] si14.livejournal.com
Шутки шутками, а это всё давно уже работает именно в таком виде (изменение схемы без перезапуска) у Ericsson'а в Erlang'е или у Столлмена в Емаксе. Равно как и изменение кода. Отказ от работы с живой системой, а не деревянным буратино — шаг назад.

Date: 2013-02-25 10:31 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Это все равно в первую очередь изменение кода, разница лишь в способе ввода нового кода в систему - с полной ее перезагрузкой или частичной или даже плавным замещением. Да, такие трюки бывают, и изредка они даже нужны и оправданы. Акробаты в цирке и не такое показывают.

Date: 2013-02-25 08:15 pm (UTC)
From: [identity profile] thinker8086.livejournal.com
>> А я бы еще предпочел

Приблизительно в этот момент програмисты перестанут быть нужны, я полагаю.

Date: 2013-03-07 09:46 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
всё в наших руках. нужно перестать писать программы, которые будут выполнять требования заказчика без программиста.

Date: 2013-03-07 09:53 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
Я думаю, есть целая куча софта, где поля не известны при компиляции. Вспомнить сериализацию - у всех времён, народов и языков сериализация предполагает асинхронность изменений кода и сериализованных объектов. Здесь не получится даже "перезапустить" систему.

Но напрашивается существование сильного утверждения о полиморфизме такого кода - сами сериализаторы-десериализаторы не могут зависеть от ими десериализуемой структуры.

Profile

thedeemon: (Default)
Dmitry Popov

December 2025

S M T W T F S
 12 3456
789101112 13
14151617181920
21222324252627
28293031   

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 25th, 2026 07:07 pm
Powered by Dreamwidth Studios