Обязательный именованный параметр с проверкой при компиляции
От: vsb Казахстан  
Дата: 19.06.23 16:27
Оценка:
У меня есть метод, которому я хочу передать несколько параметров. Типы у параметров могут совпадать. Я хочу это сделать таким образом, чтобы:

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

2. При вызове нельзя незаметно перепутать два параметра с одинаковым типом.

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

function print({x, y}: {x: number, y: number}) {
    console.log(x, y);
}

const x1 = 1, y1 = 2;
print({x: x1, y: y1});


В Java такой фичи нет.

Если мы делаем просто вызов метода, то нарушается пункт 2. К примеру я могу написать test(y1, x1) и из этого будет совершенно не очевидно, что я написал ерунду.

Можно сделать объект с полями (parameter object). Но тут нарушается пункт 1. Если мы добавляем новое поле, то компилятор не проверит, что мы его инициализировали.

В голову приходит два варианта:

Вариант 1: избавиться от условия того, что типы у параметров могут совпадать. Т.е. завести типы-обёртки для каждого аргумента.

    static class X {
        final int x;

        X(int x) {
            this.x = x;
        }

        static X x(int x) {
            return new X(x);
        }
    }

    static class Y {
        final int y;

        Y(int y) {
            this.y = y;
        }

        static Y y(int y) {
            return new Y(y);
        }
    }
    
    static void print(X x, Y y) {
        System.out.println(x.x + " " + y.y);
    }

    public static void main(String[] args) {
        int x1 = 1, y1 = 2;
        print(x(x1), y(y1));
    }


