/// Simplicity
module FP =
let add_v1 = fun a b -> a + b
let add_v2 = (+)
let add_v3 a b = a + b
FP.add_v1 1 1 |> printfn "%d"
/// Complexity
module OOP =
type Add(a) =
class
member it.Value = a
member it.Plus(b) = Add((b :> Add).Value + a)
end
OOP.Add(1).Plus(OOP.Add(1)).Value |> printfn "%d"
Выше ООП и ФП подход к решению одной задачи.
В чем принципиальное отличие?
На мой взгляд ФП ориентировано на обработку данных,
в то время как ООП старается абстрагировать проблему в некоторую модель,
что делать код намного сложнее в понимании.
Появляется неявный контекст, "за лесом не видно деревьев".
Здравствуйте, Разраб, Вы писали: Р>Появляется неявный контекст, "за лесом не видно деревьев".
Принципиальное отличие в наличии изменяемого состояния, для которого необходима идентифицируемость (identity).
Всё остальное — непринципиально. Если его нет, то объект — это просто набор функций, которые объединены (или не объединены) общим замыканием. Интерфейс становится кортежем функций, а класс — синоним конструктора.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Разраб, Вы писали:
Р>Выше ООП и ФП подход к решению одной задачи.
В твоем примере нет ООП.
Одного наличия слова class и вызова через точку недостаточно чтобы код стал ООП.
ООП это прежде всего состояние и сокращения кода за счет наследования и переопределении методов.
Плюс поддержка интерфейсов, замена множественного наследия.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>ООП ... и сокращения кода за счет наследования и переопределении методов. S>Плюс поддержка интерфейсов, замена множественного наследия.
В ООП языках представлена очень ограниченная версия — signle param dispatch.
В FP языках (Haskell/Scala — type classes, LispCLOS/Julia — multiple params dispatch) без ограничений.
Здравствуйте, novitk, Вы писали:
S>>ООП ... и сокращения кода за счет наследования и переопределении методов. S>>Плюс поддержка интерфейсов, замена множественного наследия.
N>В ООП языках представлена очень ограниченная версия — signle param dispatch. N>В FP языках (Haskell/Scala — type classes, LispCLOS/Julia — multiple params dispatch) без ограничений.
Не знаю, чё це таке, но C# точно ООП язык с примесями ФП. И народ, не переходит на тот же F# раз у него нет ограничений.
Все таки изменяемое состояние в большинстве случаев более выгодно. А переопределение методов и инкапсуляция просто уменьшает количество дополнительного кода.
Хотя без отладчика иногда сложно разобраться в высокоуровневой иерархии.
ФП хорош в рекурсиях, итерациях, где можно лаконично описать процесс
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>Не знаю, чё це таке, но C# точно ООП язык с примесями ФП. И народ, не переходит на тот же F# раз у него нет ограничений.
В F# ограничения как раз есть. Там полиморфизм такой же как в C# и ОCaml.
Не переходят на него потому же почему и на Скалу — голову придется утруждать, а производительность в разы не поднимется.
Здравствуйте, novitk, Вы писали:
S>>Не знаю, чё це таке, но C# точно ООП язык с примесями ФП. И народ, не переходит на тот же F# раз у него нет ограничений. N>В F# ограничения как раз есть. Там полиморфизм такой же как в C# и ОCaml. N>Не переходят на него потому же почему и на Скалу — голову придется утруждать, а производительность в разы не поднимется.
Утруждать то необходимо! Новые нейронные связи образуются. Помню как на Linq плевались.
Жалко, что в одном проекте нельзя 2 языка использовать.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, novitk, Вы писали:
S>> Жалко, что в одном проекте нельзя 2 языка использовать. N>зачем? для скорости через unsafe?
Ну для многих вещей хорошо подходит ФП. Я бы с удовольствием использовал F# для того же Рослина ну и просто мозг развлечь.
А так лениво делать в разных проектах.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S> Я бы с удовольствием использовал F# для того же Рослина ну и просто мозг развлечь. S>А так лениво делать в разных проектах.
Не понимаю use case. Для "того же Рослина" надо работать в MS, а для "просто мозг развлечь" берешь F# interactive и никакие проекты не нужны.
Здравствуйте, Serginio1, Вы писали:
S>ООП это прежде всего состояние
Собственно, и всё
S>и сокращения кода за счет наследования и переопределении методов.
Ни при наследовании ни при переопределении код (функций/методов) не сокращается. Сокращается только код, относящийся к состоянию. Т.е. всё отличие как ни крути сводится к появлению состояния и индивидуальных языковых плюшек для упрощения работы с ним.
S>>ООП это прежде всего состояние V>Собственно, и всё
S>>и сокращения кода за счет наследования и переопределении методов. V>Ни при наследовании ни при переопределении код (функций/методов) не сокращается. Сокращается только код, относящийся к состоянию. Т.е. всё отличие как ни крути сводится к появлению состояния и индивидуальных языковых плюшек для упрощения работы с ним.
Вот помню я программировал на Фортране, Паскале (не Турбо). Там никакого ООП не было. Только процедурное программирование. Но было состояние. Глобальные переменные и переменные функции и параметры.
Которые в огромном количестве передавались из метода в метод.
Что бы не таскать переменные через параметры, решили передавать через ссылку на структуру.
А затем, зачем нам передавать явно, давай прикрутим методы к структуре, а self будем передавать неявно через регистр, прикрутили наследование и VMT, информацию о типе, ну и так далее Получился ООП
и солнце б утром не вставало, когда бы не было меня
Р>/// Simplicity
Р>module FP =
Р> let add_v1 = fun a b -> a + b
Р> let add_v2 = (+)
Р> let add_v3 a b = a + b
Р>FP.add_v1 1 1 |> printfn "%d"
Р>/// Complexity
Р>module OOP =
Р> type Add(a) =
Р> class
Р> member it.Value = a
Р> member it.Plus(b) = Add((b :> Add).Value + a)
Р> end
Р>OOP.Add(1).Plus(OOP.Add(1)).Value |> printfn "%d"
Р>
Р>Выше ООП и ФП подход к решению одной задачи. Р>В чем принципиальное отличие? Р>На мой взгляд ФП ориентировано на обработку данных, Р>в то время как ООП старается абстрагировать проблему в некоторую модель, Р>что делать код намного сложнее в понимании. Р>Появляется неявный контекст, "за лесом не видно деревьев".
ООП необходимо для развестистой и сложной модели: бизнес процессы, ынтерпрайз, UI фреймворки — решение чисто прикладной задачи, где необходимо вытачивать дмоенную модель,
следить за консистентностью и т.д. ООП там отлично ложится при должном умении.
Если же пишется инструмент сам в себе либо что-то полусистемное, необходимая доля ООП может быть сильно уменьшена. хотел бы я посмотреть на ФП проект, хотя бы в 200к строк, и не в стиле маяковского, когда каждый чих на новой строке, а с нормальным заполнением.
То же ядро линукса можно было бы переписать на ООП и сделать код в целом понятнее и кратче, но это повлечет за собой сайд эффекты, которые будут несовместимы с требованиями.
Здравствуйте, novitk, Вы писали:
N>Здравствуйте, Serginio1, Вы писали:
S>> Я бы с удовольствием использовал F# для того же Рослина ну и просто мозг развлечь. S>>А так лениво делать в разных проектах. N>Не понимаю use case. Для "того же Рослина" надо работать в MS, а для "просто мозг развлечь" берешь F# interactive и никакие проекты не нужны.
вообще, я недавно отдуплил тему окончательно. главная сила F# скриптов,
не только в самостоятельном использований(но это тоже, особенно
сейчас с ускорением старта дотнетовой тулзы),
но прежде всего при разработке fsproj в vs code + Ionide
Каждый включенный в проект файл модуль (fs)
Отдельно от fsproj создаем fsx
через #r "nuget: Fsharp.Data" добавляем необходимые зависимости.
через #I "Utils.fs" грузим модули проекта,
добавляем отладочный код и отправляем все это в fsi
таким образом разработка идет намного веселей, вместо прыганья по класса,
отлаживаются идеальные программные единицы — функции.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Разраб, Вы писали: Р>>Появляется неявный контекст, "за лесом не видно деревьев".
S>Принципиальное отличие в наличии изменяемого состояния, для которого необходима идентифицируемость (identity). S>Всё остальное — непринципиально. Если его нет, то объект — это просто набор функций, которые объединены (или не объединены) общим замыканием. Интерфейс становится кортежем функций, а класс — синоним конструктора.
Дело в том, что ООП делает состояние и управление им неявным, взять хотя бы классический WPF, он полагается на магические пропертя и рефлексию.
Но сейчас уже набирает силу ФРП, и оно делает многие вещи более простым способом(еще можно глянуть solidjs)
open Avalonia
open Avalonia.Controls
open Avalonia.Markup.Xaml
open FSharp.Control.Reactive
open Avalonia.Threading
type AppState =
{ Count: int
EnableTimer: bool }
static member Zero = { Count = 0; EnableTimer = true }
type MainWindow() as this =
inherit Window()
do this.InitializeComponent()
member private this.InitializeComponent() =
#if DEBUG
this.AttachDevTools()
#endif
AvaloniaXamlLoader.Load(this)
let count = Subject.behavior (AppState.Zero)
let tb = this.FindControl<TextBlock>("info")
let btn = this.FindControl<Button>("inc")
let inc = fun (x: AppState) -> { x with Count = x.Count + 1 }
btn.Click.Add(fun _ ->
if count.Value.EnableTimer then
count.OnNext(
{ count.Value with
Count = count.Value.Count + 1
EnableTimer = false }
)
else
count.OnNext(inc count.Value))
count.Subscribe(fun x -> Dispatcher.UIThread.Invoke(fun _ -> tb.Text <- $"Count: {x.Count}"))
|> ignore
task {
while count.Value.EnableTimer do
do! Async.Sleep 1000
count.OnNext(inc count.Value)
}
|> ignore
Именно. Я честно был недавно удивлен на интервью. Контора на дотнете, но для скриптов используют CPython, IronPython типа не развивается.
Я говорю "а что не F#?", ответ какой-то мутный, но я так понял "не осилили".
Мельком глянул, и Base.method это похоже на extension методы в шарпах и котлине и сложность становится сопоставимой, весь вопрос в квалификации разраба и условиях в которых это создавалось.
Здравствуйте, diez_p, Вы писали:
_>Мельком глянул, и Base.method это похоже на extension методы в шарпах и котлине и сложность становится сопоставимой, весь вопрос в квалификации разраба и условиях в которых это создавалось.
S>>>>ООП это прежде всего состояние
IT>>>А в процедурном программировании разве нет состояния? При чём тут ООП?
Вопрос звучит так
"Базовое отличие ООП от ФП"
а не
"Базовое отличие Процедурного программировании от ООП"
А различие ПП от ООП я написал здесь http://rsdn.org/forum/philosophy/8706558.1
Здравствуйте, Serginio1, Вы писали:
S>Вопрос звучит так S>"Базовое отличие ООП от ФП" S>а не S>"Базовое отличие Процедурного программировании от ООП" S>А различие ПП от ООП я написал здесь S>http://rsdn.org/forum/philosophy/8706558.1
Здравствуйте, IT, Вы писали:
S>>Вопрос звучит так S>>"Базовое отличие ООП от ФП" S>>а не S>>"Базовое отличие Процедурного программировании от ООП" S>>А различие ПП от ООП я написал здесь S>>http://rsdn.org/forum/philosophy/8706558.1
там описано все то, на что ты наезжаешь.
S это Я!
S>>ООП это прежде всего состояние V>Собственно, и всё
S>>и сокращения кода за счет наследования и переопределении методов. V>Ни при наследовании ни при переопределении код (функций/методов) не сокращается. Сокращается только код, относящийся к состоянию. Т.е. всё отличие как ни крути сводится к появлению состояния и индивидуальных языковых плюшек для упрощения работы с ним.
Вот помню я программировал на Фортране, Паскале (не Турбо). Там никакого ООП не было. Только процедурное программирование. Но было состояние. Глобальные переменные и переменные функции и параметры.
Которые в огромном количестве передавались из метода в метод.
Что бы не таскать переменные через параметры, решили передавать через ссылку на структуру.
А затем, зачем нам передавать явно, давай прикрутим методы к структуре, а self будем передавать неявно через регистр, прикрутили наследование и VMT, информацию о типе, ну и так далее Получился ООП
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Sinclair, Вы писали:
S>Принципиальное отличие в наличии изменяемого состояния, для которого необходима идентифицируемость (identity).
это если мы говорим о сравнении ФП с императивным подходом.
А если сравнивать ООП с ФП, то иммутабельность видимо не является принципиальным отличием. Ведь без изменения состояния можно работать и в ООП парадигме.
Принципиальным отличием, как мне кажется, является то как мы вносим зависимости.
Мне даже кажется, что если бы функциональщина появились в языках изначально, ООП так бы и не был внедрён. Он более громоздок, вносит больше вспомогательных абстракций.
Здравствуйте, sergii.p, Вы писали:
SP>А если сравнивать ООП с ФП, то иммутабельность видимо не является принципиальным отличием.
Является. SP>Ведь без изменения состояния можно работать и в ООП парадигме.
Можно, но тогда от ООП почти ничего не остаётся. SP>Принципиальным отличием, как мне кажется, является то как мы вносим зависимости.
SP>В ООП через интерфейсы:
SP>
Здесь нет никаких интерфейсов; языки с номинативной типизацией (например, C или C++) просто не скомпилируют этот код. Для того, чтобы в foo можно было передавать Console и File, нам нужно унаследовать их от Out.
SP>В ФП — через функции:
Не обязательно. Вот вы в том, что вы назвали "ООП", просто завернули функции write внутрь структур.
В ФП вы можете сделать примерно то же самое — заверните функцию внутрь структуры:
struct Out { write: (Data) -> void}
То, что в ФП это кажется оверкиллом — ну, так это оттого, что вы выбрали вырожденный интерфейс.
Возьмите в качестве интерфейса что-нибудь поинтереснее — например, пару из NeutralElement и Combine:
public interface IGroup<T>
{
public T NeutralElement {get;}
public T Combine(T a, T b)
}
public class IntAddition: IGroup<int>
{
public int NeutralElement { get => 0; }
public int Combine(int a, int b) => a + b;
}
public static class H
{
public static T Reduce<T>(this IEnumerable<T> input, IGroup<T> group)
{
var r = group.NeutralElement;
foreach(var i in input)
r = group.Combine(r, i);
return r;
}
}
public static class Program
{
public static void Main()
{
var t = new[] {4, 8, 15, 16, 23, 42};
var sum = t.Reduce(new IntAddition());
}
}
Вот вам ООП подход.
В ФП вместо ООП тут будет не функция, а структура, с точно такой же топологией.
public static T Reduce(this IEnumerable<T> input, (Func<T> neutralElement, Func<T, T, T> combine))
{
var r = group.neutralElement();
foreach(var i in input)
r = group.combine(r, i);
return r;
}
public static class Program
{
public static void Main()
{
var t = new[] {4, 8, 15, 16, 23, 42};
var intAddition = (()=>0, (int x, int y)=>x+y);
var sum = t.Reduce(intAddition);
}
}
Видим, что всё работает точно так же, как и следовало ожидать. То есть мы по-прежнему инжектим зависимость через "интерфейс", просто теперь это не какая-то специальная конструкция в языке, а просто обычная неизменяемая структура с полями-функциями.
Отличия начинаются ровно в том месте, где у нас члены этого интерфейса начинают что-то менять.
например, так:
public interface IAccumulator<T>
{
public T Current{get;}
public void Accumulate(T value);
}
public class AddAccumulator: IAccumulator<int>
{
private _value = 0;
public int Current{ get => _value; }
public void Accumulate(int value) => _value += value;
}
public static class H
{
public static T Reduce<T>(this IEnumerable<T> input, IAccumulator<T> accumulator)
{
foreach(var i in input)
accumulator.Accumulate(i);
return accumulator.Current;
}
}
public static class Program
{
public static void Main()
{
var t = new[] {4, 8, 15, 16, 23, 42};
var sum = t.Reduce(new AddAccumulator());
}
}
Вот для такого кода написать прямой аналог на ФП уже не получится, потому что в каноническом ФП мы не можем менять состояние существующих объектов.
Строгое ФП потребует от нас поменять сигнатуры методов и слегка переколбасить код.
Что-то вроде
public struct record Accumulator<T>(T value, Func<T, Accumulator<T>> accumulate);
public static class H
{
public static T Reduce<T>(this IEnumerable<T> input, Accumulator accumulator)
{
foreach(var i in input)
accumulator = accumulator.Accumulate(i);
return accumulator.Current;
}
public static Accumulator<T> CreateAccumulator<T>(T value, Func<T, T, T> combine)
=> new Accumulator(value, (T x) => CreateAccumulator(combine(value, x), combine);
}
public static class Program
{
public static void Main()
{
var t = new[] {4, 8, 15, 16, 23, 42};
var addAccumulator = H.CreateAccumulator(0, (x, y)=> x+y);
var sum = t.Reduce(addAccumulator);
}
}
Вот как раз тут видно, почему ООП без изменяемого состояния — "не ООП".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Возьмите в качестве интерфейса что-нибудь поинтереснее — например, пару из NeutralElement и Combine: S>
S>public interface IGroup<T>
S>{
S> public T NeutralElement {get;}
S> public T Combine(T a, T b)
S>}
S>public class IntAddition: IGroup<int>
S>{
S> public int NeutralElement { get => 0; }
S> public int Combine(int a, int b) => a + b;
S>}
S>public static class H
S>{
S> public static T Reduce<T>(this IEnumerable<T> input, IGroup<T> group)
S> {
S> var r = group.NeutralElement;
S> foreach(var i in input)
S> r = group.Combine(r, i);
S> return r;
S> }
S>}
S>public static class Program
S>{
S> public static void Main()
S> {
S> var t = new[] {4, 8, 15, 16, 23, 42};
S> var sum = t.Reduce(new IntAddition());
S> }
S>}
S>
S>Вот вам ООП подход.
Замечу, что здесь нет изменяемого состояния.
S>Вот как раз тут видно, почему ООП без изменяемого состояния — "не ООП".
Мне видно противоречие. Ваш вывод для меня неубедителен.
На первый взгляд в ООП инкапсуляция явно противоречит иммутабельности. Но на самом деле это чисто технический момент. Следующий код с изменяемым состоянием
class MyObject {
int _v;
public: void setField(int v) { _v = v; }
};
легко преобразуется в иммутабельный
class MyObject {
int _v;
public: MyObject setField(int v)&& { this->_v = v; return *this; }
}
да, такой код не очень идиоматичен. Но чисто технически он иммутабельность убирает. В языках типа С# и Java конечно такое тяжело (нет move семантики), но начиная с С++ и Rust так извращаться уже можно. В данном случае ограничения языков не должны вводить в заблуждение относительно ограниченности ООП. В ООП стиле можно писать даже иммутабельно
Здравствуйте, sergii.p, Вы писали:
S>>Вот вам ООП подход.
SP>Замечу, что здесь нет изменяемого состояния.
Совершенно верно. Я об этом и говорю — без изменяемого состояния ООП от ФП практически неотличимо. "Интерфейсы" без мутабельности прекрасно реализуются на ФП.
S>>Вот как раз тут видно, почему ООП без изменяемого состояния — "не ООП". SP>Мне видно противоречие.
Где именно? SP>На первый взгляд в ООП инкапсуляция явно противоречит иммутабельности. Но на самом деле это чисто технический момент. Следующий код с изменяемым состоянием
SP>
Простите, это на каком языке? SP>да, такой код не очень идиоматичен. Но чисто технически он иммутабельность убирает.
Если я правильно понимаю, вы просто переписали мой пример кода с "иммутабельной мутабельностью" на другой язык. При этом, как мне кажется, вы допустили ошибку — вы же this меняете, нет тут никакой иммутабельности.
Для проверки попробуйте добавить const к определению int _v.
SP>В языках типа С# и Java конечно такое тяжело (нет move семантики),
Не очень понятно, при чём тут move семантика. SP>но начиная с С++ и Rust так извращаться уже можно. В данном случае ограничения языков не должны вводить в заблуждение относительно ограниченности ООП. В ООП стиле можно писать даже иммутабельно
Такое "ООП" плохо не тем, что оно иммутабельное, а тем, что это — не ООП.
Собственно, как и следовало ожидать, оно с той же громоздкостью выражается на ФП. Потому что — см. предыдущий пункт: в отстутствие изменяемого состояния, "объект" — это просто пачка функций, объединённых общим замыканием.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
C++.
&& означает, что функция может применяться исключительно к временному объекту.
#include <string>
#include <cstdio>
using namespace std;
class MyObject {
int _v;
public: MyObject setField(int v)&& { this->_v = v; return *this; }
};
int main() {
auto m = MyObject().setField(1); // okauto m2 = MyObject();
m2.setField(1); // error: 'this' argument to member function 'setField' is an lvalue, but function has rvalue ref-qualifier
}
Здравствуйте, Sinclair, Вы писали:
SP>>Замечу, что здесь нет изменяемого состояния. S>Совершенно верно. Я об этом и говорю — без изменяемого состояния ООП от ФП практически неотличимо. "Интерфейсы" без мутабельности прекрасно реализуются на ФП.
Вы почему то рассматриваете ООП исключительно как ОО + императивное программирование, где искаропки изменяемое состояние.
ОО может подклеиваться к разным парадигмам. Основая вещь это именованное состояние, интерфейс и взаимодействие.
Здравствуйте, _NN_, Вы писали: _NN>&& означает, что функция может применяться исключительно к временному объекту.
Спасибо, я так и подумал. _NN>
_NN>#include <string>
_NN>#include <cstdio>
_NN>using namespace std;
_NN>class MyObject {
_NN> int _v;
_NN> public: MyObject setField(int v)&& { this->_v = v; return *this; }
_NN>};
_NN>int main() {
_NN> auto m = MyObject().setField(1); // ok
_NN> auto m2 = MyObject();
_NN> m2.setField(1); // error: 'this' argument to member function 'setField' is an lvalue, but function has rvalue ref-qualifier
_NN>}
_NN>
А как из этого изготовить хоть что-то полезное?
Например, вот такое тоже не работает:
...
auto m2 = m.setField(2);
А это, собственно, и есть одна из основных конструкция ФП.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Pauel, Вы писали:
P>Вы почему то рассматриваете ООП исключительно как ОО + императивное программирование, где искаропки изменяемое состояние.
Отож.
P>ОО может подклеиваться к разным парадигмам. Основая вещь это именованное состояние, интерфейс и взаимодействие. P>https://rsdn.org/forum/philosophy/7358494.1
Это верно, но как ни крути, а "ООП без состояния" всё же будет бледной тенью "настоящего ООП".
В приведённой статье этой конструкции соответствует "declarative object", или stateless bundle. Ну, так это собственно и есть неизменяемый tuple, который есть примерно во всех ФП-языках.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
ООП это когда данные и функции объединяются в одну сущность.
ФП это когда функция является значением и другие функции могут её использовать, таким образом можно писать функции, оперирующие функциями (т.н. функции высшего порядка).
Подходы друг с другом никак не пересекаются и одно другому никак не мешает.
Здравствуйте, Sinclair, Вы писали:
S>А как из этого изготовить хоть что-то полезное?
так объект можно сконструировать:
const auto m = MyObject().setField1(1).setField2(2).setField3(3);
Так на основе объекта создать клон с изменённым свойством
const auto anotherObj = m.copy().setField1(10);
А что ещё надо? Ну да, immutable накладывает серьёзные ограничения на создание программ. Поэтому 90% писать на этом не могут и уходят. Но принципиальных препятствий нет. Тот ООП, который мы ненавидим любим, уже не сделаем с такими ограничениями, но то что сделаем меньшим ООП от этого не станет.
И говорить, что immutable является определяющим аттрибутом ФП, как по мне, тоже неправильно. Любая программа написанная в функциональном стиле имеет в глубине изменяемое состояние (те же регистры, вызовы библиотечных функций). Или вот мой любимый пример:
Ну чем не функциональщина? Однако же изменяемое состояние тут есть (println или другая функция логирования). То же кеширование результатов функции является вроде как аттрибутом ФП, но строится на основе изменяемого сотояния (по-другому ну никак).
В общем призываю выделять immutable программирование в отдельную парадигму.
Здравствуйте, sergii.p, Вы писали:
s> S>А как из этого изготовить хоть что-то полезное? s> так объект можно сконструировать: s>
s> const auto m = MyObject().setField1(1).setField2(2).setField3(3);
s>
Какой-то игрушечный пример. Сразу видно плохая продуманность фич. По отдельности вроде всё круто, но вместе никак не стыкуется. Обычно хочется чтобы была возможность структурировать код. Например, вынести setField1 и setField2 в отдельный метод, а setField3 проставлять только по какому-то условию. А ещё есть field4 который контейнер в который что-то может несколько раз добавляться.
И тут без полноценного типа Builder не получится.
Здравствуйте, sergii.p, Вы писали:
SP>так объект можно сконструировать: SP>
SP>const auto m = MyObject().setField1(1).setField2(2).setField3(3);
SP>
Простите, но это — чесание правой ногой левого уха. Если у меня заранее известны все нужные мне "поля" и их значения, то в разы удобнее пользоваться специализированным конструктором:
const auto m = MyObject(1, 2, 3);
Fluent-цепочки в основном нужны тогда, когда у меня есть многостадийное конструирование. Например, как-то так:
auto m_temp = MyObject().setField1(1).setField2(2);
const auto m = (conditional_expression) ? m_temp.setField3(17) : m_temp.setField4(42);
return m;
SP>Так на основе объекта создать клон с изменённым свойством
SP>
SP>const auto anotherObj = m.copy().setField1(10);
SP>
Офигеть как удобно! И, к тому же, error: 'const class MyObject' has no member named 'copy' https://godbolt.org/z/ze57E31v9
SP>А что ещё надо?
Надо, чтобы а) всё работало и б) легко читалось и писалось.
SP>Ну да, immutable накладывает серьёзные ограничения на создание программ.
Не, это не immutable накладывает, а конкретно ваш подход — ужасен.
Даже оставаясь в рамках C++ и подобных ему императивных ОО-языков, иммутабельность можно реализовать значительно менее кошмарным способом.
Для примера можно посмотреть хотя бы на то, как устроены DateTime и TimeSpan в C#.
SP>Поэтому 90% писать на этом не могут и уходят. Но принципиальных препятствий нет. Тот ООП, который мы ненавидим любим, уже не сделаем с такими ограничениями, но то что сделаем меньшим ООП от этого не станет.
Конечно станет. Вы покажите мне пример кода на этом вашем "ООП", который выходит за пределы однострочного примера.
И мы с вами убедимся, что никакого ООП там нет, и что из кода все "объекты" можно безболезненно убрать, заменив их ADT.
SP>И говорить, что immutable является определяющим аттрибутом ФП, как по мне, тоже неправильно. Любая программа написанная в функциональном стиле имеет в глубине изменяемое состояние (те же регистры, вызовы библиотечных функций).
Регистры тут вообще нерелевантны. Современные языки программирования с регистрами не связаны примерно никак. А "вызовы библиотечных функций" обычно наоборот, не "в глубине", а "на поверхности". Чтобы сохранить основной граф вычислений чистым.
SP>Или вот мой любимый пример:
SP>
SP>Ну чем не функциональщина? Однако же изменяемое состояние тут есть (println или другая функция логирования).
Функциональщина не отказывается от изменяемого состояния. Она просто делает его как можно более явным. Я не знаю, что там по этому поводу думает Rust, но в строгих ФП-языках println — это "особенная" функция, которая помимо своего прямого аргумента принимает "вселенную до" и возвращает "вселенную после".
Кстати, вот в этом коде println — это метод какого объекта? Или это всё же не ООП?
SP>То же кеширование результатов функции является вроде как аттрибутом ФП, но строится на основе изменяемого сотояния (по-другому ну никак).
Нет, кеширование атрибутом ФП не является. Но вот его возможность является прямым следствием ФП.
Memoize можно применять не к любой функции. Например, к println — нельзя.
В идеале, такое ограничение должно выражаться прямо в языке — чтобы memoize(println) давало compile-time error. А не run-time error, и уж тем более не молчаливое неверное поведение.
SP>В общем призываю выделять immutable программирование в отдельную парадигму.
Нет там никакой отдельной парадигмы. Ну, разве что мы назовём отдельной парадигмой SSA-форму.
Но она настолько сурова, что на ней никто не пишет. Удобнее всё же иметь несколько больше возможностей по структурированию кода и данных, чем чистая SSA поверх скаляров.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, vsb, Вы писали:
vsb>ООП это когда данные и функции объединяются в одну сущность.
vsb>ФП это когда функция является значением и другие функции могут её использовать, таким образом можно писать функции, оперирующие функциями (т.н. функции высшего порядка).
vsb>Подходы друг с другом никак не пересекаются и одно другому никак не мешает.
Данные и функции можно объединять в одну сущность безо всякого ООП.
Вот простейший пример:
public static Func<int, int> AddX(int x) => (int y)=>y+x;
public static int Add(int x, int y) => x+y;
public static Func<T2, R> FixX<T1, T2, R>(Func<T1, T2, R> f, T1 x) => (T2 y) => f(x, y);
public static int Main()
{
var encapsulated = AddX(3); // объединили функцию и значение
Console.WriteLine(encapsulated(42));
var enc2 = FixX(Add, 5); // объединили функцию и значение
Console.WriteLine(enc2(17));
}
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Данные и функции можно объединять в одну сущность безо всякого ООП.
Это уже пошла имитация ООП. С одной функцией ещё не так заметно, а если добавить несколько функций (к одному набору данных), то уже видно будет. Имитировать ФП через интерфейс с одной виртуальной функцией (как это сделано в Java) тоже можно. А на С можно имитировать и то и другое через указатели на функции. Суть в концепции, а не в конкретном механизме реализации.
Здравствуйте, vsb, Вы писали:
vsb>Это уже пошла имитация ООП. С одной функцией ещё не так заметно, а если добавить несколько функций (к одному набору данных), то уже видно будет.
Да, я показывал.
Но "имитировать" классическое ООП, где "реакция" на "сообщение" зависит от предыстории, на честном неизменяемом ФП затруднительно.
Поэтому ООП — это не столько про "объединение функций и данных", сколько про представление решения в виде объектов, изменяющих своё состояние во времени.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
Ну ООП это не только состояние. Но самое главное это наследование полей, свойств методов и их переопределение.
VMT! Интерфейсы! Видимость свойств и методов.
Ref тоже могут быть read only readonly ref
Да иногда сложно читать код. Тыкаешь в метод и попадаешь либо в абстрактный либо виртуальный метод. Нужно смотреть реализацию, которая может быть у десятков и сотен классов.
Вот в C# для структур нет наследования, хотя могли бы и завести тип структура с VMT как в C++
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Sinclair, Вы писали:
vsb>>Это уже пошла имитация ООП. С одной функцией ещё не так заметно, а если добавить несколько функций (к одному набору данных), то уже видно будет. S>Да, я показывал. S>Но "имитировать" классическое ООП, где "реакция" на "сообщение" зависит от предыстории, на честном неизменяемом ФП затруднительно.
Неизменяемость на мой взгляд не является неотъемлемым признаком ФП. Это отдельная концепция. Которая, кстати, в некотором виде стала весьма популярна и в таком языке, как Java, где стали очень часто применять неизменяемые объекты.
ООП на неизменяемом ФП на мой взгляд это что-то вроде Erlang. Где каждый объект это функция, рекурсивно вызывающая сама себя с изменёнными параметрами (имитация цикла событий с мутабельным состоянием) и на каждой итерации принимающая объект и как-то его обрабатывающая (обработка вызова метода).
Здравствуйте, Pauel, Вы писали:
P>>>Потому и полезно S>>Не вижу связи. Код-то на этом как писать?
P>Как я понимаю, это позволяет сделать нечто навроде initializer в C# P>
P>return new User()
P> .withName('Коля')
P> .withEmail('коля+2024@гемайл.ком')
P> .withImage('data:image/png;base64,...')
P>
P>И, поскольку, return m.with... не пролезет, то это вполне себе годное ограничение.
А не проще через конструктор . Ведь readOnly тоже должны быть инициализированы! И конструктор без параметров должен быть запрещен!
Здравствуйте, vsb, Вы писали:
vsb>Неизменяемость на мой взгляд не является неотъемлемым признаком ФП. Это отдельная концепция. Которая, кстати, в некотором виде стала весьма популярна и в таком языке, как Java, где стали очень часто применять неизменяемые объекты.
Неизменяемость это просто единственное условие где может работать ФП.
Намастырить пайплайн для обработки данных на мутабельных функциях очень даже можно, правда минорная ошибка порушит всю цивилизацию.
В свое время на Си и Ассемблере так и делали
— функция принимает указатель на список функций
— функция возвращает указатель на список функций
— функция из списка изменяет данные так и эдак
Тут есть одна сложность — доказать те или иные свойства пайпалайна в общем случае не представляется возможным. Зато в частном случае, если у вас есть десяток-другой отлаженых комбинаторов, можете использовать такое сколько влезет.
Здравствуйте, Serginio1, Вы писали:
P>>И, поскольку, return m.with... не пролезет, то это вполне себе годное ограничение. S> А не проще через конструктор . Ведь readOnly тоже должны быть инициализированы! И конструктор без параметров должен быть запрещен!
При чем здесь проще? Речь о том, для чего можно использовать заявленную фичу.
Если выбирали такой дизайн по какой причине, то очевидно что readonly и запрет такого конструктора сюда не входит.
P>>return new User()
P>> .withName('Коля')
P>> .withEmail('коля+2024@гемайл.ком')
P>> .withImage('data:image/png;base64,...')
P>>
S> А не проще через конструктор . Ведь readOnly тоже должны быть инициализированы! И конструктор без параметров должен быть запрещен!
например, нам не надо задавать email и картинку. А в конструкторе мы обязаны перечислить все параметры. Их там может быть 20 штук. И мы должны каждый явно указать. Неудобно.
Здравствуйте, Pauel, Вы писали:
P>Здравствуйте, Serginio1, Вы писали:
P>>>И, поскольку, return m.with... не пролезет, то это вполне себе годное ограничение. S>> А не проще через конструктор . Ведь readOnly тоже должны быть инициализированы! И конструктор без параметров должен быть запрещен!
P>При чем здесь проще? Речь о том, для чего можно использовать заявленную фичу.
P>Если выбирали такой дизайн по какой причине, то очевидно что readonly и запрет такого конструктора сюда не входит.
Выбирали такой дизайн криворукие дизайнеры. В шарпе сделали правильно.
и солнце б утром не вставало, когда бы не было меня
P>>>return new User()
P>>> .withName('Коля')
P>>> .withEmail('коля+2024@гемайл.ком')
P>>> .withImage('data:image/png;base64,...')
P>>>
S>> А не проще через конструктор . Ведь readOnly тоже должны быть инициализированы! И конструктор без параметров должен быть запрещен!
SP>например, нам не надо задавать email и картинку. А в конструкторе мы обязаны перечислить все параметры. Их там может быть 20 штук. И мы должны каждый явно указать. Неудобно.
В шарпе есть именованные параметры и параметры по умолчанию
Здравствуйте, Pauel, Вы писали: P>Как я понимаю, это позволяет сделать нечто навроде initializer в C# P>
P>return new User()
P> .withName('Коля')
P> .withEmail('коля+2024@гемайл.ком')
P> .withImage('data:image/png;base64,...')
P>
Это решаает какую-то проблему, которой нет. P>Это ж C++, тут на всё есть особый пример
Ну, тогда хотелось бы релевантных примеров. Обещали показать immutable OOP, а показали какую-то убогую многостадийную инициализацию иммутабельных объектов.
Можно взять какую-то реалистичную задачу, для которой ООП подходит хорошо. Ну, там, реализацию оконного интерфейса.
Чтобы при включении checkbox1 у нас editbox1 становился enabled, а при выключении — disabled.
На ООП это работает отлично, хоть в наивной форме, хоть в продвинутых архитектурах вроде MVC/MVP/MVVM.
Как это устроено на ФП, я тоже отдалённо представляю.
А вот как это будет устроено на "иммутабельном ООП", хотелось бы посмотреть.
Ну, то есть у меня, конечно же, есть некоторые представления о том, как бы это выглядело; но они крайне далеки от конкретно этой реализации с &&.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, sergii.p, Вы писали: SP>например, нам не надо задавать email и картинку. А в конструкторе мы обязаны перечислить все параметры. Их там может быть 20 штук. И мы должны каждый явно указать. Неудобно.
Никто не мешает нам иметь пяток конструкторов для типовых комбинаций начальных параметров. Никто не мешает нам использовать null object pattern для задания параметров, которые не нужны.
Ну и, самое главное — никто не мешает нам делать нормальную реализацию иммутабельных объектов, когда все set-методы возвращают копию объекта.
Это позволяет делать не только многостадийную инициализацию в одну строку, но и описывать произвольные функциональные зависимости.
Например, нетрудно строить всякие конвейеры в стиле
from d in _dates select d.AddDays(17).AddYears(-1);
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Serginio1, Вы писали:
P>>Если выбирали такой дизайн по какой причине, то очевидно что readonly и запрет такого конструктора сюда не входит. S> Выбирали такой дизайн криворукие дизайнеры. В шарпе сделали правильно.
Что вы предлагаете, переписать с С++ на C# ради инициализаторов?
Тамошние инициализаторы, кстати говоря, довольно убогие.
Я бы предпочел инициализатор навроде такого
Здравствуйте, Sinclair, Вы писали:
S>Никто не мешает нам иметь пяток конструкторов для типовых комбинаций начальных параметров. Никто не мешает нам использовать null object pattern для задания параметров, которые не нужны.
Пять конструкторов — это значит, что скорее всего в итоге невнятная документация и в коде будет мешанина, т.к. кто нибудь передаст null не туда.
Множественные перегрузки конструкторов или методов — это разве что в ДСЛ сгодится, а так лучше без всего этого обходиться.
S>Ну и, самое главное — никто не мешает нам делать нормальную реализацию иммутабельных объектов, когда все set-методы возвращают копию объекта.
мешает — гц. Всё таки дохловат в дотнете гц. Если плодить на каждый чих временные объекты, вы очень быстро раздуваете нулевое поколение и выходите за его пределы.
Если у вас есть промежутки, где гц может включиться, то это не проблема. Но вот всякие переинициализации в синхронном коде, да еще в цикле — у вас на ровном месте будет вылазить конское поглощение памяти. Подкинули 10 филдов, внезапно, это 10 клонов всей вашей кунсткамеры.
Для плоских объектов это еще куда ни шло. А если у вас на таком месте задается структура типа DOM, то вы ошалеете с такой иммутабельностью.
Здравствуйте, Sinclair, Вы писали:
S>А вот как это будет устроено на "иммутабельном ООП", хотелось бы посмотреть. S>Ну, то есть у меня, конечно же, есть некоторые представления о том, как бы это выглядело; но они крайне далеки от конкретно этой реализации с &&.
В иммутабельном OOP это будет примерно так же, как делают лет 10 во фронтенде — redux + react.
Модель — каждое изменение, сколь угодно малое, дает вам новую иерархию, старую можно хранить для истории. Для этого глубокая древовидная структура годится слабо, нужна как можно более плоская модель.
Вьюшка — обновляется согласно модели — если ссылка на новый объект и на старый отличается, надо пересоздать все нижележащие элементы
Рутовый объект — мутабельный, запускает цикл обновление, в конце переприсваивает ссылки на новый UI и новую модель
Из недостатков — конский расход памяти, конский расход процессора, нужно знать как под капотом работает рендерер и подобные механизмы, итд. Иначе очень легко намастырить код, когда на каждый onchange наверху будет отжираться конский граф объектов
Собственно, большая часть туториалов по реакту это "вот так не надо, потому что унутре ... в итоге всё дохнет"
Повторить такой фокус для классических winforms точно не выйдет , и для xaml сильно вряд ли выйдет — эти вещи никогда не проектировались под подобное использование.
Здравствуйте, Pauel, Вы писали:
P>Здравствуйте, Serginio1, Вы писали:
P>>>Если выбирали такой дизайн по какой причине, то очевидно что readonly и запрет такого конструктора сюда не входит. S>> Выбирали такой дизайн криворукие дизайнеры. В шарпе сделали правильно.
P>Что вы предлагаете, переписать с С++ на C# ради инициализаторов?
Здравствуйте, Pauel, Вы писали:
P>Пять конструкторов — это значит, что скорее всего в итоге невнятная документация и в коде будет мешанина, т.к. кто нибудь передаст null не туда. P>Множественные перегрузки конструкторов или методов — это разве что в ДСЛ сгодится, а так лучше без всего этого обходиться.
Это, скажем так, смелое утверждение.
Давайте возьмём более-менее любую популярную библиотеку и посмотрим, сколько конструкторов у привычных нам объектов.
Например, какой-нибудь FileStream. Или тот же DateTime.
S>>Ну и, самое главное — никто не мешает нам делать нормальную реализацию иммутабельных объектов, когда все set-методы возвращают копию объекта. P>мешает — гц. Всё таки дохловат в дотнете гц. Если плодить на каждый чих временные объекты, вы очень быстро раздуваете нулевое поколение и выходите за его пределы.
Во-первых, речь шла не о дотнете, а о C++. Современные компиляторы C++ прекрасно устраняют лишние копирования в цепочках вроде m2 = m1.setField1(17).setField2(42).
Во-вторых, в дотнете иммутабельность прекрасно дружит с value-типами. Где джит тоже устраняет лишние копирования в цепочках модификаций. P>Если у вас есть промежутки, где гц может включиться, то это не проблема. Но вот всякие переинициализации в синхронном коде, да еще в цикле — у вас на ровном месте будет вылазить конское поглощение памяти. Подкинули 10 филдов, внезапно, это 10 клонов всей вашей кунсткамеры. P>Для плоских объектов это еще куда ни шло. А если у вас на таком месте задается структура типа DOM, то вы ошалеете с такой иммутабельностью.
В-третьих, для того и делаются перегрузки конструкторов — чтобы избегать цепочек бессмысленных копирований в среде, где нет escape analysis.
А в-четвёртых, если вы хотите посмотреть на приложение, в котором на таком месте задаётся структура типа DOM, то запустите Visual Studio.
Там весь редактор кода — это иммутабельное дерево поверх иммутабельного дерева.
И все инспекторы и codefix-провайдеры работают с этим иммутабельным деревом именно через всякие root.ReplaceNode(classDeclaration, classDeclaration.AddModifiers(items)).
Вполне себе всё это шустро работает, и GC им не мешает .
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Pauel, Вы писали:
S>> XAML это про декларативность. То есть за строкой скрывается куча кода.
P>Именно это и есть в моем примере
Нет это конструкторы. Ты явно не использовал XAML S>> Ну а в приведённом тобой коде это экономия на new.
P>Вы сначала накидайте полный эквивалент в c# а уже потом сравнивайте
Здравствуйте, Sinclair, Вы писали:
S>Давайте возьмём более-менее любую популярную библиотеку и посмотрим, сколько конструкторов у привычных нам объектов. S>Например, какой-нибудь FileStream. Или тот же DateTime.
DateTime плохой пример. DateTimeOffset — 8 конструкторов. Я в свое время задолбался писать конформанс тесты под один из сервисов на дотнете и джаве — у них вечно были ошибки с рендерингом выхлопа с датами.
Визуально, по коду, разницы между двумя вариантами почти что нет — код ревью не работает. А покрыть тестами все нюансы со временем мягко говоря задача не маленькая.
S>А в-четвёртых, если вы хотите посмотреть на приложение, в котором на таком месте задаётся структура типа DOM, то запустите Visual Studio. S>Там весь редактор кода — это иммутабельное дерево поверх иммутабельного дерева. S>И все инспекторы и codefix-провайдеры работают с этим иммутабельным деревом именно через всякие root.ReplaceNode(classDeclaration, classDeclaration.AddModifiers(items)). S>Вполне себе всё это шустро работает, и GC им не мешает .
Подозреваю, они напилили там целую кучу оптимизаций под гц, что бы не было лишних переаллокаций. В 00х у них было полно проблем с перформансом, память могла уходить гигабайтами на ровном месте. Потом вероятно привели всё в порядок.
Здравствуйте, Serginio1, Вы писали:
P>>Именно это и есть в моем примере S>Нет это конструкторы. Ты явно не использовал XAML
Подозреваю, я xaml попробовал задолго до того, как вы про него узнали Не впечатлил — громоздко, многословно, тормозно
Сам по себе XAML не летает — это просто фронтенд для описания структуры.
А далее готовая структура подаётся в бакенд, который её интерпретирует.
Декларативные свойства обеспечивают не xaml, а тот самый бакенд.
Собственно, вместо XAML вы можете использовать что угодно, абы структура на выходе была такая же.
Вы можете и сейчас это сделать разными путями
1. создавать структуру на основе более внятного ДСЛ, нежели xaml, транслируюя в xaml
2. императивно
3. на основе инциализаторов, которые кривые, что вы и продемонстрировали
То есть, буде в наличии внятные инциализаторы, нет необходимости городить огород из XAML
S>>> Ну а в приведённом тобой коде это экономия на new.
P>>Вы сначала накидайте полный эквивалент в c# а уже потом сравнивайте
S>bkb S>
Здравствуйте, Pauel, Вы писали:
P>>>Именно это и есть в моем примере S>>Нет это конструкторы. Ты явно не использовал XAML
P>Подозреваю, я xaml попробовал задолго до того, как вы про него узнали Не впечатлил — громоздко, многословно, тормозно
Там есть конвертеры, тригеры итд.
Я шарпом занимаюсь с 2002 года. И сначала XAML у меня вызвал отвращение.
Да я и не особо дизайнер.
P>Сам по себе XAML не летает — это просто фронтенд для описания структуры. P>А далее готовая структура подаётся в бакенд, который её интерпретирует.
Ну вообще то он компилируется Компиляция XAML
P>Декларативные свойства обеспечивают не xaml, а тот самый бакенд. P>Собственно, вместо XAML вы можете использовать что угодно, абы структура на выходе была такая же. P>Вы можете и сейчас это сделать разными путями P>1. создавать структуру на основе более внятного ДСЛ, нежели xaml, транслируюя в xaml P>2. императивно P>3. на основе инциализаторов, которые кривые, что вы и продемонстрировали
P>То есть, буде в наличии внятные инциализаторы, нет необходимости городить огород из XAML
Я так же программно иногда делаю.
Но это от лени. Я не так часто его использую.
S>>>> Ну а в приведённом тобой коде это экономия на new.
P>>>Вы сначала накидайте полный эквивалент в c# а уже потом сравнивайте
S>>bkb S>>
P>Итого — по вашему коду не сильно видна разница между объектами и списками, + шум new()
Ну можно
list = new List<object>
а new кому то шум, а кому то явное определение
P>Кроме того, у вас вероятно ошибка P>Вот здесь создаем список у которого два элемента, один с типом ItemA, второй ItemB P>
new ItemA() {id= 'a', content= yyy },
new ItemB() {id= 'a', content= zzz },
или если есть конструктор с параметрами id и content
new ItemA(id: 'a', content: yyy),
new ItemB(id: 'a', content: zzz ),
Это не особо принципиально. Это из области флаттера и Dart . У Котлина тоже такое есть.
Но XAML он не для программистов, а дизайнеров. Изначально идея была отделить дизайнеров и программистов
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
P>>Подозреваю, я xaml попробовал задолго до того, как вы про него узнали Не впечатлил — громоздко, многословно, тормозно S>Там есть конвертеры, тригеры итд.
Спасибо, не надо — там где хватает обычного вызова функции надо намастырить кусочек xml
P>>Сам по себе XAML не летает — это просто фронтенд для описания структуры. P>>А далее готовая структура подаётся в бакенд, который её интерпретирует.
S> Ну вообще то он компилируется Компиляция XAML
И что с того? Это всё равно описание структуры. Всю работу делает бакенд.
Во что именно xaml компилируется, в вызовы типа "parent.addElement(...)" ? Если так, то никаких преимуществ перед инициализаторами нет.
Если во чтото более низкоуровневое — тут слабо верится.
S> Это не особо принципиально. Это из области флаттера и Dart . У Котлина тоже такое есть. S>Но XAML он не для программистов, а дизайнеров. Изначально идея была отделить дизайнеров и программистов
Подозреваю, в микрософте была идея, что над кодом будут работать дизайнеры и программисты
А прогресс пошел по другому пути — начали плодиться разрешения от 320x200 до 8K, десятками тысяч и дизайнерам уже было не до кода — успеть бы картинок нарисовать.
Лайоут типа респонзив итд у микрософта был непредусмотрен, соответсвенно видение микрософта умерло вместе с ихними инструментами для рисования ui.
Для ui разработчиков нужно максимальное разделение концепций — отделить даные, лайоут, стили, рендеринг и поведение.
То есть вот такой стек у нас выходит
1 данные
2 семантический рендеринг
3 стили
4 лайоут, композиция
5 реакция и поведение
технически 4 и 3 делают одним и тем же инструментом, но реально это трохи разные вещи. стили это look & feel, layout это взаимодействие элементов
У микрософта 2, 3 и 4 это Винигрет, до кучи многословный
Вещи из п1-5 могут деливериться каждый по отдельности, буквально. До момента деплоя вы можете никогда не видеть вон те стили и вон тот лайоут на тех данных с таким вот поведением.
Почему так произошло — потому что спрос на фронтенд всё время растет, фронтенд становится сложнее с т.з. требований. А раз растет сложность требований, то очевидно и внутренняя структура будет меняться точно так же — будет усложняться.
Здравствуйте, Pauel, Вы писали:
P>>>Подозреваю, я xaml попробовал задолго до того, как вы про него узнали Не впечатлил — громоздко, многословно, тормозно S>>Там есть конвертеры, тригеры итд.
P>Спасибо, не надо — там где хватает обычного вызова функции надо намастырить кусочек xml
XAML это в том числе и визуальный редактор. Ты сразу видишь результат, интеллисенс итд.
P>>>Сам по себе XAML не летает — это просто фронтенд для описания структуры. P>>>А далее готовая структура подаётся в бакенд, который её интерпретирует.
S>> Ну вообще то он компилируется Компиляция XAML
P>И что с того? Это всё равно описание структуры. Всю работу делает бакенд. P>Во что именно xaml компилируется, в вызовы типа "parent.addElement(...)" ? Если так, то никаких преимуществ перед инициализаторами нет. P>Если во чтото более низкоуровневое — тут слабо верится.
Ну вот в то, что ты пишешь вручную без редактора.
S>> Это не особо принципиально. Это из области флаттера и Dart . У Котлина тоже такое есть. S>>Но XAML он не для программистов, а дизайнеров. Изначально идея была отделить дизайнеров и программистов
P>Подозреваю, в микрософте была идея, что над кодом будут работать дизайнеры и программисты P>А прогресс пошел по другому пути — начали плодиться разрешения от 320x200 до 8K, десятками тысяч и дизайнерам уже было не до кода — успеть бы картинок нарисовать. P>Лайоут типа респонзив итд у микрософта был непредусмотрен, соответсвенно видение микрософта умерло вместе с ихними инструментами для рисования ui.
P>Для ui разработчиков нужно максимальное разделение концепций — отделить даные, лайоут, стили, рендеринг и поведение.
P>То есть вот такой стек у нас выходит P>1 данные P>2 семантический рендеринг P>3 стили P>4 лайоут, композиция P>5 реакция и поведение
P>технически 4 и 3 делают одним и тем же инструментом, но реально это трохи разные вещи. стили это look & feel, layout это взаимодействие элементов
P>У микрософта 2, 3 и 4 это Винигрет, до кучи многословный
P>Вещи из п1-5 могут деливериться каждый по отдельности, буквально. До момента деплоя вы можете никогда не видеть вон те стили и вон тот лайоут на тех данных с таким вот поведением.
P>Почему так произошло — потому что спрос на фронтенд всё время растет, фронтенд становится сложнее с т.з. требований. А раз растет сложность требований, то очевидно и внутренняя структура будет меняться точно так же — будет усложняться.
Фронтэнд это для браузеров. Для десктопа нужны другие инструменты.
Но, что я не вижу особо альтернатив. Может XAML заменить на YAML.
Суть она должна быть удобна для дизайнеров. Но прошло столько лет, а XAML никуда не делся.
Тот же MAUI на XAML
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
P>>Спасибо, не надо — там где хватает обычного вызова функции надо намастырить кусочек xml S>XAML это в том числе и визуальный редактор. Ты сразу видишь результат, интеллисенс итд.
Такой редактор можно прикрутить к чему угодно — такое понаписывали чуть ли не на все случаи жизни, наверное на всех языках есть
Инициализаторы это json подобный язык — проще просто некуда
У xaml сверх этого нет ни единого преимуществе
Apple вон вовсе выдал SwiftUI — шикарная вещь. Может не самая идеальная, но уж точно лучше xaml
P>>Если во чтото более низкоуровневое — тут слабо верится. S> Ну вот в то, что ты пишешь вручную без редактора.
не смешите людей — редактор даже на основе тупого кода в winforms прикрутили, а вы трясетесь, что json подобный язык вдруг станет препятствием
S>Суть она должна быть удобна для дизайнеров. Но прошло столько лет, а XAML никуда не делся.
Дизайнеры с кодом не работают. Им не до этого — задача дизайнера не xaml выдать, а нарисовать картинки на все случаи жизни, размеры экрана итд.
XAML никуда не делся потому, что его чисто технически нечем заменить
1. в языке нет ничего внятного, что может послужить заменой
2. в рантайме хилые средства, приходится мастырить грабли типа attached property, конвертеры, триггеры итд.
> Для десктопа нужны другие инструменты.
Очень смешно — софта на веб-технологиях появляется всё больше. pva стали нормой. electron прижился. сапр и те потиху идут в веб.
Чисто десктопный софт теперь это узенькая ниша инструментов которые идут вместе с системой.
Здравствуйте, Serginio1, Вы писали:
S>Здравствуйте, _NN_, Вы писали:
_NN>>C++. _NN>>&& означает, что функция может применяться исключительно к временному объекту.
S> Вот же извращенцы! И до такого надо ещё додуматься! S>А почему не в конструкторе?
В чём собственно вопрос ?
C++ позволяет гибко указать когда функция может применяться к объекту:
Компиляторы — крайне фиговый пример, потому что специфика в полный рост, причем специфика такая, что нигде больше в больших объемах не встречается.
А так да, на компиляторах ФП максимально демонстрирует свои преимущества. А вот если брать мейнстримовый мейнстрим, то на тех же задачках из области CRUD (и в целом на задачах, в которых несколько моделей с преобразованиями) все становится совсем не очевидным.
Здравствуйте, vsb, Вы писали:
vsb>ООП это когда данные и функции объединяются в одну сущность.
Это, скажем так, не единственное и даже не так чтобы основное.
Основное отличие — идеологическое. ООП это декомпозиция на модели, а ФП — на функции. Т.е. ООП описывает дизайн софта как набор графов из разнотипных узлов со связями и ассоциированными с узлами методов, а ФП как одну большую функцию, которая является композицией функций поменьше.
vsb>Подходы друг с другом никак не пересекаются и одно другому никак не мешает.
Некоторое пересечение все же есть, хотя бы потому что для одной задачи обычно существует как функциональное, так и ООП решение.
Здравствуйте, _NN_, Вы писали:
_NN>Здравствуйте, Serginio1, Вы писали:
S>>Здравствуйте, _NN_, Вы писали:
_NN>>>C++. _NN>>>&& означает, что функция может применяться исключительно к временному объекту.
S>> Вот же извращенцы! И до такого надо ещё додуматься! S>>А почему не в конструкторе?
_NN>В чём собственно вопрос ? _NN>C++ позволяет гибко указать когда функция может применяться к объекту:
Здравствуйте, Serginio1, Вы писали: S> Ну ООП это не только состояние. Но самое главное это наследование полей, свойств методов и их переопределение.
Наследование полей как раз самое неглавное. Например, в COM нет никакого наследования полей, что не мешает ему быть ОО-платформой.
S>VMT! Интерфейсы! Видимость свойств и методов.
VMT — это деталь реализации. Можно строить ООП существенно по-другому.
В том же Object Pascal (aka Delphi) есть две разных VMT. S> Ref тоже могут быть read only readonly ref S>Да иногда сложно читать код. Тыкаешь в метод и попадаешь либо в абстрактный либо виртуальный метод. Нужно смотреть реализацию, которая может быть у десятков и сотен классов.
Не в этом дело. S> Вот в C# для структур нет наследования, хотя могли бы и завести тип структура с VMT как в C++
Нет смысла. Наследование структур в .Net имело бы весьма ограниченную полезность.
Наследование имеет смысл там, где можно выполнять принцип подстановки, а для этого нужна ссылка, а не экземпляр.
То есть, к примеру, не получится объявить var b = BaseStruct[] и положить в него b[0] = new DerivedStruct().
Чтобы это было можно,
а) элементы массива должны быть ссылками
б) в каждой ссылке должна быть информация о типе, чтобы GC мог правильно интерпретировать то, на что она указывает.
Получаем, собсно, reference type aka class.
Весь полиморфизм value-типов, который возможен в рамках управляемой среды, будет жить исключительно на стеке.
То есть там, где можно получить некий ref. И тогда понятно, что можно в качестве ref baseStruct передать ref DerivedStruct.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Serginio1, Вы писали: S>> Ну ООП это не только состояние. Но самое главное это наследование полей, свойств методов и их переопределение. S>Наследование полей как раз самое неглавное. Например, в COM нет никакого наследования полей, что не мешает ему быть ОО-платформой.
В Delphi интересно сделали поддержку интерфейсов. Делали отдельную VMT для каждого объекта где перед вызовом метода корректировался Self.
В COM нет полей, но есть VMT.
S>>VMT! Интерфейсы! Видимость свойств и методов. S>VMT — это деталь реализации. Можно строить ООП существенно по-другому. S>В том же Object Pascal (aka Delphi) есть две разных VMT.
На самом деле одна. Просто с отрицательным смещением идет VMT для метаклассов. (class procedure DoSomething;override Нужны ли метаклассы????1 S>> Ref тоже могут быть read only readonly ref S>>Да иногда сложно читать код. Тыкаешь в метод и попадаешь либо в абстрактный либо виртуальный метод. Нужно смотреть реализацию, которая может быть у десятков и сотен классов. S>Не в этом дело. S>> Вот в C# для структур нет наследования, хотя могли бы и завести тип структура с VMT как в C++ S>Нет смысла. Наследование структур в .Net имело бы весьма ограниченную полезность. S>Наследование имеет смысл там, где можно выполнять принцип подстановки, а для этого нужна ссылка, а не экземпляр. S>То есть, к примеру, не получится объявить var b = BaseStruct[] и положить в него b[0] = new DerivedStruct(). S>Чтобы это было можно, S>а) элементы массива должны быть ссылками S>б) в каждой ссылке должна быть информация о типе, чтобы GC мог правильно интерпретировать то, на что она указывает. S>Получаем, собсно, reference type aka class. S>Весь полиморфизм value-типов, который возможен в рамках управляемой среды, будет жить исключительно на стеке. S>То есть там, где можно получить некий ref. И тогда понятно, что можно в качестве ref baseStruct передать ref DerivedStruct.
Ну вот С++ прекрасно себе используют структуры и не кашляют.
Можно создать ограничения итд. А так приходится наследоваться через Агрегирование.
Кстати в том же Delphi есть Implements то есть полю делегируют реализацию интерфейса
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S> На самом деле одна. Просто с отрицательным смещением идет VMT для метаклассов. (class procedure DoSomething;override https://stackoverflow.com/questions/15416733/what-is-the-difference-between-dynamic-and-virtual-methods
S> Ну вот С++ прекрасно себе используют структуры и не кашляют.
В C++ нет GC, так что обязанность отслеживать внутреннее устройство всех объектов лежит на программисте. S>Можно создать ограничения итд. А так приходится наследоваться через Агрегирование. S> Кстати в том же Delphi есть Implements то есть полю делегируют реализацию интерфейса
Это просто реализация COM.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Serginio1, Вы писали:
S>> На самом деле одна. Просто с отрицательным смещением идет VMT для метаклассов. (class procedure DoSomething;override S>https://stackoverflow.com/questions/15416733/what-is-the-difference-between-dynamic-and-virtual-methods
S>> Ну вот С++ прекрасно себе используют структуры и не кашляют. S>В C++ нет GC, так что обязанность отслеживать внутреннее устройство всех объектов лежит на программисте.
Боксинг структуры это тот же объект. А что касается стеков и массивов, то все там хранится в явном виде. S>>Можно создать ограничения итд. А так приходится наследоваться через Агрегирование. S>> Кстати в том же Delphi есть Implements то есть полю делегируют реализацию интерфейса S>Это просто реализация COM.
А что мешает это присобачить к структурам?
То есть это не интерфейс, а просто автогенерация методов, которое можно сделать через SG, а конфликты решать через атрибуты
И соответственно сделать доступ к protected полям
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>>В C++ нет GC, так что обязанность отслеживать внутреннее устройство всех объектов лежит на программисте. S> Боксинг структуры это тот же объект. А что касается стеков и массивов, то все там хранится в явном виде.
В том-то и дело. Как будет устроен layout массива структур в таком случае?
Будет ли массив структур ковариантен, как массив ссылочных типов?
S> А что мешает это присобачить к структурам?
Отсутствие у них VMT. S> То есть это не интерфейс, а просто автогенерация методов, которое можно сделать через SG, а конфликты решать через атрибуты S> И соответственно сделать доступ к protected полям
Теоретически можно это навелосипедить, но вы получите обратно reference-типы.
В чём тогда смысл "структурности"?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Serginio1, Вы писали:
S>>>В C++ нет GC, так что обязанность отслеживать внутреннее устройство всех объектов лежит на программисте. S>> Боксинг структуры это тот же объект. А что касается стеков и массивов, то все там хранится в явном виде. S>В том-то и дело. Как будет устроен layout массива структур в таком случае? S>Будет ли массив структур ковариантен, как массив ссылочных типов?
S>> А что мешает это присобачить к структурам? S>Отсутствие у них VMT.
Еще раз наследование для структур это не VMT это наследование полей свойств и методов.
Это наследование без виртуальных методов. Для структур это запрещено!
S>> То есть это не интерфейс, а просто автогенерация методов, которое можно сделать через SG, а конфликты решать через атрибуты S>> И соответственно сделать доступ к protected полям S>Теоретически можно это навелосипедить, но вы получите обратно reference-типы. S>В чём тогда смысл "структурности"?
Как я и писал выше это обычное наследование, но без виртуальных методов
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали: S>>> А что мешает это присобачить к структурам? S>>Отсутствие у них VMT. S> Еще раз наследование для структур это не VMT это наследование полей свойств и методов. S>Это наследование без виртуальных методов. Для структур это запрещено!
Какой сценарий в прикладном коде выиграет от наследования с такими ограничениями?
Наследование ценно там, где экземпляр субтипа может выступать в качестве супертипа.
А в нашем случае что?
Вот у нас
public struct Name
{
public Name: string;
}
public class Person
{
public Name: Name;
}
А теперь мы хотим применить наследование:
public struct FullName: Name
{
// Name: string is inheritedpublic LastName: string;
}
...
var p = new Person();
p.Name = new FullName {Name = "John", FullName = "Doe"}
Что будет происходить? Молчаливое урезание FullName до Name? Ошибка компиляции?
Единственное место, где FullName можно относительно безопасно использовать вместо Name — это ref Name аргумент в какую-нибудь функцию. Ну, или в ref-поле ref-структуры — то есть, опять же, конструкции, которая может жить исключительно на стеке.
И то, непонятно, как GC будет отслеживать ссылки — ему ведь нужно как-то понимать, что в данном экземпляре Person через Name.LastName прикопана ссылка на строку.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Serginio1, Вы писали: S>>>> А что мешает это присобачить к структурам? S>>>Отсутствие у них VMT. S>> Еще раз наследование для структур это не VMT это наследование полей свойств и методов. S>>Это наследование без виртуальных методов. Для структур это запрещено! S>Какой сценарий в прикладном коде выиграет от наследования с такими ограничениями? S>Наследование ценно там, где экземпляр субтипа может выступать в качестве супертипа. S>А в нашем случае что? S>Вот у нас S>
S>public struct Name
S>{
S> public Name: string;
S>}
S>public class Person
S>{
S> public Name: Name;
S>}
S>
S>А теперь мы хотим применить наследование: S>
S>public struct FullName: Name
S>{
S> // Name: string is inherited
S> public LastName: string;
S>}
S>...
S>var p = new Person();
S>p.Name = new FullName {Name = "John", FullName = "Doe"}
S>
S>Что будет происходить? Молчаливое урезание FullName до Name? Ошибка компиляции? S>Единственное место, где FullName можно относительно безопасно использовать вместо Name — это ref Name аргумент в какую-нибудь функцию. Ну, или в ref-поле ref-структуры — то есть, опять же, конструкции, которая может жить исключительно на стеке. S>И то, непонятно, как GC будет отслеживать ссылки — ему ведь нужно как-то понимать, что в данном экземпляре Person через Name.LastName прикопана ссылка на строку.
Для структур такое присвоение запрещено, как и ref.
p.Name = new FullName {Name = "John", FullName = "Doe"}
В чем проблемы? Мы работаем не с объектами!
В конце концов это может быть агрегирование с SG с доступом к protected полям и свойствам!
и солнце б утром не вставало, когда бы не было меня
Многие концепции в ООП и ФП сходны, особенно это видно на гибридных (а не чистых функциональных) языках. Замыкание создает объект с полями, алгебраические типы и pattern matching напоминают наследование и т.д.
OCaml, Haskell, CLOS в Common Lisp так или иначе добавляют ООП к функциональной парадигме. Ключевым является только вопрос изменяемого состояния у объектов, и вот он решается по-разному.
Теория категорий, на которой базируется Haskell, определяет категорию как набор доменов (данных) и морфизмов (стрелок, связей, функций) между ними — это похоже на определение ООП по Алану Кею (существуют только объекты и сообщения между ними + скрытие состояния и познее связывание).
Я сейчас как раз пытаюсь самостоятельно реализовать ООП на Лиспе через замыкания и setq, но пока выходит криво и неудобно. Хотелось бы что-то вроде:
Это прямо как корпускулярно-волновой дуализм в физике. Тот, кто постигнет связь между ФП и ООП, дойдет до полного просветления в computer science. Невежды же считают, что верна только одна из этих парадигм.
Как запру я тебя за железный замок, за дубовую дверь окованную,
Чтоб свету божьего ты не видела, мое имя честное не порочила…
М. Лермонтов. Песня про царя Ивана Васильевича, молодого опричника и удалого купца Калашникова
Здравствуйте, Sinclair, Вы писали:
S>Принципиальное отличие в наличии изменяемого состояния, для которого необходима идентифицируемость (identity).
Имхо, неизменяемого состояния не достаточно. В ФП не менее важно, что данные являются самодостаточными, они не привязаны к методам. То есть функции отдельно, данные отдельно. Это сильно меняет способ декомпозиции. ФП программа выглядит иначе — обычно это пайплайн из функций, через который "прокачиваются" данные. Если сделать обычные классы, у которыйх все поля readonly — это все равно ООП программа. ФП появляется, когда есть пайплайн.
Здравствуйте, mrTwister, Вы писали: T>Имхо, неизменяемого состояния не достаточно. В ФП не менее важно, что данные являются самодостаточными, они не привязаны к методам. То есть функции отдельно, данные отдельно. Это сильно меняет способ декомпозиции. ФП программа выглядит иначе — обычно это пайплайн из функций, через который "прокачиваются" данные. Если сделать обычные классы, у которыйх все поля readonly — это все равно ООП программа. ФП появляется, когда есть пайплайн.
Ну, во-первых, если вы попробуете написать программу с "обычными классами, у которых все поля readonly", а также константами вместо всех локальных переменных, то вы неизбежно получите пайплайн (как мне кажется).
Во-вторых, для практического программирования иммутабельности, конечно же, недостаточно.
Например потому, что эмуляция ФП в виде readonly-классов заставит вручную выписывать все замыкания, что сделает код утомительно многословным.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Worminator X, Вы писали: WX>Я сейчас как раз пытаюсь самостоятельно реализовать ООП на Лиспе через замыкания и setq, но пока выходит криво и неудобно. Хотелось бы что-то вроде:
У нас в магистратуре в курсе лекций по Clojure кратко упоминалась возможность навелосипедить себе какое угодно ООП. С наследованием хоть интерфейсов, хоть реализации, хоть состояния.
С прототипным наследованием и с наследованием классов. С одиночным и множественным наследованием. С одинарной, двойной, и множественной диспетчеризацией.
Один из предлагаемых курсовиков, ЕМНИП, как раз был "реализуйте свой вариант ООП". Поскольку у лиспа синтаксиса никакого нет, то любой результат будет не хуже встроенных лисп-конструкций.
Подробностей я, к сожалению, не запомнил, но могу взять копию слайдов у лектора
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Ну, во-первых, если вы попробуете написать программу с "обычными классами, у которых все поля readonly", а также константами вместо всех локальных переменных, то вы неизбежно получите пайплайн (как мне кажется).
Не получится пайплайн, так как функции не будут друг другом стыковаться. Чтобы стыковались, надо отделять данные от функций, как в линке, где есть отдельно IEnumerable и отдельно набор extension методов над ним. ФП — это когда вся программа подобным образом устроена. При этом сильно меняется подход к декомпозиции. Забавно смотреть, когда берут обычный ООП код, внутри метода какого-нибудь класса используют паттерн-матчинг и думают, что это теперь такой ФП
Здравствуйте, Разраб, Вы писали:
Р>В чем принципиальное отличие?
Отличие в "парадигме". ))
В точке взгляда на проблему и в способах её решения.
ООП предлагает проектирование сверху, т.е. начиная от высокоуровневых абстракций, оставляя реализацию абстракций гибкой.
И так на каждом уровне иерархии.
Сокрытие состояний в ООП принципиально, т.к. помогает несовершенному человеческому моску абстрагироваться от частностей.
В ФП данные открыты, проектирование идёт от вычислений над открытыми данными.
Т.е. трансформация состояний в ФП явная, в отличие от ООП.
Почти всегда в ФП удобней проектировать снизу, через анализ операций над данными.
Ввиду того, что поддержка абстракций в ООП изначально была выше (и этот подход доказал своё право на жизнь в сложном ПО), в ФП тоже активно завозят различные методы абстрагирования, например, классы типов в Хаскель для кортежей операций, или категории данных — эдакая шаблонизация устройства структур.
Но без "окончательной" инкапсуляции (сокрытия) устройства данных, ФП всерьёз не выстрелит.
А стоит только пойти на это — и ФП превратится в ООП. ))
В любом случае, ООП-подход на сегодня самый толерантный, т.к. включил в себя все известные на сегодня парадигмы.
Например, наработки из ФП прекрасно используются в ООП для реализации алгоритмов над данными, т.е. при наполнении абстракций "мясом".
=============
А про изменяемость/неизменяемость, как тут некоторые пытаются озвучивать, это лишь составные части парадигм, которые могут быть присущи обоим подходам. Это уже частности, которые уместны для конкретных рассматриваемых алгоритмов над данными. Для одних ситуаций неизменяемость помогает, для других мешает.
Здравствуйте, Sinclair, Вы писали:
S>Поэтому ООП — это не столько про "объединение функций и данных", сколько про представление решения в виде объектов, изменяющих своё состояние во времени.
Повторяешь известные заблуждения, на которые здесь отвечали еще в нулевые. ))
Основные отличия на сегодня только в явности или неявности представления состояния.
В ООП и ФП происходит одно и то же: вот есть некое состояние S, есть вычисления над ним, есть новое состояние S'.
Разница в том, что в ФП обычно можно одновременно ссылаться как на старое состояние S, так и на новое S', т.е. уникальность сущности как бы "расплывается".
Но в ФП точно так же можно организовать ассоциацию на новое состояние, чтобы эмулировать identity из ООП.
И обратное тоже верно, например, COW или RCU — это использование практик из ФП в ООП, т.е. явное разделение состояний и одномоментная "транзакционная" подмена текущего состояния S новым вычисленным S'.
Т.е., наработки из обоих подходов успешно взаимно проникают и о серьёзном противопоставлении парадигм говорить на сегодня уже бессмысленно.
Здравствуйте, Sinclair, Вы писали:
S>Это позволяет делать не только многостадийную инициализацию в одну строку, но и описывать произвольные функциональные зависимости. S>Например, нетрудно строить всякие конвейеры в стиле S>
S>from d in _dates select d.AddDays(17).AddYears(-1);
S>
Кстате, реляционки — хороший показатель того, как ООП и ФП прекрасно уживаются совместно.
Т.е. никакого противопоставления там нет и близко.
Запросы, хранимки, таблицы и строки таблиц — это всё объекты базы, в лучших традициях ООП.
С т.з. пользователя, при изменении строки база не порождает копию строки, запросом изменяется именно некая "конкретная" строка (берём самый востребованный случай наличия уникального ключа).
Но "унутре" вовсю резвится ФП, т.к., в зависимости от степени изоляции, одни юзвери в тот же самый момент могут видеть как старую версию строки при пробеге по таблице (если взяли иммутабельный снапшот), так и новую (попросили динамический курсор).
Здравствуйте, mrTwister, Вы писали:
T>Не получится пайплайн, так как функции не будут друг другом стыковаться. Чтобы стыковались, надо отделять данные от функций, как в линке, где есть отдельно IEnumerable и отдельно набор extension методов над ним.
Не обязательно. Посмотрите, к примеру, как устроены DateTime / TimeSpan. Там нет никакого отделения данных от функций, а пайплайн — есть.
Точно так же можно вполне себе убрать Enumerable extension methods, перенеся их внутрь всех перечислимых классов.
Вот, к примеру, в тайпскрипте из коробки есть аналогичный линку пайплайн на массивах — инстанс-методы map, reduce, filter. (а для Iterable<T> — нету). Они как раз считают массив неизменяемым.
Именно так и будет выглядеть пайплайн, если заставить писать все классы immutable. В принципе не получится сделать void List<T>.Add(T item), только List<T> List<T>.Add(T item).
А, значит, можно будет написать
const a = List<int>.Empty.Add(4).Add(8).Add(15).Add(16).Add(23).Add(42);
И всё остальное точно так же будет стыковаться — просто List<T>.Filter(...) будет возвращать List<T>. А чтобы, скажем, стыковать разные типы коллекций, у List<T> будут методы .ToArray() и static FromArray().
Enumerable с его екстеншн-методами — просто способ обойтись без написания map Select/reduce GroupBy/filter Where в каждой коллекции.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Во-первых, речь шла не о дотнете, а о C++. Современные компиляторы C++ прекрасно устраняют лишние копирования в цепочках вроде m2 = m1.setField1(17).setField2(42). S>Во-вторых, в дотнете иммутабельность прекрасно дружит с value-типами. Где джит тоже устраняет лишние копирования в цепочках модификаций.
В ассемблере просто жесть. Почти 90% функций set_string1/set_string2/set_string3 занимает move всего этого огромного this по цепочке. Я не вижу оптимизации или можешь тогда показать что не так в коде, может я идею не уловил?
Здравствуйте, Sinclair, Вы писали:
S>Не обязательно. Посмотрите, к примеру, как устроены DateTime / TimeSpan. Там нет никакого отделения данных от функций, а пайплайн — есть.
Это не то совсем. В этом подходе список функций фиксирован и определяется разработчиком типа. Это нормально для библиотечного типа вроде DateTime, и не подходит для бизнес-логики. У тебя могут быть два независимых модуля: А и Б, у модуля А свои функции и пайплайн написанный в терминах этих функций, у модуля Б свои функции. И выход пайплайна модуля А передается на вход пайплайна модуля Б. В твоем подходе ты не сможешь таким образом декомпозировать код на независимые модули.
Здравствуйте, mrTwister, Вы писали:
T>Здравствуйте, Sinclair, Вы писали:
S>>Не обязательно. Посмотрите, к примеру, как устроены DateTime / TimeSpan. Там нет никакого отделения данных от функций, а пайплайн — есть.
T>Это не то совсем. В этом подходе список функций фиксирован и определяется разработчиком типа.
Внешние функции никто не отменял. Что мешает строить пайплайн на них?
T>И выход пайплайна модуля А передается на вход пайплайна модуля Б. В твоем подходе ты не сможешь таким образом декомпозировать код на независимые модули.
moduleAModel
.doModuleAStuff() // can be extension
.toModuleBModel() // extension
.doModuleBStuff() // can be extension
//etc
Здравствуйте, mrTwister, Вы писали:
T>Это не то совсем. В этом подходе список функций фиксирован и определяется разработчиком типа. Это нормально для библиотечного типа вроде DateTime, и не подходит для бизнес-логики. У тебя могут быть два независимых модуля: А и Б, у модуля А свои функции и пайплайн написанный в терминах этих функций, у модуля Б свои функции. И выход пайплайна модуля А передается на вход пайплайна модуля Б. В твоем подходе ты не сможешь таким образом декомпозировать код на независимые модули.
Если у вас функция из модуля Б принимает на вход тип из модуля А, то в любом случае модуль Б зависит от А. Не получится сделать его независимым ни в ФП, ни в ООП, ни в процедурном программировании.
Более реалистичные варианты:
1. Функция из модуля А возвращает какой-то тип, определённый в модуле Core. Например, Tuple<string, int>. Прекрасно работает в модуле Б, который вообще ничего не знает про модуль А, и, может быть, даже написан раньше.
2. Функция из модуля М склеивает выход функции из модуля А со входом функции из модуля Б при помощи переходной функции, написанной в модуле М. Ну, там List<T> toList(T[] array). Тоже всё работает и в ФП, и в ООП.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, GarryIV, Вы писали:
GIV>Внешние функции никто не отменял. Что мешает строить пайплайн на них?
Ничего не мешает, я же ровно это выше по треду и писал:
Чтобы стыковались, надо отделять данные от функций, как в линке, где есть отдельно IEnumerable и отдельно набор extension методов над ним. ФП — это когда вся программа подобным образом устроена.
Здравствуйте, Sinclair, Вы писали:
S>Если у вас функция из модуля Б принимает на вход тип из модуля А, то в любом случае модуль Б зависит от А. Не получится сделать его независимым ни в ФП, ни в ООП, ни в процедурном программировании. S>Более реалистичные варианты:
Все так.
S>1. Функция из модуля А возвращает какой-то тип, определённый в модуле Core. Например, Tuple<string, int>. Прекрасно работает в модуле Б, который вообще ничего не знает про модуль А, и, может быть, даже написан раньше.
Не обязательно Tuple, это могут бить бизнес-ориентированные типы объявленные в базовом модуле.
S>2. Функция из модуля М склеивает выход функции из модуля А со входом функции из модуля Б при помощи переходной функции, написанной в модуле М. Ну, там List<T> toList(T[] array). Тоже всё работает и в ФП, и в ООП.
Назвать этот подход "ООП" — это как каша из топора. От ООП там остался только язык программирования, созданный в эпоху расцвета идей ООП.
Здравствуйте, mrTwister, Вы писали:
T>Назвать этот подход "ООП" — это как каша из топора. От ООП там остался только язык программирования, созданный в эпоху расцвета идей ООП.
Ну так это и есть ФП, завелосипеденное из "ООП с ограничениями".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Videoman, Вы писали:
V>В ассемблере просто жесть. Почти 90% функций set_string1/set_string2/set_string3 занимает move всего этого огромного this по цепочке. Я не вижу оптимизации или можешь тогда показать что не так в коде, может я идею не уловил?
Вообще, код самих функций неинтересен, т.к. они предназначены для инлайнинга, и в бою вызываться через call вообе не должны.
А main можно чуть улучшить, если внести тела функций внутрь определения класса. Это поможет инлайнингу, за которым, собственно, и должно последовать устранение временных копий. Ещё немножко эффективнее получается, если возвращать из set_stringX не obj, а obj&&.
Но даже после таких манипуляций на стеке остаётся три либо четыре временных объекта, и main превращается в перекидывание данных из одного в другой. Подозреваю, что вмешивается нетривиальная логика std::string.
Н-да, похоже, я переоценил мастерство современных компиляторов С++.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Но даже после таких манипуляций на стеке остаётся три либо четыре временных объекта, и main превращается в перекидывание данных из одного в другой. Подозреваю, что вмешивается нетривиальная логика std::string. S>Н-да, похоже, я переоценил мастерство современных компиляторов С++.
Аналогичные мысли. Сам стараюсь, где только можно, использовать иммутабельный подход. Уж больно хорошо упрощается управление зависимостями времени жизни объектов. Поэтому заинтересовал твой пример и решил проверить на практике и сразу напоролся на ограничения компилятора. Вообще конечно, такое можно проделать для объектов, у которых move гарантированно дешевый, например состояние поместить в std::unique_ptr и перетаскивать указатель, реализовать копирование и делегировать все вызовы к внутреннему состоянию, но это уже не стек и куча ручной оптимизации, к сожалению.
Есть такая идея, её озвучил папа ООП, Алан Кей, сославшись на Боба Бартона, что все сущности [относящиеся к одному уровню вложенности] надо уравнивать в правах. И получится что-нибудь крутое.
Когда что-нибудь (например, функции) становятся, как это называют, "гражданами первого класса", это оно и есть.
Ну вот, если посмотреть с этой точки зрения, то идеи ООП и ФП — это разные права граждан первого класса, которыми наделили функции. То есть, разные аспекты уравнивания.
ООП это когда функциям (а не только переменным) позволили прописаться в структурах.
ФП это когда функциям (а не только переменным) позволили быть переданными как параметры. Причём, стало можно определять по месту (при вызове) не только выражения, вычисляемые как значения обычных переменных, но и выражения, задающие функции (т.е. лямбды).
В сумме получается C#, кумбайя, кумбайя Поэтому не надо их противопоставлять.
I'm a sewer mutant, and my favorite authors are Edgar Allan Poo, H.G. Smells and George R.R. Martin.