LINUX.ORG.RU

Вопрос про zig

 , абстракция


0

2

Это правда, что зиг не поддерживает интерфейсы (а ля чисто-абстрактные классы в c++, impl в rust и т.д.)? Т.е. надо в коде всё время бойлерплейт вставлять, типа:

fn add(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
    comptime {
        if (!@hasDecl(@TypeOf(a), "add"))
            @compileError("type must support add");
    }
    return a + b;
}

Если реально так, то не понимаю, кому может понравиться така поделка. Как вообще документировать интерфейсы либ, фреймворков? А если никто не написал документацию (стандартный сценарий развития разработки)?

★★★★★

Последнее исправление: seiken (всего исправлений: 1)

Офигеть, в языке без интерфейсов неудобно работать с интерфейсами!Какой позор!

Может, стоит выбрать соотв. язык к задаче, а не ругать, что язык не может во всё?

Зигу не нужны такие конструкции, у него мощный комптайм и он не нуждается в подобной яме, как классы и прочее…

anonymous
()

Я чёт, подозреваю, что это вопрос из разряда -«а хде в этих ваших расто-хаскелях наследование, чтоб прямо вот дословно как в жабке?». У языка заметно другая идиоматика. Вот это:

    comptime {
        if (!@hasDecl(@TypeOf(a), "add"))
            @compileError("type must support add");
    }

просто вынесется в отдельную компилтайм функцию предикат is_addable(comptime T: type), один раз и в одном месте и где надо будет «вызываться». И отличие от, например, растового «where T: Add» будет, что называется, с точностью до переобозначения. И небольшая проблема тут только отсутсвие перегрузки операторов. Зиг особо не знаю, так краем глаза посматриваю, язык так-то занятный и подкупает как раз вот этой непосредственной вербозностью работы с типами

zurg
()
Ответ на: комментарий от zurg

чёт, подозреваю, что это вопрос из разряда -«а хде в этих ваших расто-хаскелях наследование

нет. Вопрос был вообще не про наследование. Интерфейсы есть уже в С. Это структура с членами-указателями на функции. Для разраба вызываемых функций (коллбеков) такие таблицы указателей служат compile-time проверкой, что в вызывающем коде все функции имеют корректную сигнатуру. Для разраба вызывающих функций такая структура - декларация того, что пользовательский код можно вызывать безопасно, т.е. что в подсовываемые реализации интерфейса содержат функции с нужными сигнатурами.

В случае с зигом такое ощущение, что он отказался от интерфейсов по каким-то своим психиатрическим причинам. Детский лепет про неэффективность vtbl поскипан.

seiken ★★★★★
() автор топика
Ответ на: комментарий от seiken

Вопрос был вообще не про наследование.

Да, это как отдалённый пример, в смысле, искомая фича в данном языке или выглядит по другому или вообще не нужна.

Интерфейсы есть уже в С

Пфф, в зиге тоже можно указатели на функции в структуру напихать, да и просто и методы есть и модульность

zurg
()
Ответ на: комментарий от dataman

Zig does not have an interface keyword — and that’s a feature, not a bug.

Прочитав статью я склоняюсь, что это скорее баг, а не фича.

Авторы языка (стандартной библиотеки) явно используют этот паттерн, то есть аргумент «не нужно» отметаем.

Остается аргумент «DIY», чтобы как бы прочувствовать цену абстракции и принимать осознанное решение. Но, видишь ди ты эту цену при, возможно неявном, вызове аллокаторов или стриминговых функций? Так ли это действительно важно? Со стороны это выглядит скорее как эпатажное «фи» чем продуманный и обоснованный дизайн.

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

anonymous
()
Ответ на: комментарий от anonymous

которую имело бы смысл скрыть за дефолтной реализацией с удобным синтаксисом

Так функции же пишутся. И реализацию можно сделать с произвольными параметрами и условиями, а не тем куцым огрызком, который в качестве отдельного языка времени компиляции в Си/Расте (ладно, в Расте можно полнофункциональный макрос наваять, но так не пишут).

Условным неудобством является только то, что тип не виден в заголовке, а надо смотреть ещё несколько строк. Но в Си++ с шаблонами та же проблема (посмотри на итераторы).

monk ★★★★★
()
Ответ на: комментарий от monk

Условным неудобством является только то, что тип не виден в заголовке, а надо смотреть ещё несколько строк.

Так в этом уже как минимум одна проблема. А если автор занимается проверкой типов на строке 197 функции abcdxyz?

seiken ★★★★★
() автор топика
Ответ на: комментарий от seiken