Вариант 2: сделать хитрый билдер.

    static class PrintArgs0 {
        static PrintArgs0 printArgs() {
            return new PrintArgs0();
        }
        
        PrintArgs1 x(int x) {
            return new PrintArgs1(x);
        }
    }
    
    static class PrintArgs1 {
        private final int x;

        PrintArgs1(int x) {
            this.x = x;
        }
        
        PrintArgs y(int y){
            return new PrintArgs(x, y);
        }
    }
    
    static class PrintArgs {

        private final int x;
        private final int y;

        public PrintArgs(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
    
    static void print(PrintArgs args) {
        System.out.println(args.x + " " + args.y);
    }

    public static void main(String[] args) {
        int x1 = 1, y1 = 2;
        print(printArgs().x(x1).y(y1));
    }


Оба варианта слегка безумны. Даже с кодогенератором плодить класс на каждый параметр это чересчур.

Может я не вижу чего-то очевидного?
Отредактировано 19.06.2023 16:28 vsb . Предыдущая версия . Еще …
Отредактировано 19.06.2023 16:27 vsb . Предыдущая версия .
Re: Обязательный именованный параметр с проверкой при компиляции
От: · Великобритания  
Дата: 19.06.23 20:46
Оценка: 7 (1)
Здравствуйте, vsb, Вы писали:

vsb> Может я не вижу чего-то очевидного?


Завернуть в параметр-интерфейс:
interface Param{
    int x();
    int y();
}
static void print(Param p)
{
    System.out.println(p.x() + " " + p.y());
}
public static void main(String[] args) {
    print(new Param() {
        @Override public int x() {
            return 1;
        }
        @Override public int y() {
            return 2;
        }
    });
}

Такое более привычно с т.з. дизайна, хоть и многословно.
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[2]: Обязательный именованный параметр с проверкой при компиляции
От: flаt  
Дата: 21.06.23 11:06
Оценка:
Здравствуйте, ·, Вы писали:

·>Здравствуйте, vsb, Вы писали:



·>Завернуть в параметр-интерфейс:

·
·>Такое более привычно с т.з. дизайна, хоть и многословно.

При отсутствии именованных параметров, обычно используют билдеры (особенно, в Java) и прокси-типы (newtypes). Неужели озвученный параметр-интерфейс — привычное дело в мире сабжа?
Re[3]: Обязательный именованный параметр с проверкой при ком
От: · Великобритания  
Дата: 21.06.23 11:17
Оценка:
Здравствуйте, flаt, Вы писали:

F>·>Такое более привычно с т.з. дизайна, хоть и многословно.

F>При отсутствии именованных параметров, обычно
Он же написал почему это не работает для его требований:

F>используют билдеры (особенно, в Java)

не будет compile-time ошибки при добавлении нового парама.

F>и прокси-типы (newtypes).

Чем поможет-то? func(1, 2) => func(new NewType(1, 2))?! Имена-то где? Ну или "Вариант 1" выше, который тоже не сахар.

F>Неужели озвученный параметр-интерфейс — привычное дело в мире сабжа?

Если нужно потребовать от клиентского кода предоставлять сразу пачку именованных символов — то да.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 21.06.2023 11:57 · . Предыдущая версия . Еще …
Отредактировано 21.06.2023 11:56 · . Предыдущая версия .
Re[4]: Обязательный именованный параметр с проверкой при ком
От: sergii.p  
Дата: 26.07.23 07:10
Оценка:
Здравствуйте, ·, Вы писали:

F>>и прокси-типы (newtypes).

·>Чем поможет-то? func(1, 2) => func(new NewType(1, 2))?! Имена-то где? Ну или "Вариант 1" выше, который тоже не сахар.

никто обычно функции c константами не вызывает. Существует одно место в коде, где значение связывается с типом и дальше по call stack невозможно ошибиться
NewType1 val1 = new NewType1(1);
NewType2 val2 = new NewType2(2);
...
func(val1, val2);
Re[5]: Обязательный именованный параметр с проверкой при ком
От: · Великобритания  
Дата: 26.07.23 08:46
Оценка:
Здравствуйте, sergii.p, Вы писали:

F>>>и прокси-типы (newtypes).

SP>·>Чем поможет-то? func(1, 2) => func(new NewType(1, 2))?! Имена-то где? Ну или "Вариант 1" выше, который тоже не сахар.

SP>никто обычно функции c константами не вызывает.

Пара булевых флагов и вот уже путаница. Т.е. достаточно иметь более одного параметра одного типа.

SP>Существует одно место в коде, где значение связывается с типом

Если ты предлагаешь искусственно объявлять уникальный тип для каждого параметра, то это и есть упомянутые выше прокси типы, которые отстой.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[6]: Обязательный именованный параметр с проверкой при ком
От: sergii.p  
Дата: 26.07.23 10:02
Оценка:
Здравствуйте, ·, Вы писали:

·>Пара булевых флагов и вот уже путаница. Т.е. достаточно иметь более одного параметра одного типа.


булевы параметры — это триггер на code review обычно. Их можно заменить enum. И внезапно повысили чатаемость кода и усилили систему типов.

вместо:

connect(true, false, true);


появится что-то типа:

connect(SSL.use, KeepAlive.No, UseExistConnection.Yes);


Практически именованные типы.

·>прокси типы, которые отстой.


конструктивная аргументация...
Re[7]: Обязательный именованный параметр с проверкой при ком
От: · Великобритания  
Дата: 26.07.23 10:37
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>·>Пара булевых флагов и вот уже путаница. Т.е. достаточно иметь более одного параметра одного типа.

SP>булевы параметры — это триггер на code review обычно. Их можно заменить enum. И внезапно повысили чатаемость кода и усилили систему типов.
Да это же пример. Вот ещё код, с правильными типами, а толку?
Receipt transfer(Account from, Account to, Money amount, Money fee);

...
...

Account to = getAccountSeller();
Account from = getAccountBuyer();
Money fee = calcFee();
Money amount = getOrderPrice();

var receipt = transfer(to, from, fee, amount);
send(receipt);


SP>·>прокси типы, которые отстой.

SP>конструктивная аргументация...
Предложи конструктивно как переписать правильно, чтобы ошибка была очевидна.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 26.07.2023 10:44 · . Предыдущая версия . Еще …
Отредактировано 26.07.2023 10:38 · . Предыдущая версия .
Re[8]: Обязательный именованный параметр с проверкой при ком
От: sergii.p  
Дата: 26.07.23 12:31
Оценка:
Здравствуйте, ·, Вы писали:

·>Предложи конструктивно как переписать правильно, чтобы ошибка была очевидна.


// Общая часть. Каждый раз переписывать не надо
class NewType<T> {
    T value;
    public T to_underlying() {
        return value;
    }
    public NewType(T obj) {
        this.value = obj;
    }
    // можно ещё переопределить методы toString, hashCode, кому надо
}

// Каждый тип объявлять немного многословно. Не хватает typedef конструкции в языке
class XType extends NewType<Integer> {
    XType(int val) { super(val); }
}

class YType extends NewType<Integer> {
    YType(int val) { super(val); }
}


class HelloWorld {
    static void print(XType x, YType y) {
        //...
    }
    
    public static void main(String[] args) {
        var x = new XType(3);
        var y = new YType(42);
        print(x, y);
        //print(y, x); //Error YType cannot be converted to XType
    }
}
Отредактировано 26.07.2023 12:36 sergii.p . Предыдущая версия .
Re[9]: Обязательный именованный параметр с проверкой при ком
От: · Великобритания  
Дата: 26.07.23 13:04
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>·>Предложи конструктивно как переписать правильно, чтобы ошибка была очевидна.

SP>class XType extends NewType<Integer> {
Это "вариант 1" у топик-стартера в первом сообщении и его мнение "плодить класс на каждый параметр это чересчур". . Зачем ты это всё рассказал-то?
Кстати, вместо твоей колбасы с генериками давно можно просто писать record XType(int value){}.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[10]: Обязательный именованный параметр с проверкой при ком
От: sergii.p  
Дата: 26.07.23 13:31
Оценка:
Здравствуйте, ·, Вы писали:

·>Это "вариант 1" у топик-стартера в первом сообщении и его мнение "плодить класс на каждый параметр это чересчур". .


у ТС класс занимал 10 строчек. Тут 3. Мне показалось что это основной критерий почему нельзя "плодить класс на каждый параметр".

·>Зачем ты это всё рассказал-то?


ну ты ведь попросил, я рассказал.

·>Кстати, вместо твоей колбасы с генериками давно можно просто писать record XType(int value){}.


возможно. java — не мой основной язык. Если это работает, тем более идиома нового типа будет актуальна.
Re[11]: Обязательный именованный параметр с проверкой при ко
От: · Великобритания  
Дата: 26.07.23 14:03
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>·>Это "вариант 1" у топик-стартера в первом сообщении и его мнение "плодить класс на каждый параметр это чересчур". .

SP>у ТС класс занимал 10 строчек. Тут 3. Мне показалось что это основной критерий почему нельзя "плодить класс на каждый параметр".
Потому что ты смухлевал, удалив переводы строк и static метод для конструирования. А по сути ровно то же.
Ты предложил так
class XType extends NewType<Integer> {
    XType(int val) { super(val); }
}

Можно было так:
class XType { final int val;
    XType(int val) { this.val=val; }
}

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

SP>·>Зачем ты это всё рассказал-то?

SP>ну ты ведь попросил, я рассказал.
Ну это уже было в стартовом сообщениии. А вопрос был об альтернативах.

SP>·>Кстати, вместо твоей колбасы с генериками давно можно просто писать record XType(int value){}.

SP>возможно. java — не мой основной язык. Если это работает, тем более идиома нового типа будет актуальна.
У этой идиомы и другие недостатки. Например, нет никакого способа запретить таки использовать один и тот же тип в разных параметрах. При копи-пастах код начнёт расползаться.
Было
Receipt transfer(AccountFrom from, AccountTo to, MoneyAmount amount, MoneyFee fee);

кто-нибудь побыстрому накопипастил и получилось
Receipt transfer(AccountFrom from, AccountTo to, MoneyAmount amount, MoneyAmount discountAmount, MoneyFee fee);

и ищи баги опять...

Мой вариант с интерфейсом этим не страдает, т.к. новые методы в интерфейс нельзя копипастить.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 26.07.2023 14:33 · . Предыдущая версия . Еще …
Отредактировано 26.07.2023 14:32 · . Предыдущая версия .
Re: Обязательный именованный параметр с проверкой при компил
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 26.07.23 14:43
Оценка:
Здравствуйте, vsb, Вы писали:

На самом деле именованные аргументы хороши когда много параметров по умолчанию https://learn.microsoft.com/ru-ru/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#example-1

Проще использовать Инициализаторы объектов

Не знаю, как в яве, но в Котлине я так понимаю возможно
и солнце б утром не вставало, когда бы не было меня
Отредактировано 26.07.2023 14:43 Serginio1 . Предыдущая версия .
Re[2]: Обязательный именованный параметр с проверкой при компил
От: vsb Казахстан  
Дата: 26.07.23 15:18
Оценка: 10 (1)
Здравствуйте, Serginio1, Вы писали:

S> Проще использовать <span class='lineQuote level1'>S&gt;Инициализаторы объектов</span>


S> Не знаю, как в яве, но в Котлине я так понимаю возможно


В котлине и именованные параметры есть, так что этой проблемы не стоит в принципе.

В Java инициализаторов объектов нет. Хотя вероятно будут, как продолжение развития records, но когда — не понятно.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.