Pull to refresh

Comments 36

по поводу классов: от того, что всегда есть значения по умолчанию, всё хорошо будет, не в этой модели, а в общем случае? и не будет ошибок с тем, что потенциально можно пропустить инициализацию какого-то поля с данной моделью их инициализации? хотя понимаю, что более старые языки тоже многие к этому склонны, но всё-таки.

и если тут утверждается, что тут счёт ссылок неатомарный, то что будет в многопотоке?

есть ли какая-то гарантия что я не протащу слабую ссылку в другой поток имея возможность редактировать основную из этого?

по поводу классов: от того, что всегда есть значения по умолчанию, всё хорошо будет, не в этой модели, а в общем случае? и не будет ошибок с тем, что потенциально можно пропустить инициализацию какого-то поля с данной моделью их инициализации? хотя понимаю, что более старые языки тоже многие к этому склонны, но всё-таки.

Я бы сказал наоборот, более новые языки отказываются от конструкторов, например тот же Раст. В языках, где есть конструкторы (например в Java) вводятся особые категории объектов (beans) имеющие конструкторы без параметров, чтобы поддерживать сериализацию для сетевого общения и персистентности. Или вводится паттерн builder, который позволяет избавиться от ограничений конструкторов.

и если тут утверждается, что тут счёт ссылок неатомарный, то что будет в многопотоке?

Аргентум возводит неизменяемость шаренных объектов в абсолют (да пребудет с ним сила). Это даёт важную гарантию: любой поток, имеющий шаренную ссылку на объект, может быть уверен, что этот объект и вся доступная из него по любому графу ссылок структура тоже неизменяемы. Более того, все эти объекты гарантированно живы, а ссылки валидны до тех пор, пока поток удерживает корневую ссылку. А корневая ссылка всегда лежит либо в стеке потока, либо в объекте, принадлежащем этому потоку. Отсюда имеем два следствия:

  • почти весь доступ к таким объектам можно выполнять вообще без операций retain/release;

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

Поэтому даже те немногие операции retain/release, которые всё же нужны, можно откладывать, группировать в пачки и свободно переупорядочивать - главное, чтобы retain всегда предшествовал release. Благодаря этому их можно выполнять либо на отдельном служебном потоке, либо под одним мьютексом, захватываемым раз в десятки тысяч операций. В итоге общая стоимость синхронизации тоже делится на те же десятки тысяч.

есть ли какая-то гарантия что я не протащу слабую ссылку в другой поток имея возможность редактировать основную из этого?

Вы можете передавать слабые ссылки между потоками и посылать по ним асинхронные сообщения (система сама доставляет такие сообщения в нужный поток). Но вы не можете синхронно разыменовывать слабую ссылку, указывающую на объект другого потока. Именно поэтому в Аргентуме отсутствуют гонки данных.

А поскольку единственный способ межпоточного взаимодействия — это асинхронная отправка сообщений, дедлоки тоже исключены.

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

мне до сих пор была интересна эта серия статей, так что я бы подождал одну про синхронизации, спасибо.

кроме раста я наверное не знаю языков, в которых к синхронизации как-то принуждают (но я не особо и разбирался в теме), так что также будут интересны и сравнения (в расте же вроде есть трейты Sync и Send, которые мешают передавать значения нарушающие синхронизацию в другой поток — даже если разымплементировать их надо вручную для чего-то самописного; и чтобы взять ссылку из значения под мьютексом, нужно его залочить, если память не изменяет)

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

----

по поводу конструкторов:

в расте у полей значения по умолчанию запрещены, и каждое поле нужно обязательно инициализировать; чтобы же получить значение по умолчанию нужно использовать Default::default() или какой-нибудь _::new(), а чтобы проинициализировать напрямую нужноуказать значение для кпждого поля по отдельности.

противоположным является подход например в C, Java или C#, где если не указать значение по умолчанию, то выберется конструктор по умолчанию, null или т.п. Конечно, в C# есть required, который правда всё равно для части случаев будет так себе, а в плюсах можно написать какой-то собственный простенький тип, в котором будет что-то вроде

template<class T>
operator T() const
{
  static_assert(false);
}

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

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

заодно синтаксис value.{/*lambda*/} мне напоминает что-то похожее в сишарпе или джаве (хотя наверное в котлине оно и вовсе будет околоидиоматичным, хотя и не уверен):