Таким образом, зиг способствует написанию write-only лапши. А в 21м веке даже малограмотные знают, что программист тратит больше времени и усилий на чтение единицы исходных кодов, чем на её написание.

seiken ★★★★★
() автор топика
Ответ на: комментарий от seiken

Так в этом уже как минимум одна проблема. А если автор занимается проверкой типов на строке 197 функции abcdxyz?

Тогда при компиляции увидишь ошибку. Причём нормальную, а не «в строке 197 abcdxyz не удалось подобрать кандидат реализации шаблона … и тут 500 строк …».

monk ★★★★★
()
Ответ на: комментарий от monk

а не «в строке 197 abcdxyz не удалось подобрать кандидат реализации шаблона

в С++ скоро всё переведут на концепты, надо только потерпеть

seiken ★★★★★
() автор топика
Ответ на: комментарий от zurg

Это где это в расте куцый огрызок?

Как в расте написать

fn f(comptime T: type, a: T, b: T) T {
    return switch (@typeInfo(T)) {
        .comptime_int => f_comptime(a, b),
        .int => |info| if (info.bits <= 16)
            f_runtime(a, b)
        else
            @compileError("ints too large"),
        else => @compileError("only ints accepted"),
    };
}

Тем более на фоне сишки.

То в сишке и расте куцый на фоне зига.

monk ★★★★★
()
Ответ на: комментарий от seiken

Например. Или можно сделать что-то вроде

fn mult(a: matrix, b: matrix) Matrix(a.n, b.m) {
  comptime {
    if (a.m != b.n)
       @compileError("должно совпадать количество колонок в a и строк в b");
  }
  ...
}
monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от seiken

А зачем здесь comptime?

Чтобы проверка была при компиляции у программиста, а не при запуске у пользователя.

А если я передаю матрицы из кода на zig в код на C?

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

Можно сделать общий код, который будет работать как с динамическими (для которых будет проверка времени выполнения), так и со статическими матрицами.

monk ★★★★★
()
Ответ на: комментарий от monk
#[derive(Debug)]
pub struct Matrix<const N: usize, const M: usize> {
    pub data: [[i32; M]; N],
}

pub const fn matrix_mul<const N: usize, const M: usize, const P: usize>(
    l: &Matrix<N, M>,
    r: &Matrix<M, P>,
) -> Matrix<N, P> {
    let mut result = [[0; P]; N];

    let mut i = 0;
    while i < N {
        let mut j = 0;
        while j < P {
            let mut k = 0;
            while k < M {
                result[i][j] += l.data[i][k] * r.data[k][j];
                k += 1;
            }
            j += 1;
        }
        i += 1;
    }

    Matrix { data: result }
}

fn main() {
    const M_RES: Matrix<2, 2> = matrix_mul(
        &Matrix {
            data: [[1, 2, 3], [4, 5, 6]],
        },
        &Matrix {
            data: [[1, 2], [3, 4], [5, 6]],
        },
    );

    dbg!(M_RES);

    // Compile error
    // 
    // matrix_mul(
    //     &Matrix {
    //         data: [[1, 2, 3, 0], [4, 5, 6, 0]],
    //     },
    //     &Matrix {
    //         data: [[1, 2], [3, 4], [5, 6]],
    //     },
    // );
}

Darfin
()
Ответ на: комментарий от Darfin
error[E0308]: mismatched types
  --> src/main.rs:48:20
   |
48 |              data: [[1, 2], [3, 4], [5, 6]],
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a size of 4, found one with a size of 3

А можно сделать нормальное сообщение об ошибке? И чтобы указывало на matrix_mul, а не на создание матрицы. Ведь может как раз здесь верно, а в [1, 2, 3, 0] лишний элемент.

monk ★★★★★
()
Ответ на: комментарий от Darfin

А сделать, чтобы позволяло в том числе динамические?

Вот так:

fn mult(a: matrix, b: matrix) Matrix(a.n, b.m) {
  comptime {
    if (a.m != b.n && a.m && a.n)
       @compileError("должно совпадать количество колонок в a и строк в b");
  }
  // если a.m и b.n ненулевые, то это будет выполнено при компиляции и проверка выкинется
  if ((a.m == 0 || a.n == 0) && a.current_m != b.current_m) { 
    return matrixError.BadRowsColumns;
  }
  ...
}
monk ★★★★★
()

Если реально так, то не понимаю, кому может понравиться така поделка

Тем кто пишут хелловорлды на C, очевидно. Сколько раз уже говорили, не надо серьёзно этот язык рассматривать.

