Работая над проектом far2l (порта Far Manager для Linux), я наткнулся на один очень красивый баг, который в итоге удалось починить только с помощью нейросети ChatGPT. Причём баг оказался не столько в самом far2l, сколько в кроссплатформенной UI библиотеке wxWidgets. Бесплатная версия ChatGPT (3.5) с задачей справиться не смогла, а вот платная (4) таки справилась. Сегодня поделюсь с вами рассказом о том, как это было.

В чём заключался баг?

Есть такой консольный текстовый редактор — turbo. Он сделан на современной адаптации фреймворка Turbo Vision, и интересен тем, что поддерживает перенос по словам — как в блокноте Windows или html textarea. Поэтому если нужно работать в консоли с длинными текстами, он подходит идеально. В этом редакторе, если запустить его из-под встроенного терминала far2l, горячие клавиши Alt+ЛатинскаяБуква не работали в русской раскладке. Ожидаемо? Да. Но неприятно. Мы это починили.

Шаг первый

А можно ли вообще передать через терминал состояние клавиши, не привязанное к текущей раскладке клавиатуры? Стандартными средствами — нельзя: скажем, GNOME Terminal в ответ на комбинацию Alt+ф шлёт символы \x1B \xD1 \x84, т.е. ESC и потом номер символа в Unicode. Но far2l имеет свой специальный режим расширенной поддержки клавиатурного ввода, far2l terminal extensions! И там есть поле Virtual Key Code (протокол основан на формате описания клавиатурных событий Windows), значение которого не привязано к раскладке клавиатуры. Просим автора turbo добавить поддержку этого протокола, пишем прототип, чтоб ему проще было. Это шаг первый. Но баг остаётся на месте!

Шаг второй

Смотрим, что же приходит в turbo в этом самом расширенном протоколе, и видим, что в поле Virtual Key Code для комбинаций Alt+РусскаяБуква — нули. Почему так происходит? Изучаем код far2l, и выясняем, что 0 в качестве кода символа приходит для таких событий от wxWidgets, на которых сделана графическая версия far2l. Почему?

Шаг третий

Собираем демо-приложение keyboard из wxWidgets под Windows и GTK и сравниваем поведение: пробуем нажимать там Alt с русскими буквами. Поведение и правда отличается! В GTK-версии key_code событий KeyUp/Down в таких случаях нулевой, а в Windows версии там латинская буква, соответствующая нажатой клавише. А событие OnChar в GTK версии для таких комбинаций не выстреливает вовсе! Вот если бы wx вели себя как в Windows, это самое значение латинской буквы можно было бы как раз использовать для заполнения поля Virtual Key Code. Но где его взять? Учитывая, что ни API GTK (через который wxWidgets работают в Linux), ни API X11 я толком не знаю. И, похоже, решить нашу задачку, используя эти API, не так-то просто, ведь тикеты про проблему с нулевым кодом клавиши висят в баг-трекере wxWidgets годами! А ещё надо чтоб это всё работало в Wayland.

Шаг четвертый

Выходит, нам надо как-то научить wxWidgets догадываться о том, какая латинская буква соответствует нажатой клавише в русской раскладке. Как? Очевидного способа в голову не приходит. От GTK в wxWidgets приходит так называемый код символа (соответствующий кириллической букве) и RAW скан-код из X11 (который можно сопоставить латинской букве, но хардкодить таблицу преобразования не хочется, да и в wx такой хак едва ли примут). Латинскую букву, соответствующую нажатой в русской раскладке клавише, API GTK не отдаёт. На этой стадии я зависаю примерно на год: тикет о проблеме в wx открыт в январе 2022го.

Шаг пятый

Все обсуждают ChatGPT, я тоже решаю поэкспериментировать с ней. А что если поставить ей задачку: «как получить код латинской буквы из скан-кода X11», и посмотреть, что будет? Спрашиваю. Пока ещё бесплатную версию, 3.5:

Please write a code sample how to get the Latin character corresponding to the known key code from X11 event record, if the Latin keyboard layout is not installed in the system, using X11 C language API.

(Общаться с ChatGPT лучше на английском, на нём она «умнее», и всё же я буду приводить здесь русский перевод нашей переписки для русскоязычных читателей, выделяя его курсивом. Вот так: пожалуйста, напиши пример кода, как получить латинский символ, соответствующий известному коду клавиши из записи события X11, если в системе не установлена латинская раскладка клавиатуры; используй X11 API для языка Си)

В ответ она выдаёт мне вполне рабочий пример кода, но увы — он возвращает символ в текущей раскладке, а вовсе не в латинской!

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

int main() {
    Display *display;
    Window window;
    XEvent event;
    int screen;

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Cannot open display\n");
        return 1;
    }

    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 500, 300, 1,
                                  BlackPixel(display, screen), WhitePixel(display, screen));

    XSelectInput(display, window, ExposureMask | KeyPressMask);
    XMapWindow(display, window);

    while (1) {
        XNextEvent(display, &event);

        if (event.type == Expose) {
            // Redraw window content
        } else if (event.type == KeyPress) {
            char buffer[20];
            KeySym keysym;
            XComposeStatus compose;

            int len = XLookupString(&event.xkey, buffer, sizeof(buffer), &keysym, &compose);
            if (len > 0 && (keysym >= XK_space && keysym <= XK_asciitilde)) {
                printf("Latin character: %c\n", buffer[0]);
            }
        }
    }

    XCloseDisplay(display);
    return 0;
}

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