new SomeType() {{
  some_field = ...;
  ...
}}
some_value with {some_field = ..., ...}
some_value.apply{
  some_field = ...
}

some_value.also{
  it.some_field = ...
}

впринципе, в C# (и Rust тоже вроде как) также можно и сделать extension-метод, чтобы работал так же как also в котлине, но всё равно котлин в этом плане как будто красивейший, и подход из аргентума мне напоминает его, тем более что это ещё и идёт по умолчанию — по дизайну решение безусловно красивое.

ну а так, мне тоже нравится будто бы повсеместный отказ от конструкторов (и от перегрузок функций в частности)

просто в аргентуме значение по умолчанию будто бы словно есть абсолютно всегда, если я правильно понимаю, что не во всех ситуациях полезно (условно не существует же никакого сокета или дескриптора файла по умолчанию — верно? а если и есть, то стоит считать, что он должен вести себя равно какому-нибудь null, не так ли?)

DOM - это очень узкий класс задач, точнее часто используемый класс, но составляющий только некоторую часть программы.

ЯП общего назначения дают широкий выбор реализации модели. Хоть и не так удобно.

В этой реализации - основанной на ссылках на динамическую память, вероятно, стоит конкурировать с королями списков - Lisp/Racket.

За мою 30+ летнюю практику в программировании я или сразу реализовывал все приложения на DOM-подобных структурах данных или, приходя в доведенный до ручки проект подкупом, лестью, угрозами переводил задачу на DOM-подобные структуры данных, попутно вычищая тонны костылей и убирая глюки уровня бизнес-логики, утечки и падения. В каком-то смысле вы правы, DOM составляет не 100% от структур данных программы, а только 99%. Остальное можно реализовать на C/C++ тщательно покрыть тестами и выставить на уровень бизнес-логики через FFI.

Геймдев и ембеддед, любящие статику и арены, будут не согласны.

Да и мне обычно хватало массивов или мапов структур.

Собственно я и не против, но я за свободу выбора структур данных под задачу.

Дальний предок Аргентума - zsScript - секретный язык компании Зодиак Гейминг - как раз использовался в течение 20 лет и продолжает использоваться до сих пор в глубоком эмбеддинге для написания игр и UI-rich приложений во встраиваемой технике - в первую очередь в системах кабельного телевидения, где он рвет все прочие технологии как тузик грелку, в то время как современные игровые движки давно перешли на языки со сборкой мусора и начали течь и тормозить.

основанной на ссылках на динамическую память, вероятно, стоит конкурировать с королями списков - Lisp/Racket.

Языки, в которых полиморфные структуры данные, контейнеры переменных размеров и графы объектов представлены ссылками на динамически аллоцированную память - это вообще все языки программирования.

Категоричность обычно неверна. Куда бы ткнуть пальцем? - например в Модулу-2

//  Modula2
module CardItems;

type
 CardItem     = pointer to CardItemRec;
 CardItemKind = (Text, Image, Button);

TextItemPtr  = POINTER to TextItem;
ImageItemPtr = POINTER to ImageItem;
ButtonItemPtr= POINTER to ButtonItem;

CardItemRec = record
 kind : CardItemKind;
end;

TextItem = record (CardItemRec)
 text : array [0..63] of char;
end;

...

CardItemArray = POINTER TO ARRAY OF CardItemPtr;

var
 items : CardItemArray;
 n     : CARDINAL;
 t : TextItemPtr;
 img : ImageItemPtr;
begin
 n := 3;
 NEW(items, n);

 NEW(t); t^.kind := Text;
 items^[0] := t;
  
 NEW(img); img^.kind := Image;
 items^[1] := img;
  ...
end.

Сто лет на модуле не писал, могуть быть некоорые синтаксические ошибки.

Тогда я ошибся - прозевал когда ранее смотрел возможности в стандарте.

Есть NEW в перечне стандартных процедур.

Где можно взять компилятор Аргентум?
Код в статье выглядит интересно, но хотелось бы попробовать его для своих задач.

А как представить большой произвольный граф( к примеру, друзья в соцсетях) ? Только слабыми ссылками ?