anonymous
()
Ответ на: комментарий от monk

А можно сделать нормальное сообщение об ошибке?

В расте паника в const функциях отображается как ошибка компиляции с любым произвольным текстом.

Другое дело, что применение на практике константных функций сильно ограничено.

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

Приведи практический пример на зиге с матрицами: перемножить две матрицы в компайл тайме и вывести результирующую матрицу в рантайме. Скорее всего, окажется, что размеры матриц будут жестко определены и результирующая матрица будет размещена не в динамической памяти.

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

Darfin
()
Ответ на: комментарий от Darfin

В расте паника в const функциях отображается как ошибка компиляции с любым произвольным текстом.

Как сделать, чтобы если не выполнилось

    l: &Matrix<N, M>,
    r: &Matrix<M, P>,

запустилась const функция?

Приведи практический пример на зиге с матрицами: перемножить две матрицы в компайл тайме и вывести результирующую матрицу в рантайме.

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

А как ты в компайлтайме сделаешь не жёстко определённую матрицу?

результирующая матрица будет размещена не в динамической памяти

Если очень хочется, можно и в динамической. Что-то вроде

    comptime a = gen_comptime_vector(...);
    const dyn_a = try allocator.alloc(Matrix(a.n, a.m)); 
    comptime var i = 0;
    inline while (i < a.m * a.n) : (i += 1) {
      dyn_a[i / a.m][i % a.m] = a[i / a.m][i % a.m];
    }

Сгенерирует много строчек типа

dyn_a[0][0] = 1.0;
dyn_a[0][1] = 42.3;
dyn_a[1][0] = 24.2;
...
monk ★★★★★
()
Ответ на: комментарий от Darfin

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

Я же привёл пример практической задачи: функция, которая проверяет размерность матриц при компиляции, если эта размерность известна при компиляции и при выполнении, если не известна.

monk ★★★★★
()
Ответ на: комментарий от Darfin

Так эту функцию написать надо. Я общий подход показал. Можно нагенерировать данные при компиляции (вплоть до умножения матриц), а потом на основании этих данных нагенерировать код.

Просто задача очень странная. Всё равно, что числа Фибоначчи на шаблонах Си++ считать.

monk ★★★★★
()
Ответ на: комментарий от monk

Если код так запущен что понадобилась специализация, то сейчас можно накостылить(когда-нибудь стабилизируют полноценную) как-то так:

//какие-то внешние ф-ии которые надо обобщить
const fn f_comptime(a: i32, b: i32) -> i32 {
    a + b
}
//эта для 16 и менее битовых 
fn f_runtime<T>(a: T, b: T) -> T
  where  T: Sized + Sub<Output = T>,
{
    a - b
}

trait FunInt { fn f(&self, b: i32) -> i32;  }
impl FunInt for i32 {
    fn f(&self, b: i32) -> i32 {
        f_comptime(*self, b)
    }
}
pub trait Fun  where  Self: Sized {
    type R;
    fn f(&self, b: Self::R) -> Self::R;
}
impl<T> Fun for &T
    where T: Copy + Sub<Output = T>
{
    type R = T;
    fn f(&self, b: T) -> T {
        const { assert!(16 >= 8*size_of::<T>(), "ints too large") }
        f_runtime(**self, b)
    }
}
//чисто косметический макрос 
macro_rules! fun {
    ($a:expr, $b:expr) => { (&$a).f($b) };
}

pub fn main() {
    dbg!(fun!(43u16, 34u16));
    dbg!(fun!(34i32, 43i32));
    //dbg!(fgen!(34i64, 43i64)); ошибка компиляции
    dbg!((&5i8).f(5i8));
}

https://godbolt.org/z/jbshbcK37. В общем, нормально всё у раста со статической диспетчеризацией, как и с применением макросов. Они полноценные в отличие от первобытного сишного текстоменятеля.

zurg
()
Ответ на: комментарий от zurg

Они полноценные в отличие от первобытного сишного текстоменятеля.

Так я в первом сообщении и написал, что на макросах наваять проверку можно.

monk ★★★★★
()
Ответ на: комментарий от zurg

исправления:

//dbg!(fgen!(34i64, 43i64)); ошибка компиляции заменить на //dbg!(fun!(34i64, 43i64)); //ошибка компиляции

и #[inline(never)] в других местах (const assert ничего не стоит как и весь этот диспатч) https://godbolt.org/z/boarbzbxM

zurg
()
Последнее исправление: zurg (всего исправлений: 2)