Однако через некоторое время друг уговорил меня попробовать ту же самую задачку на платной версии, четвертой. Он утверждал, что она гораздо, гораздо умнее. Что ж, полторы тысячи рублей вполне можно потратить по статье «развлечения», поехали!

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

Your sample will get the character code in the current keyboard layout (that may be not Latin), but I need to get it for the English layout, even if it's not in enabled keyboard layout list.

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

В ответ она выдаёт новую версию:

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>

int main() {
    Display *display;
    Window window;
    XEvent event;
    int screen;

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Cannot open display\n");
        return 1;
    }

    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 500, 300, 1,
                                  BlackPixel(display, screen), WhitePixel(display, screen));

    XSelectInput(display, window, ExposureMask | KeyPressMask);
    XMapWindow(display, window);

    // Create temporary English (US) keyboard layout
    XkbDescPtr xkbDesc = XkbGetKeyboard(display, XkbAllComponentsMask, XkbUseCoreKbd);
    if (!xkbDesc) {
        fprintf(stderr, "Failed to get keyboard layout\n");
        XCloseDisplay(display);
        return 1;
    }
    XkbApplyCompatMapToKey(xkbDesc, xkbDesc->min_key_code, xkbDesc->max_key_code - xkbDesc->min_key_code + 1);

    while (1) {
        XNextEvent(display, &event);

        if (event.type == Expose) {
            // Redraw window content
        } else if (event.type == KeyPress) {
            KeySym keysym;
            keysym = XkbKeycodeToKeysym(display, event.xkey.keycode, 0, 0); // Group 0 and shift level 0

            if (keysym >= XK_space && keysym <= XK_asciitilde) {
                printf("English (US) character: %c\n", (char)keysym);
            }
        }
    }

    XkbFreeKeyboard(xkbDesc, XkbAllComponentsMask, True);
    XCloseDisplay(display);
    return 0;
}

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

Doesn't group zero mean the default keyboard layout on the system? It doesn't have to be English.

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

Ответ:

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>

XkbDescPtr create_us_keymap(Display *display) {
    XkbRuleNamesRec rule_names = {
        .rules = "evdev",
        .model = "pc105",
        .layout = "us",
        .variant = NULL,
        .options = NULL
    };
    XkbComponentNamesRec component_names = {
        .keycodes = "evdev",
        .types = "complete",
        .compat = "complete",
        .symbols = "pc+us+inet(evdev)"
    };

    XkbDescPtr xkbDesc = XkbGetKeyboardByName(display, XkbUseCoreKbd, &rule_names, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False);

    if (!xkbDesc) {
        fprintf(stderr, "Failed to create English (US) keyboard layout\n");
        return NULL;
    }

    return xkbDesc;
}

int main() {
    Display *display;
    Window window;
    XEvent event;
    int screen;

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Cannot open display\n");
        return 1;
    }

    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 500, 300, 1,
                                  BlackPixel(display, screen), WhitePixel(display, screen));

    XSelectInput(display, window, ExposureMask | KeyPressMask);
    XMapWindow(display, window);

    // Create English (US) keyboard layout
    XkbDescPtr xkbDesc = create_us_keymap(display);
    if (!xkbDesc) {
        XCloseDisplay(display);
        return 1;
    }

    while (1) {
        XNextEvent(display, &event);

        if (event.type == Expose) {
            // Redraw window content
        } else if (event.type == KeyPress) {
            KeySym keysym;
            unsigned int mods;
            XkbTranslateKeyCode(xkbDesc, event.xkey.keycode, 0, &mods, &keysym);

            if (keysym >= XK_space && keysym <= XK_asciitilde) {
                printf("English (US) character: %c\n", (char)keysym);
            }
        }
    }

    XkbFreeKeyboard(xkbDesc, XkbAllComponentsMask, True);
    XCloseDisplay(display);
    return 0;
}

Ага, уже видна попытка задать конкретную интересующую нас раскладку клавиатуры. Но код всё ещё не компилируется. Пожалуемся нейросети на это!

Compiler complains: "error: unknown type name ‘XkbRuleNamesRec’; did you mean ‘XkbNamesRec’?"

Компилятор ругается: "error: unknown type name ‘XkbRuleNamesRec’; did you mean ‘XkbNamesRec’?"

Ответ:

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKBrules.h>