class SocialNetwork {
    people = Array(Person);
    groups = Array(Group);
}
class Person {
    name = str;
    birthday = Date;
    following = WeakArray(Person);
    groups = WeakMap(String, Group);
}
class Group {
    name = str;
    members = WeakArray(Person);
    content = Array(Post);
}

Конечно слабыми ссылками.

Группа (со всеми своими постами) не должна удаляться, при выходе из нее последнего человека, ее время жизни контролируется исключительно самой социальной сетью.

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

Так что да, слабые ссылки тут максимально уместны.

А есть ли примеры важных( в реальных программах) графов, которые нельзя эффективно реализовать на языке Аргентум ?

Я не встречал, если увидите, дайте знать.

В реальных программах графы будут динамически подгружаемыми или из базы или из джейсона или из хмл.

А это уже другая история.

Но тоже было интересно протестировать на гигабайтных размерах..

Пример графа, загруженного из JSON http://lat.asuscomm.com:3000/?t=JSON. Код парсит JSON в нативные объекты Аргентума, обрабатывает их и записывает обратно. В Аргентуме все структуры данных динамические, ему все равно, откуда они берутся.

Пример бенчмарка с аллокацией деревьев: https://aglang.org/how-fast-is-argentum-part-2/ расход памяти меньше чем у C++ и Раста.

===В Аргентуме все структуры данных динамические ===


И массивы тоже ? И почему они не встроенный тип ?

И массивы. Все языки, в которых массивы встренные, имеют вдобавок два-три вида массивов-объектов и тонну конверсий между ними. Зачем усложнять?

Массивы только динамические- здесь же должна быть потеря скорости ? И если да, то насколько ?

Какие репозитарии на гитхаб актуальны ?
Старые( carol11) давно не обновлялись.
В новых ( https://github.com/kota-codex ) версии компонент Аргентума уже с номерами 1.x , что странно для экспериментального языка.

Актуальными будут репы kota-codex. Пеезд произошел по политическим мотивам, чтобы фейсбук-мета не украл язык. Система версий Аргентума не отражает "экспериментальность".

  • Старший номер увеличивается при частичной потере обратной совместимости.

  • Средний номер - односторонняя обратная совместимость.

  • Младший - номер билда/фикса без новых фич, но этот номер не пишется, если 0. Он вообще редко будет использоваться

Небольшие изменения в синтаксисе иногда происходят. Например, раньше был особый синтаксис введения именованного значения для ? && операторов (a?=name expr), теперь там просто лямбда (a?`name expr) это, кстати, сделано по советам с хабра. Но в основном новые вещи добавляются, а не изменяются.

Касаемо препринтов - я собираюсь их отозвать, т.к. они были поданы во время моей работы в мете, которая повела себя "немного нехорошо"... в общем публикации будут, но позже и немного в другом формате.

Мне кажется единственный правильный источник материалов на сегодня - aglang.org

Посмотрел повнимательнее. Выглядит так, что взята частная задача (часто она встречается или нет, спорить смысла нет) и под неё сделан язык.

Ес-но, данную конкретную задачу он выполняет хорошо. А вот какой ценой?

Что мы видим:

  1. Глубокий автовывод типов. Это известный подход с определёнными недостатками. В том числе для указателей с интроспекцией типов:(`card  card.items[42] && _~Button) - Ну будет рантайм паника или тихий ноль, а толку? Все равно покрывать тестами

  2. Глубокое копирование структур по умолчанию. Оно нужно далеко не всегда. Частичное, соответственно придётся делать руками?

  3. Запрет циклических ссылок. А циклический связной список можно сделать? Или вероятно через одну слабую ссылку и будет неудобно.

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

  5. Запрет на самоссылки? Ну это раст-вей - давайте что-то неудобное компилятору запретим, а потом будем героически с этим бороться =)

Как с этим бороться - только пробовать писать реальные приложения, и в процессе дорабатывать, что неудобно.

Ждем статью, как при неатомарных счетчиках ссылок реализуется беспроблемная многопоточность.

