LINUX.ORG.RU
ФорумTalks

Выразительный код без программазма на вашем любимом ЯП

 ,


1

2

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

На Джулии это довольно прямолинейно выражается следующим образом:

function makesequencer(next_f)
    let X = Nothing
        function(Y)
            if X == Nothing
                # initialization
                X = Y
            else
                Xnew = next_f(X, Y)
                X = Xnew
            end
            return X
        end
    end
end

А можно ли ту же идею реализовать на вашем любимом ЯП? Желательно с минимумом программазма в виде новых типов, классов-хренассов... В моем примере вообще нет упоминания никаких типов данных.

★★★★★

Можно. Причем разными способами. И в сях. И в плюсах. И думаю в любом ООП языке через объект. (и это правильно)

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

RiseOfDeath ★★★★
()
Последнее исправление: RiseOfDeath (всего исправлений: 2)
Ответ на: комментарий от RiseOfDeath

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

Кому должен?

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

Кроме того:

Желательно с минимумом программазма в виде новых типов, классов-хренассов

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

RiseOfDeath ★★★★
()
Последнее исправление: RiseOfDeath (всего исправлений: 1)
Ответ на: комментарий от vertexua

Кому должен?

Как минимум здравому смыслу.

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

И я знаю только одно исключение из этого правила - рандомизатор.

Готов выслушать ваши примеры, когда такое допустимо.

p.s.

Я, если что, считаю что дефолтный strtok это не очень хорошо (в отличии от strtok_r)

RiseOfDeath ★★★★
()
Последнее исправление: RiseOfDeath (всего исправлений: 5)
Ответ на: комментарий от RiseOfDeath

не корректный подход т.к. условный f(x) должен для одинаковых x давать одинаковый результат

«Не корректный» с какой т.з.? С т.з. Джулии все корректно. А с объектами, по крайней мере в С и С++ будет то же самое: object.method(agrs) не обязан быть инвариантом в любых местах программы.

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

ИМХО: Если это собственное поведение объекта - можно (если это не синглтон). Если говорить о функции самой по себе, которая работает не свой объект, а действует во вне - так нельзя.

p.s.

Зачему что это мое личное мнение. Я готов выслушать аргументы против.

RiseOfDeath ★★★★
()
Последнее исправление: RiseOfDeath (всего исправлений: 3)

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&am...

fn make_sequencer<F, T>(next: F) -> impl FnMut() -> Option<T>
where
    F: Fn(Option<T>) -> Option<T>,
    T: Clone,
{
    let mut value = None;
    move || {
        value = next(value.clone());
        value.clone()
    }
}

fn main() {
    let mut seq = make_sequencer(|x: Option<i32>| match x {
        Some(x) => Some(x + 1),
        None => Some(0),
    });
    for _ in 0..5 {
        println!("{:?}", seq());
    }
}

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

Вторая версия. Я заметил что там инициализация еще нужна. Но у тебя кривоватая какая-то.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&am...

fn make_sequencer<F, T>(init: T, next: F) -> impl FnMut() -> T
where
    F: Fn(T) -> T,
    T: Clone,
{
    let mut value = init;
    move || {
        value = next(value.clone());
        value.clone()
    }
}

fn main() {
    let mut seq = make_sequencer(0, |x| x + 1);
    for _ in 0..5 {
        println!("{:?}", seq());
    }
}

Вот из этого кода столько ассемблера

https://pastebin.com/ms7iafxk

;)

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

В моем примере вообще нет упоминания никаких типов данных.

Ну это просто одна из фич языка - generic by default, пока не укажешь ограничения. В принципе удобно, но есть свои недостатки.

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

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

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

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

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

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

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

Не надо гадать, все написано в документации

vertexua ★★★★★
()

На Джулии это довольно прямолинейно

И довольно убого. Решать что-либо за счёт подобных грязных функций — плохая идея. Здесь ведь даже текущее состояние (X) нельзя узнать.

Xnew = next_f(X, Y) X = Xnew

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

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

WitcherGeralt ★★
()
        end
    end
end

Ужас какой.

EXL ★★★★★
()

Пфффф static int counter; Си, всё ясно и лаконично

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

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

o_O

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

Ого, и этот капец называли убийцей питона!

def gen(next_f, x):
    while True:
        yield x := next_f(x)

рассосано на 13 строк? Чума.

t184256 ★★★★★
()
def makesequencer(next_f):
    """
    >>> agg = makesequencer(lambda x, y: x + y)
    >>> agg(0)
    0
    >>> agg(5)
    5
    >>> agg(25)
    30
    """
    agg = []
    def f(y):
        agg.append(y)
        return next_f(agg[-2], agg[-1]) if len(agg) > 1 else y
    return f
aedeph_ ★★
()

создание расширяемых (настраиваемых) функций, умеющих хранить свое состояние.

Зачем изобретать кульхацкерский «велосипед», если есть Supplier?

package test;

import java.util.function.Supplier;

public class Test {
    
    static private Supplier<Integer> raccoon() {
        final int[] cnt = new int[1];
        return (() -> ++cnt[0]);
    }
    
    public static void main(String[] args) {
        Supplier<Integer> lotor = raccoon();
        System.out.println(lotor.get());
        System.out.println(lotor.get());
        System.out.println(lotor.get());
    }
}

Идею я подал - дальше самостоятельно.

На собеседовании, естественно.

Bioreactor ★★★★★
()
Последнее исправление: Bioreactor (всего исправлений: 2)
Ответ на: комментарий от t184256

А, ну у тебя next его должен возвращать, а это не то. Если next_f чистая, то в последовательность нельзя вмешаться. Видимо, это требуется.

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