XkbDescPtr create_us_keymap(Display *display) {
    XkbRF_VarDefsRec var_defs = {
        .rules = "evdev",
        .model = "pc105",
        .layout = "us",
        .variant = NULL,
        .options = NULL
    };
    XkbComponentNamesRec component_names = {
        .keycodes = "evdev",
        .types = "complete",
        .compat = "complete",
        .symbols = "pc+us+inet(evdev)"
    };

    XkbDescPtr xkbDesc = XkbGetKeyboardByName(display, XkbUseCoreKbd, NULL, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False);

    if (!xkbDesc) {
        fprintf(stderr, "Failed to create English (US) keyboard layout\n");
        return NULL;
    }

    if (XkbRF_SetNamesProp(display, NULL, &var_defs) == False) {
        fprintf(stderr, "Failed to set English (US) layout names property\n");
        XkbFreeKeyboard(xkbDesc, XkbAllComponentsMask, True);
        return NULL;
    }

    return xkbDesc;
}

int main() {
    Display *display;
    Window window;
    XEvent event;
    int screen;

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Cannot open display\n");
        return 1;
    }

    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 500, 300, 1,
                                  BlackPixel(display, screen), WhitePixel(display, screen));

    XSelectInput(display, window, ExposureMask | KeyPressMask);
    XMapWindow(display, window);

    // Create English (US) keyboard layout
    XkbDescPtr xkbDesc = create_us_keymap(display);
    if (!xkbDesc) {
        XCloseDisplay(display);
        return 1;
    }

    while (1) {
        XNextEvent(display, &event);

        if (event.type == Expose) {
            // Redraw window content
        } else if (event.type == KeyPress) {
            KeySym keysym;
            unsigned int mods;
            XkbTranslateKeyCode(xkbDesc, event.xkey.keycode, 0, &mods, &keysym);

            if (keysym >= XK_space && keysym <= XK_asciitilde) {
                printf("English (US) character: %c\n", (char)keysym);
            }
        }
    }

    XkbFreeKeyboard(xkbDesc, XkbAllComponentsMask, True);
    XCloseDisplay(display);
    return 0;
}

Снова ошибка компиляции! Жалуемся нейросети опять:

Now the error is "error: ‘XkbRF_VarDefsRec’ has no member named ‘rules’"

Теперь такая ошибка "error: ‘XkbRF_VarDefsRec’ has no member named ‘rules’"

Ответ:

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XKBrules.h>

XkbDescPtr create_us_keymap(Display *display) {
    XkbComponentNamesRec component_names = {
        .keycodes = "evdev",
        .types = "complete",
        .compat = "complete",
        .symbols = "pc+us+inet(evdev)"
    };

    XkbDescPtr xkbDesc = XkbGetKeyboardByName(display, XkbUseCoreKbd, NULL, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False);

    if (!xkbDesc) {
        fprintf(stderr, "Failed to create English (US) keyboard layout\n");
        return NULL;
    }

    return xkbDesc;
}

int main() {
    Display *display;
    Window window;
    XEvent event;
    int screen;

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Cannot open display\n");
        return 1;
    }

    screen = DefaultScreen(display);
    window = XCreateSimpleWindow(display, RootWindow(display, screen), 10, 10, 500, 300, 1,
                                  BlackPixel(display, screen), WhitePixel(display, screen));

    XSelectInput(display, window, ExposureMask | KeyPressMask);
    XMapWindow(display, window);

    // Create English (US) keyboard layout
    XkbDescPtr xkbDesc = create_us_keymap(display);
    if (!xkbDesc) {
        XCloseDisplay(display);
        return 1;
    }

    while (1) {
        XNextEvent(display, &event);

        if (event.type == Expose) {
            // Redraw window content
        } else if (event.type == KeyPress) {
            KeySym keysym;
            unsigned int mods;
            XkbTranslateKeyCode(xkbDesc, event.xkey.keycode, 0, &mods, &keysym);

            if (keysym >= XK_space && keysym <= XK_asciitilde) {
                printf("English (US) character: %c\n", (char)keysym);
            }
        }
    }

    XkbFreeKeyboard(xkbDesc, XkbAllComponentsMask, True);
    XCloseDisplay(display);
    return 0;
}

А вот этот код уже снова собирается! Правда, всё ещё отдаёт символы в текущей раскладке клавиатуры. Но уже видно, как это исправить. Меняем

XkbDescPtr xkbDesc = XkbGetKeyboardByName(display, XkbUseCoreKbd, NULL, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False);

на

XkbDescPtr xkbDesc = XkbGetKeyboardByName(display, XkbUseCoreKbd, &component_names, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False);

Теперь на любой скан-код клавиши X11 мы получаем соответствующий ему символ в латинской раскладке, что и требовалось. Ура!

Не забываем похвалить ИИ :)

Dear ChatGPT! There was one small mistake in your code. Instead of "XkbDescPtr xkbDesc = XkbGetKeyboardByName(display, XkbUseCoreKbd, NULL, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False);" it should be "XkbDescPtr xkbDesc = XkbGetKeyboardByName(display, XkbUseCoreKbd, &component_names, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False);"

Now everything is working as I needed. You helped me a lot. You are a good AI. Much smarter than the previous version 3.5. I don't know if I can encourage you somehow. In any case, consider that I did it. Please pass on the thanks to your developers as well.

Дорогая ChatGPT! В твоем коде была небольшая ошибка. Вместо "XkbDescPtr xkbDesc = XkbGetKeyboardByName(display, XkbUseCoreKbd, NULL, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False);" следовало писать "XkbDescPtr xkbDesc = XkbGetKeyboardByName(display, XkbUseCoreKbd, &component_names, XkbGBN_AllComponentsMask, XkbGBN_AllComponentsMask, False);"