`card  card.items[42] && _~Button) - Ну будет рантайм паника или тихий ноль, а толку? Все равно покрывать тестами

Не будет ни паники ни нуля. Будет optional_none правильного optional<T> типа, который обработается по цепочке следующими && ? || : операторами и превратится в правильную реакцию бизнес логики.

Глубокое копирование структур ... нужно далеко не всегда. Частичное, соответственно придётся делать руками?

Частичное копирование повреждает структуры данных нарушая инварианты композиции. Если оно у вас есть, значит вы или неосознанно используете в этом месте агрегацию (потому что ваш язык не различает эти два понятия - композицию и агрегацию) или вы ломаете ваши стркутуры данных и логику приложения.

Запрет циклических ссылок. А циклический связный список можно сделать? Или вероятно через одну слабую ссылку и будет неудобно.

Циклический список это низкоуровневая деталь реализации структуры данных, которая не должна быть видна уровню бизнес-логики, так же как не должны быть видны goto и адресная арифметика.

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

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

Запрет на самоссылки? Ну это раст-вей - давайте что-то неудобное компилятору запретим, а потом будем героически с этим бороться =)

Я не знаю, что такое "Запрет на самоссылки". В Аргентуме - запрет на само-владение. Если он вам кажется не логичным, обоснуйте.

В этом вы безусловно правы: Решается частная задача обеспечения целостности структур данных высокоуровневых приложений. Не любых комбинаций байт в памяти, а только иерархий объектов, удовлетворяющих UML ограничениям композиции, аграгации, ассоциации.

пробовать писать реальные приложения, и в процессе дорабатывать, что неудобно.

Агрентум построен на ссылочной модели, обкатанной 20-летней эксплуатацией в телевизионной кабельной индустрии (Надо бы статью про историю написать). Есть более сотни запущенных коммерческих проектов. Я бы вас попросил с вашей стороны предложить сценарии из вашей практики, когда эта ссылочная модель не применима.

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

И наоборот, если пропал из списка больных, значит съели инопланетяне, и он должен исчезнуть из организации, Дом же на страже)

когда эта ссылочная модель не применима.

Я не только про ссылочную модель, а о языке вообще.

И наоборот, если пропал из списка больных, значит съели инопланетяне, и он должен исчезнуть из организации, Дом же на страже)

class Org {
   employees = Map(String, Employee); // владение (композиция)
   sick = WeakArray(Employee);        // просто ссылки (ассоциация)
}

Речь шла, напомню, про запрет на 2 родителя.

Что владельцем пишем в карточке сотрудника? И как будем тогда работать со вторым владельцем ?

А ну т.е карточка сотрудника про список больных сама не знает.

Тоже вариант.

А как выглядит обращение, даже лучше итерация по weakarray, тоже красиво, с опционал?

x = WeakArray(Employee);
...
x[20] ? _.givePromotion() : handleIfNone();

Посмотрел в код array.ag. Прямо сейчас в классе WeakArray нет метода для итерирования, но поскольку классы Аргентума открыты, можно дописать свой:

class WeakArray {
    each(action(T)) {
        forRange(0, size()) {
            this[_] ? action(_)
        }
    }

}
...
x.each`i {
   i.givePromotion()
}
//или
x.each\_.givePromotion()

в разделе " что плохого" указан "птичий язык".

А какие альтернативы ? Использовать больше ключевых слов ?
Мне нравится использование знаков препинания вместо if else( и не только) , но есть проблемы в некоторых знаках( доставшихся от Си и далее). К примеру, использование для иначе- ":" . Это неинтуитивно, нелогично. Может стоит заменить просто двоеточие на "#:" Решетка выступает просто в качестве отрицания- первая ветвь после if( "?") утвердительная ,вторая отрицательная.
Но в любом случае хорошо отказаться от восклицательного знака для отрицания. Это не просто не-интуитивно, это контр-интуитивно. Восклицание это скорее утверждение чего-то. Знак тильда хорошо подходит для отрицания, но у вас он уже занят. Может переменить это ? Если же не использовать для отрицания тильду, то тогда знак "#" хорошо подходит . Как решетка и как дважды перечеркнутое равно. Сравнение на неравно тогда будет- "#=" .
Визуально лучше всего, конечно, знаки из полного Юникод выбирать. Но их с клавиатуры сложно вводить.

Желательно написать статью про тот самый 1% прикладных задач, для которых собственных средств Аргентума недостаточно, и нужно использовать FFI .
И как это влияет на безопасность-сохранность( safety) .

Sign up to leave a comment.

Articles