А, ну у тебя next его должен возвращать, а это не то.

Нет, ты что-то не так понял. У меня он должен быть отправлен генератору .send()ом и будет принят y = yield ...ом.

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

Это не то же самое, что в условии. Вот этот вариант ближе: Выразительный код без программазма на вашем любимом ЯП (комментарий)

Только там вся история не нужна (динамический массив не нужен).

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

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

ТС спросил «А можно ли ту же идею реализовать на вашем любимом ЯП?», а не «изобретите паттерн генератор столь же криво, как и я».

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

Нет, ООПизма хотелось бы избежать. Все эти «public private static void», "..get()" - это ненужная инженерия для простого, но расширяемого примера. Придумали какой-то новый класс сапплаер, но код такой же громоздкий, как и всегда в этих Джавах.

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

В условии что-то такое (фиксированного количества предыдущих значений) и требуется. Вместо коллекции для этого конечно лучше взять collections.deque, но это вообще никак не изменит код.

идиоматичного подхода

Помнить, что первым всегда next, а дальше send? Нахер такие идиоматичные подходы.

aedeph_ ★★
()
Последнее исправление: aedeph_ (всего исправлений: 1)
Ответ на: комментарий от seiken

Кому не нужна? А вообще.

def makesequencer(next_f):
    x = None
    def f(y):
        nonlocal x
        x = next_f(x, y) if x else y
        return x
    return f
aedeph_ ★★
()
Ответ на: комментарий от seiken

ООПизма хотелось бы избежать.

Ну, что тут сказать... Каждый - ССЗБ.

Или адепт секты проФФеСора В.С.ЛугоФФского.

Это понятный код

import java.util.function.Supplier;

public class Test {
    
    static private Supplier<Integer> raccoon(int x, int y) {
        final int[] cnt = new int[1];
        cnt[0] = x;
        return (() -> cnt[0] += y);
    }
    
    public static void main(String[] args) {
        Supplier<Integer> lotor = raccoon(5, 5);
        for (int i = 0; i < 3; i++) {
            System.out.println(lotor.get());
        }
    }
}

10
15
20

А то, что в сообщении топика и на прочих недоязычках ниже - кулькакерское поделие.

Bioreactor ★★★★★
()
Последнее исправление: Bioreactor (всего исправлений: 1)
Ответ на: комментарий от seiken

Какой ужас, это страшное ООП!

class Sequencer:
    def __init__(self, next_f, x=None):
        self.next_f, self.x = next_f, x
    def __call__(self, y):
        self.x = y if self.x is None else self.next_f(self.x, y)
        return self.x
s = Sequencer(lambda x, y: x + ' ' + y)
print(s('О'))
print(s('боже'))
print(s('ООП'))
print(s('как'))
print(s('страшно'))
t184256 ★★★★★
()
Ответ на: комментарий от WitcherGeralt

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

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

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

А потом сам генератор еще жрет Y постоянно. Причем использует только первый раз

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

Помнить, что первым всегда next, а дальше send? Нахер такие идиоматичные подходы.

Не нравится, как принято в языке? Не пиши. Рекомендую познакомиться с @Goury.

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

В тред набежало читоров. Напомню - Julia компилируется LLVM в натив. Так что корректнее приносить примеры с С++ и кушать их с маслом.

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

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

В тред набежало обалдевших ребят, радостно рассказывающих, что надо было не то, что ТС просил в шапке, а вместо этого (то|это|пятое|десятое). Меня лично радует, что в их числе есть также я и сам ТС.

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

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

vertexua ★★★★★
()
shared void run() {

    function makeSequencer<Type>(Type(Type, Type) fn) given Type of Object {
        variable Type? x = null;
        function seq(Type y) {
            x = if (exists loc_x = x) then fn(loc_x, y) else y;
            return x;
        }
        return seq;
    }

    value aggregator = makeSequencer(uncurry(Integer.plus));
    print(aggregator(5));
    print(aggregator(10));
    print(aggregator(20));
}
aedeph_ ★★
()
Ответ на: комментарий от t184256
self.x = y if self.x is None else self.next_f(self.x, y)

наследие Фортрано-Матлаба живêт и развивается в форме новых ЯП типа Джулии, в т.ч и потому что позволяет с минимальными изменениями кодировать математические формулы на ЯП без шума ООПшных конструкций. В данном случае self убивает легкость чтения вычислительной логики. Также, математическое выражение вариантов предполагает записи условий и значений на отдельных строках.

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

Сколько времени прошло, а педонисты до сих пор доставляют:

[питон] А как записать многострочную лямбду?

____ЫЫЫ____ https://www.artima.com/weblogs/viewpost.jsp?thread=147358

Понятно, почему его из Гугля «выставили на мороз»(С).

Bioreactor ★★★★★
()
Последнее исправление: Bioreactor (всего исправлений: 2)
Ответ на: комментарий от t184256

Не нравится, как принято в языке?

Так и не принято, вовсе. Знаешь, сколько раз в коде всей стандартной библиотеки cpython используется send?

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

пехтон, на котором абсолютно что угодно короче

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

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

наследие Фортрано-Матлаба живêт и развивается в форме новых ЯП типа Джулии

а жаль

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

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

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

Так и не принято, вовсе.

Собирай ребят, скажи: отзываем нафиг PEP 342.

Знаешь, сколько раз в коде всей стандартной библиотеки cpython используется send?

Передай, что раз в реализации стандартной библиотеки фича не нужна, то не нужна и конечным пользователям. И, чтоб два раза не ходить, операторы @ и walrus, аннотации типов и async/await тоже пусть сразу вынесут за одним.

t184256 ★★★★★
()

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

aquadon ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.