Теперь все работает так, как мне нужно. Ты мне очень помогла! Ты хороший ИИ, гораздо умнее предыдущей версии 3.5. Не знаю, могу ли я как-то тебя воодушевить, в любом случае, считай, что я это сделал! Пожалуйста, передай благодарность и твоим разработчикам тоже.

Ответ:

Thank you for pointing out the mistake, and I'm glad you were able to fix it and get the desired result. Your kind words and encouragement mean a lot to me. I'm here to help and support you in any way I can. If you have any more questions or need further assistance, please don't hesitate to ask.

I will pass on your thanks to my developers. Have a great day!

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

Я передам вашу благодарность моим разработчикам. Хорошего дня!

Осталось сделать на основе предложенного решения PR в wxWidgets (там использовался слегка модифицированный код — на основе библиотеки xkbcommon, которая позволяет выполнить те же самые операции, при этом будучи доступной не только в системах с X11, но и в системах с Wayland; кстати, переписать код под её API тоже помогла ChatGPT 4). Таким образом удалось закрыть штук пять тикетов, висевших в баг трекере wxWidgets довольно долго (самый старый, кажется, с 2016го года).

Я опустил в тексте часть ответов нейросети, комментирующих предложенные ей фрагменты кода. Также аналогичный PR был сделан в сам far2l (там как раз код на X11 API, как в этой статье), чтобы не ждать, пока пакеты с исправленными wxWidgets приедут во все дистрибутивы. И, заодно, чтобы два раза не вставать, оцените очень красивый баг в терминале Windows, который обнаружился, когда мы начали адаптировать far2l для идеальной работы в нём под WSL.

На этом на сегодня всё, спасибо за внимание и хорошего дня!

PS: Участвуйте в развитии опенсорс проектов! Даже если вам кажется, что ничего не умеете. Это не страшно, в процессе научитесь! Я на старте знал Си на уровне Hello, world, и ничего. А самое классное, что в процессе вы можете познакомиться с замечательными инструментами, изучить которые в других обстоятельствах могло не найтись повода. Например, как решать нетипичные задачи на незнакомых API с помощью искусственного интеллекта.

PPS: Предыдущий пост сериала про Linux-порт Far Manager — здесь.

