Re[17]: В России опять напишут новый объектно-ориентированны
От: Sinclair Россия https://github.com/evilguest/
Дата: 05.03.18 05:25
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Тоже верно. Но мне, как разработчику, хочется сделать этот автовыводимый тип как public. Более того, я еще хочу иметь возможность присвоить этому типу имя.

Вот тут мне не вполне понятно — зачем имя этому типу?
Вот смотри, как я себе это представляю. Допустим, мы хотим отдать клиенту какой-то view (пока что пренебрежём всем этим ajax-ом).
Ну там — юзернейм, ласт логин, форумлист, топиклист, и т.п.
Для того, чтобы удобно было делать разметку, мы хотим иметь типизированное свойство Model.
Пишем (в биндинге, в коде, или ещё где) this.Model.User.LoginNam; — и нам сразу среда автодополняет, а компилятор статически ругается, если мы не дописали свойство правильно.
В классике нам для этого нужно продекларировать свойство Model заранее:
public class MainView: View
{
   MainViewModel Model {get; set;}
}

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

public class MainView: View
{
    var Model {get; } = new {
    ForumList = (from f in DB.Forums where f.Parent.IsNull() select f.ShortName, f.Description, f.Id),
        TopicList = (from t in DB.Topics where t.ForumID == this.CurrentForum orderby t.LastReplyDate descending select t.Subject, t.ReplyCount, t.LastReplyDate, t.Author.NickName, t.Id),
        User = (from u in users where u.Id == this.CurrentUserId).SingleOrDefault()
    };
}

Тут нам имя типа для этого Model вообще ненужно. Он используется ровно один раз.
Ну, ок, этот код выглядит не слишком опрятно. Опрятнее было бы как-то так:

public class MainView: View
{
    var Model {get; } = BuildModel();
}

Да и внутри BuildModel хотелось бы побольше структурированности — чтобы все вот эти вот кишочки стейтментов наружу не торчали. Там же на самом деле будет развесистая-развесистая хрень: и LastReplyDate, и ReplyCount не хранятся, а вычисляются; везде применяются атрибуты безопасности — типа удалённые сообщения нужно прятать от всех, кроме модераторов, ну и прочее, и прочее.
Почти всё, что нам нужно, работает (то есть работало бы) через конструктор анонимного класса:
    var model = new {
    ForumList = getForums(),
        TopicList = getTopics(CurrentForumId),
        User = getUserDetail(CurrentUserId)
    };

Здесь подразумевается выведение типов:
var getForums = () => from f in DB.Forums where f.Parent.IsNull() select f.ShortName, f.Description, f.Id;
var getTopics = (int forumId) => from t in DB.Topics where t.ForumID == forumId orderby t.LastReplyDate descending select t.Subject, t.ReplyCount, t.LastReplyDate, t.Author.NickName, t.Id;

В современном C# этому мешает, по большому счёту, одна проблема — отсутствие автовывода возвращаемого типа.
В именованных типах ещё и типы свойств надо декларировать явно, но это можно почти что обойти при помощи декларации анонимных типов.
А вот с автовыводом возврата всё совсем плохо. Для полноценных методов этого вообще нет. Можно было бы заменить на лямбды, как я выше показал, но увы — сериал Lost поучаствовал и здесь: CS0815 "Cannot assign lambda expression to an implicitly-typed variable"
Понятно, что мы-то здесь подразумеваем не Func<>, а Expression<Func>, потому что всю эту кунсткамеру нам предстоит компилировать в SQL (ну или во что-то ещё, для передачи в RDBMS execution pipeline). Но компилятору этого не объяснишь.
Впрочем, такое уж различие между Expression<> и Func<> — это опять какая-то искусственная ерунда.
С учётом того, что внутри лежит байткод, я не вижу никакой причины не встраивать оба типа конверсии — прямой и обратный — в эти выражения.
И вообще, более правильно спроектирован, скажем, Regex — там нет дуализма между скомпилированными и интерпретируемыми выражениями, а вместо этого есть только флаг в конструкторе.
Который, по моему мнению, тоже избыточен. Не вижу никакой причины заставлять программиста предсказывать частоту использования конкретного регекспа (ну, или нашего лямбда-выражения).
Этим должен заниматься рантайм — аналог JIT-а. Мы же всегда можем рассчитать как стоимость компиляции регекспа, так и стоимость его выполнения в обоих вариантах. Увидели, что нам передали гигабайтную строку -ок, компилируем.
Увидели, что интерпретируем тот же регексп в сотый раз — компилируем.
И то же самое с Expression. Понадобилось нам проехаться по AST — ок, берём у Method свойство Statements и поехали.
Понадобилось его реально вычислить — берём и вычисляем.
А что там делается за кадром — это дело среды.

V>Например, в современном С++ такое возможно через комбинацию auto, typedef и decltype.

V>Но даже такая связка недостаточно удобна, как по мне, т.к. декларация typedef — она независимая от целевого метода, возвращающего "анонимный" тип, а хочется именно привязать одно к другому.
Ну вот надо начинать со схемы использования. Как Хейльсберг начал проектировать Дельфи со строчки на доске: Label1.Font.Color := clGray;

V>Например, взять автовычисляемые поля.

V>Если "исходники" данных для вычисляемого поля так же идут по сети в рамках того же экземпляра кортежа, то значения автовычисленных полей, передаваемых по сетке, будут избыточными. В общем, когда некая "одна система" будет ответственна за оба конца транспорта, то она может использовать знания о происходящем для оптимизаций ф-ии доставки данных.

V>На шаблонах С++ можно. В любом случае в момент компиляции всё разресолвится.

Это понятно. Но нам надо не только стереть типы при компиляции, но и как-то оставить достаточно данных для движка RDBMS, чтобы обеспечить runtime-execution.
Сейчас оптимизаторы традиционных ЯП и оптимизаторы RDBMS устроены очень-очень сильно по разным принципам.
Разработка оптимизатора, который сочетает в себе оба подхода, тянет на десяток PhD thesis.
V>Для нынешней динамики SQL это всё недостижимо, ес-но. О проблемах узнаешь только в рантайм и то, зачастую когда уже поздно. ))
Ну, в рамках разработки подхода динамикой SQL можно пренебречь. То есть мы можем считать, что у нас есть волшебный объект DB, с мемберами, которые адекватно описывают всю-всю схему данных в базе. Его модификация — это тяжёлая штука, т.к. она требует перелопачивания фактически хранимых данных. Как это описать — отдельный вопрос.

И мы поверх DB строим объекты типа Model, которые описывают "вычисляемую" часть модели (их может быть много — столько, сколько нужно для работы). Они сами по себе stateless. Это даже скорее не объекты, а выражения — то есть нам нужны функции getMainPageViewModel(DB, viewParam1, viewParam2, ...), которые описывают, как получить данные для отображения.

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

S>>Хотя уже можно делать штуки типа

S>>IQueryable<T> FilterByDateRange(this IQueryable<T> input, Expression<Func<T, DateTimeOffsetDate>> dateSelector DateTimeOffset? from, DateTimeOffset? to)

V>Но выглядит страшно, согласись. ))

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

V>Ограничения типов (которые на сейчас сейчас даются в виде интерфейсов и базовых классов) должны составлять полную сигнатуру типов и их членов, но эти ограничения не входят в сигнатуру типа и его мемберов.

Да тут дело не в ограничениях. Вот в этом же упрощённом примере я мог бы пойти по одному из трёх путей:
1. Потребовать у типа элемента коллекции иметь свойство Date (СPP-style)
2. Потребовать у типа элемента коллекции наследоваться от IDatedObject { DateTimeOffset Date { get; }} (CLR-style)
3. Потребовать у пользователя метода передавать селектор для даты
Как видим из примера использования, первые два способа неприемлемы, т.к. у объекта может быть более одного свойства типа даты, а ведут они себя в данном случае одинаково.
Это не так очевидно для синтетического примера с First и Last name, но и в нём нужно поступать примерно так же.
Потенциальным улучшением было бы умение использовать "дефолтные" значения селекторов для самых частых случаев. То есть если мы не передали ничего в dateSelector, то подразумевается o=>o.DocumentDate. (потенциально решаемо на С++ частичной специализацией).
Но в любом случае ограничения нам тут нужны во вторую очередь.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.