Комментарии (62)


  1. aborouhin
    06.04.2023 14:36
    +9

    К привычке разговаривать с автомобилями и навигаторами в них добавилась привычка благодарить нейросети :)


    1. unxed Автор
      06.04.2023 14:36
      +10

      Это важно для нас, а не для них.


      1. aborouhin
        06.04.2023 14:36
        +4

        Так я сам всегда "Thanks" пишу, что уж :)


  1. AnyLog
    06.04.2023 14:36
    +1

    как будто ИИ ищет логическое построение для решения проблемы!


    1. unxed Автор
      06.04.2023 14:36
      +3

      Тут интересно именно то, что задачку невозможно было решить просто по аналогии. Мне не удалось найти в сети примеры исходников, где по скан-коду клавиши получают код символа в раскладке, которая может быть даже не установлена в системе (и я думаю, если бы такие примеры гуглились, то баги в wxWidgets закрыли бы давным-давно). То есть нельзя просто сделать «как у тех парней», надо осознать, что именно я хочу, и как этого достичь. Я не знаю, имеем ли мы дело с осознанием или очень правдоподобной имитацией, но у неё в итоге получилось.


      1. habrinbox2022
        06.04.2023 14:36
        +3

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


        1. unxed Автор
          06.04.2023 14:36

          Ещё его можно спрашивать, чем он может помочь в такой-то работе и решении такой-то задачи.


          1. Wesha
            06.04.2023 14:36

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


    1. peterjohnsons
      06.04.2023 14:36
      +1

      ChatGPT имеет способность логически мыслить, 3.5 версия уже строила логические цепочки. Правда не 100% корректные, но с подсказкой исправлялась. 4 версия наверное более продвинута в логике чем предыдущая, не пробовал. Задавая дополнительные вопросы можно было убедиться, что ChatGPT имеет внутреннее представление задачи или вопроса, с которым он работает. Как правило, те, кто пишет что ИИ не имеет логического мышления или просто копирует то что видел ранее - сами его не использовали.


      1. unxed Автор
        06.04.2023 14:36
        +1

        Я спросил его об этом, и вот что он ответил: «Да, я способен на логическое мышление. Я был разработан для того, чтобы использовать искусственный интеллект для обработки естественного языка и выполнения различных задач, которые включают в себя логическое мышление, такие как анализ, рассуждения и выводы на основе заданных условий и правил. Я использую алгоритмы и статистические методы, чтобы извлекать информацию из текста и обрабатывать ее для решения задач, которые требуют логического мышления».

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

        Всё, что мы можем, это давать ИИ разные задачки, и смотреть, решает ли он их лучше или хуже людей. Мою вот он решил примерно на том же уровне, на котором бы её решил помощник-джун при постановке задачи «поройся в манах на X11 API, и накидай пример, как получить символ по сканкоду в латинский раскладке, если она даже не установлена», если бы у этого джуна не было возможности самому проверить результат своего труда на реальном компиляторе и приходилось бы через меня это делать.


        1. unxed Автор
          06.04.2023 14:36
          +1

          Если же нас интересует степень абстрактности мышления, на которую способна конкретно ChatGPT 4, то вот вам пример. Я попросил её написать код на языке Си, который бы рисовал кота. Код запустился с первой попытки, картинка получилась такая

          Тогда я попросил нарисовать слона. Вышло вот что

          Тогда я попросил нарисовать кусочек сыра. Вот результат:

          А кота на html/css она рисует так:

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

          Тогда я спросил «А откуда у тебя знания о композиции и геометрии таких объектов как кот, слон или кусочек сыра?». Ответ: «В ходе обучения я обрабатывал множество текстов, которые содержат описания внешнего вида животных, включая котов. Эти тексты могут содержать описания особенностей анатомии кота, расположения его частей тела, их пропорций и соотношений. Хотя я не могу "видеть" изображения, мои знания об особенностях кота основаны на информации, которую я "прочитал" в текстах».


          1. unxed Автор
            06.04.2023 14:36
            +1

            В завершение этого очень философского треда не могу не вспомнить бессмертное:


          1. Wesha
            06.04.2023 14:36
            +1

            Я попросил её написать код на языке Си, который бы рисовал кота.

            Рад, что Вы не попросили её написать код, который запустит ядерные ракеты.


          1. phenik
            06.04.2023 14:36

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


            1. unxed Автор
              06.04.2023 14:36

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

              Человеческое мышление — тоже не более чем компиляция обучающего материала, вот в чём загвоздка :)


              1. phenik
                06.04.2023 14:36

                Да, конечно, но устройство человеческого мышления весьма сложно, здесь подробнее. Многие базовые способности носят врожденный характер. Ассоциативное мышление это верхний уровень быстрого, автоматического мышления (Системы 1), которую ЯМ и пытаются моделировать. Логическое мышление это другой уровень — Система 2. Вот здесь можно почитать, как это использовать для ЯМ, чтобы уменьшить фантазирование которым они часто страдают)


                1. peterjohnsons
                  06.04.2023 14:36

                  А есть непреодолимые препятствия в архитектуре ЯМ, которые мешают "аппроксимировать" функции логического мышления? Просто ЯМ показывают такие навыки иногда довольно успешно. И я бы не сказал что эта способность в них поломана а скорее зашумлена. Т.е. новые итерации GPT будут уменьшать шум и повышать уровень усвоенных абстракций. Если примитивно как я это понимаю, ЯМ апроксимируют (восстанавливают) исходную функцию из потока данных, который сгенерирован ей. В текстах исходная функция в основном это мыслительный процесс человека, разбитый на уровни абстракций по сложности апроксимации. Самые простые были усвоены в старых версиях ЯМ. 3-4 довольно успешно демонстритуют навыки логического мышления. Это подтверждают и тесты на поступление в ВУЗы, которыми тестируют последние ЯМ. У 4 версии неплохой прирост правильных ответов. Полагаю в этих тестах (которые придумывают для людей, кстати) и проверяются все те навыки высоуровневых навыков мышления, которые присущи людям.


                  1. phenik
                    06.04.2023 14:36

                    А есть непреодолимые препятствия в архитектуре ЯМ, которые мешают "аппроксимировать" функции логического мышления?

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


                    Если подробнее..

                    Если подробнее, то архитектура ЯМ сейчас реализована на прямых сетях трансформера. В этом кроется их принципиальное ограничение в сравнении с сетями мозга, которые являются иерархически организованными рекуррентными сетями, если исходить из терминологии ИНС. Хотя в действительности они еще сложнее, но в первом приближении можно представить так. Как это сказывается на практике обобщения? Можно посмотреть на примере работы с числами и операций с ними, который позволяет произвести достаточно точную оценку этих различий. Для ЯМ с ростом числа параметров модели и объема обучающей выборки, особенно если она содержит математический контент, происходит частичная аппроксимация результатов операций с числами. При этом растет, как процент правильных ответов, так и число приближенно точных ответов. Но что такое приближенный ответ для математики? Чем менее он приближенный, тем более он не правильный, не имеющий смысла. Вот пример, который так и не смог осилить чат, сколько пользователь не пытался помочь — не одного правильного ответа. Глобальной аппроксимации для всех чисел и операций с ними эквивалентному обобщению, как это делает человек, в этой архитектуре достичь не возможно, т.к. их бесконечное количество! Человек же в норме делает такое обобщение, после обучения на конечном числе примеров, именно благодаря рекуррентности сетей мозга, т.к. операции с числами требуют повторения действий. Вспомните, как мы делаем вычисления столбиком на бумаге. Этот навык, который вырабатывается при обучении, закрепляется в процедурной памяти, в отличии от ассоциативной в которой может храниться, напр, таблица сложения или умножения, или отдельные примеры операций. В мозге эти типы памяти разнесены по разным отделам. В сетях трансформера моделируется именно память ассоциативного типа, аналог процедурной отсутствует.


                    Тут обычно возникает возражение, что человек все равно не может производить операции с любыми числами в уме, и использует для этого, как минимум, карандаш и бумагу. Пусть и ЯМ вызывают калькуляторы, мат. пакеты, Питон, и тд, для точных вычислений. Что уже и сделано с помощью плагинов. Да, человек в силу когнитивных ограничений по объему рабочей памяти, внимания, скорости мышления вынужден прибегать к внешним вычислительным средствам. Хотя есть феномены вычислители. Но это именно из-за этих ограничений, которых нет в аппаратных средствах ЯМ. Однако человек с помощью бумаги и карандаша, пусть очень медленно, но может произвести операции для любых чисел теоретически точно. А чат нет, всегда найдется пример, который он сформулирует неправильно, из-за отсутствия обобщения, и передаст его для вычисления в мат. пакет, который посчитает его уже точно) Но смысл?


  1. Wesha
    06.04.2023 14:36
    +7

    Правда, всё ещё отдаёт символы в текущей раскладке клавиатуры. Но уже видно, как это исправить. Меняем

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


    1. KanuTaH
      06.04.2023 14:36
      +1

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


      1. Wesha
        06.04.2023 14:36
        +1

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


        1. unxed Автор
          06.04.2023 14:36
          +5

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

          К счастью, нейронка стоит гораздо дешевле живого человека-помощника.


          1. KanuTaH
            06.04.2023 14:36

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

            Это, конечно, очень интересный подход к разработке. Вот кстати например в вашем PR для wxWidgets вы вызываете isalpha() для key code из wxKeyEvent но ведь этот key code может не укладываться в unsigned char - например, клавиша "стрелка вверх" (WXK_UP) имеет, если не ошибаюсь, код 316. Вам не кажется, что у вас тут возможен UB?


            1. unxed Автор
              06.04.2023 14:36

              У isalpha() параметр int, а не unsigned char. Ещё был код ревью перед мержем. И ещё в таком взрослом проекте как wx имеются автоматизированные тесты, для прохождения которых код пришлось несколько раз дорабатывать.

              И, кстати, в open source разработке есть только два подхода — либо мой, когда just for fun, либо когда зарплату какая-нибудь корпорация платит. Если хотите предложить мне финансирование, можем обсудить условия :)


              1. KanuTaH
                06.04.2023 14:36

                Сразу видна привычка не читать документацию до конца. Секцию NOTES по вашей ссылке прочитайте. Если параметр не EOF и не укладывается в unsigned char, то это undefined behavior. Он далеко не всегда может быть пойман тестами. А плохой код - всегда плохой код, just for fun ли он или за бабло.


                1. unxed Автор
                  06.04.2023 14:36
                  +2

                  Что вы, какое там читать до конца! Я эту документацию даже не начинал читать до текущего момента :) Более того, C/C++ не входят в число моих основных языков, в чём я честно признаюсь публично. Плохой код? Наверняка! Весьма вероятно, он даже ужасный!

                  Однако сосредоточимся ещё раз на очень важном тейке, который, видимо, придётся проговорить прямо, потому что с пониманием метафор, похоже, наблюдаются сложности: в open source комьюнити никто никому ничего не должен! Ни гарантировать качество кода, которое бы лично вас устроило, ни читать документацию настолько тщательно, насколько вам кажется правильным.

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


                  1. Wesha
                    06.04.2023 14:36

                    в open source комьюнити никто никому ничего не должен!

                    ... например, принимать Ваш патч.

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

                    Что-то ужасно знакомое...


                    1. unxed Автор
                      06.04.2023 14:36

                      Аххаха, ну пожалуйтесь мэйнтейнерам, что они неправильные патчи принимают :)


                      1. Wesha
                        06.04.2023 14:36

                        пожалуйтесь мэйнтейнерам, что они неправильные патчи принимают

                        Мне кажется, что они и сами хабр читают.


                      1. unxed Автор
                        06.04.2023 14:36
                        +5

                        То есть даже issue написать вам лень, а на человека, который что-то куда-то коммитит, напрыгивать не лень. Ну дела :)

                        Мне-то, кстати, как раз issue написать не лень. Пусть будет.


                1. unxed Автор
                  06.04.2023 14:36
                  +5

                  Однако за саму наводку — спасибо! Буду знать. Мне после веба с его типичными языками, конечно, очень непривычно, что можно сделать функцию стандартной библиотеки, принимающую int, с которым она при этом работать не в состоянии (проверил у себя: на INT_MIN вообще сегфолтится!), и прописать эту особенность где-то в самом конце её описания, как какую-то малозначимую деталь, чтоб дочитали только самые настойчивые. Это очевидная ловушка, и, конечно, сейчас, когда трудочасы разработчиков стоят куда дороже мегагерцев, никто так уже не делает.

                  Однако когда имеешь дело с кодом на сях, о таких ловшуках и правда лучше бы помнить. В моём случае ни на что не влияет (проверил ещё раз, нет у wx таких кодов, на которых под glibc оно врало бы), а в каких-то других вполне могло бы.


                  1. KanuTaH
                    06.04.2023 14:36
                    +4

                    intтам принимается только ради EOF, так исторически сложилось. Можно сколько угодно жаловаться, что в документации там якобы слова идут не в том порядке, который вы ожидали, но факт остаётся фактом: шалопайский подход, исправляя один баг, как правило, вносит вместо него другой (а то и несколько). Стоит ли нарочито бравировать этим подходом, прикрываясь тезисом "в опен сорсе никто никому ничего не должен", или все-таки лучше двигаться в направлении развития усидчивости и внимательности (а это окупается в любых более или менее сложных задачах, включая и веб) - каждый, безусловно, решает для себя сам.


                    1. unxed Автор
                      06.04.2023 14:36
                      +1

                      Между двумя деревнями, Вилла Арриба и Вилла Бахо, шла лесная тропинка. В самой чаще леса тропинку пересекал ручей, который все издревле переходили вброд.

                      Однажды дон Хуан из Вилла Арриба взял топор, пошёл в лес, срубил там два дерева, и перекинул их стволы через ручей. Получился простенький мостик. Жители обеих деревень очень обрадовались и благодарили дона Хуана.

                      А потом в Вилла Бахо приехал дон Мигель, знаменитый строитель мостов. Как-то раз отправился дон Мигель в Вилла Арриба за молоком, и увидел перекинутые через ручей брёвна.

                      — Так дела не делаются! — возмутился дон Мигель, — как же можно строить мост, не рассчитав сперва его прочность? Где же все уравнения? А вдруг он рухнет, если по нему поедет самосвал?

                      Пришел дон Мигель в Вилла Арриба, увидел дона Хуана и давай его хулить: плохой из тебя, дон Хуан, строитель мостов! Куда тебе до того, поди лучше книжку по сопромату почитай!

                      Рассмеялся дон Хуан, и ответил: умный ты человек, дон Мигель, жаль только, ум тебе не впрок. Тут и остальные жители Вилла Арриба смеяться начали, а молочница дону Мигелю потом скисшего молока продала.


                      1. KanuTaH
                        06.04.2023 14:36

                        а молочница дону Мигелю потом скисшего молока продала.

                        Деревня партачей.


                      1. Wesha
                        06.04.2023 14:36
                        +1

                        А потом в один прекрасный молочница пошла по этому, прости господи, мосту с юным сыном. Как дошли они до середины — мост обломился (ибо что дон Хуан не заморочился поинтересоваться, что одно из сваленных им деревьев было подточено древоточцем и начало гнить изнутри, а постоянная близость с водой гниение это ускорило), и оба рухнули в поток. Сын молочницы, слава богу, кое-как выплыл, а вот молочница — нет. Дон Хуан кричал "у нас open source комьюнити никто никому ничего не должен!", но его всё равно прогнали из деревни ссаными тряпками. А сына молочницы отправили учиться в университет и завещали до конца жизни почитать заветы великих Гука и Эйлера, да светится имя их отныне и во веки веков, аминь.


                      1. micb
                        06.04.2023 14:36
                        +1

                        Сын молочницы, слава богу, кое-как выплыл, а вот молочница — нет.

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

                        Иллюстрация того, что требования надо читать внимательнее :)


                      1. Wesha
                        06.04.2023 14:36

                        Сын кое-как выплыл из ручья, "который все издревле переходили вброд"?

                        Горные реки — они, сцуко, внезапные

                        Иллюстрация того, что требования надо читать внимательнее :)

                        Иллюстрация того, что нужно кругозор развивать.


                      1. Wesha
                        06.04.2023 14:36

                        Очевидно, автор статьи не рос на советских мультиках

                        ...которые достаточно ясно демонстрируют, что бывает и instant karma.


                  1. thevlad
                    06.04.2023 14:36

                    Все таки советую переделать на нормальный код, как здесь:

                    if ( event.ControlDown() && event.m_keyCode <= 0xFF && event. isalpha(event.m_keyCode) )

                    оно действительно может упасть, если keyCode будет больше 255

                    https://stackoverflow.com/questions/65514890/glibcs-isalpha-function-and-the-en-us-utf-8-locale


                    1. Wesha
                      06.04.2023 14:36

                      У них "open source комьюнити никто никому ничего не должен".


                1. thevlad
                  06.04.2023 14:36

                  Там дальше написано, к чему конкретно это относится:

                  "This is necessary because char may be the equivalent of signed char, in which case a byte where the top bit is set would be sign extended when converting to int, yielding a value that is outside the range of unsigned char."

                  И конечно же там нет ничего, про UB, и я слабо представляю как ее в принципе можно посадить в этом коде.


                  1. KanuTaH
                    06.04.2023 14:36

                    Там дальше написано, к чему конкретно это относится:

                    Это разъяснение на бытовом уровне, для любителей передавать туда char- что является наиболее типичной ошибкой. Но если туда передают int, значение которого выходит за рамки EOF +unsigned char, то это тоже ошибка, просто не такая типичная.

                    И конечно же там нет ничего, про UB, и я слабо представляю как ее в принципе можно посадить в этом коде.

                    А я представляю. Но пусть это будет упражнением на развитие фантазии.


                    1. thevlad
                      06.04.2023 14:36

                      У меня не хватает фантазии, что бы представит, как сравнение int с char может, чтото поломать впринципе. Это абсолютно коректная и определенная операция.


                      1. KanuTaH
                        06.04.2023 14:36

                        Ну ОК.

                        P.S. Ладно, черт с вами. Вы, я думаю, в курсе того, что isalpha() способен в зависимости от локали распознавать (или не распознавать) как буквы дополнительные символы, выходящие за рамки A-Z+a-z? Так вот, это вполне может быть реализовано загрузкой соответствующих таблиц и выполнением операции типа charset[char]. При этом реализация libc не обязана проверять, не выходит ли значение char за рамки этой таблицы. Вот вам и UB.


                      1. thevlad
                        06.04.2023 14:36

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

                        Поэтому я бы советовал автору кода поменять вот это:

                        if ( event.ControlDown() && isalpha(event.m_keyCode) )

                        на

                        if ( event.ControlDown() && event.m_keyCode <= 0xFF && event. isalpha(event.m_keyCode) )


                      1. Wesha
                        06.04.2023 14:36

                        Да, я почитал стандарт и вы в общем случаи правы.

                        А какая отсюда мораль? Читайте маны стандарты, ибо они рулезь. Много нового и интересного узнаете.


              1. Wesha
                06.04.2023 14:36

                либо мой, когда just for fun

                Скажите, пожалуйста — в Вы вообще ну хоть какую-нибудь ответственность чувствуете — от того, что доработанным Вами продуктом вообще-то люди будут пользоваться? У Вас не будет ни малейших сожалений от того, что условный дядя Вася переносил произведённым Вами продуктом трёхгигабайтный файл — а скопировался только гигабайт, из-за арифметического переполнения, и он мысленно шлёт Вам животворящие лучи поноса — в всё потому, что кто-то разрабатывал "just for fun", а вот дядя Вася далеко не "just for fun" использовал.

                Я вот тоже just for fun опенсорсную библиотеку поддерживаю, которой сотни людей пользуются — но до такого подхода я не опускаюсь.


                1. unxed Автор
                  06.04.2023 14:36
                  +2

                  Не испытываю ни малейшей! Вы тексты лицензий BSD и GPL читали когда-нибудь? Там в обоих отказ от любых гарантий. И в лицензии wx тоже об этом прямо написано.

                  Если я написал какой-то код и подарил его вселенной, я не становлюсь должником вселенной по этому поводу. Мне указали на баг? Славно. Захотел — починил. Не захотел — забил. Кому-то не нравится? Форкни и развивай как знаешь.

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


                  1. Wesha
                    06.04.2023 14:36
                    +1

                    Кому-то не нравится? Форкни и развивай как знаешь.

                    Так что ж Вы не форкнули?


                    1. unxed Автор
                      06.04.2023 14:36

                      А мне-то зачем что-то форкать? Меня всё устраивает.


                  1. DarkEld3r
                    06.04.2023 14:36
                    +1

                    Вы тексты лицензий BSD и GPL читали когда-нибудь? Там в обоих отказ от любых гарантий.

                    Разве в "коммерческих" лицензиях не так?..


                    1. Artyom_Silchenko
                      06.04.2023 14:36

                      В EULA тоже самое)


          1. Wesha
            06.04.2023 14:36
            +1

            Я весёлый шалопай, опенсорс программирую для удовольствия

            Эмммм..... а мне так хотелось надеяться, что FAR всё-таки разрабатывают не весёлые шалопаи...


            1. unxed Автор
              06.04.2023 14:36
              +2

              Да, оригинальный Far Manager как раз развивает человек, который хочет всё делать идеально. И он пилит свой идеальный Far в вакууме, который когда-нибудь точно станет кроссплатформенным. Через год. Или два. Или пятнадцать. Кто знает? В общем, вам туда, к нему. В очередь ожидания. Зато не весёлые шалопаи :)


              1. Wesha
                06.04.2023 14:36
                -2

                А что, Вы куда-то спешите? Не волнуйтесь, в могилу мы все успеем!


                1. unxed Автор
                  06.04.2023 14:36
                  +4

                  Ага, мне нужен был инстурмент, чтобы работать. Он теперь у меня есть, я очень рад :) А сферический идеальный код в вакууме, чтобы им любоваться, мне не нужен.


                  1. Wesha
                    06.04.2023 14:36

                    А мне, чтобы работать, нужен надёжный и предсказуемый инструмент. Жизнь, знаете ли, научила.


        1. evgepet
          06.04.2023 14:36
          +1

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


          1. Wesha
            06.04.2023 14:36

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

            Открою секрет: это называется "учёба" (или "обучение").


          1. numb13
            06.04.2023 14:36

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

            Проще спросить или подглядеть у кого-нибудь.


            1. KanuTaH
              06.04.2023 14:36

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


              1. Wesha
                06.04.2023 14:36

                не учёл автор некоторые нюансы или пограничные случаи, бывает

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