LINUX.ORG.RU

О статической типизации для обычных людей

 ,


0

3

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

Есть три функции, которые должны всегда идти по порядку. То есть func_1(), func_2(), func_3() - правильно, а func_3(), func_2(), func_1() или func_2(), func_1(), func_3() - неправильно. Ошибка должна определяться на этапе компиляции.

Решаем с помощью немного нестандартного чейнинга:

#include <iostream>

using namespace std;

class Chain3
{
private:
    Chain3(){}
public:
    friend class Chain2;
    void func_3()
    {
        cout << "func_3()" <<  endl;
    }
};

class Chain2
{
private:
    Chain3 chain3{};
    Chain2(){}
public:
    friend class Chain1;
    Chain3 & func_2()
    {
        cout << "func_2()" <<  endl;
        return chain3;
    }
};

class Chain1
{
private:
    Chain2 chain2{};
public:
    Chain2 & func_1()
    {
        cout << "func_1()" <<  endl;
        return chain2;
    }
};

int main()
{
    Chain1().func_1().func_2().func_3();
    return 0;
}

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

Кроме типизации тут использованы ещё и особенности языка C++, позволяющие обойтись без макросов. В си или расте, вероятно, без макросов не получится. На Go, вероятно, такой код вообще не написать, хотя там и статическая типизация.

Цепочку можно легко продлить. Можно сделать даже не цепочку, а дерево, но при этом листья будут идти за ветвями, ветви за стволом и т.д.


Ответ на: комментарий от neumond

ерунда

Да. Питон может, а питонщики не могут. Придётся делать самому.

Вариант 1. Создаём функции по мере необходимости, затем удаляем.

func3 = None
func2 = None

def func1():

    def _func2():

        def _func3():
            print("func3")
            global func3
            func3 = None

        print("func2")
        global func2, func3
        func3 = _func3
        func2 = None

    global func2, func3
    func2 = _func2
    print("func1")

func1()
func2()
func3()

func3() # TypeError: 'NoneType' object is not callable
func2()
func1()

Вариант 2. Имитируем компиляцию, чтобы частично корректная цепочка не могла запуститься.

class Chain:

    funcs = []

    def func1(self):

        def _func1():
            print("func1")

        if not self.funcs:
            self.funcs.append(_func1)
        else:
            raise Exception
        return self

    def func2(self):

        def _func2():
            print("func2")

        if len(self.funcs) == 1:
            self.funcs.append(_func2)
        else:
            raise Exception
        return self

    def func3(self):

        def _func3():
            print("func3")

        if len(self.funcs) == 2:
            self.funcs.append(_func3)
        else:
            raise Exception
        return self

    def run(self):
        if len(self.funcs) == 3:
            for f in self.funcs:
                f()
                f = None
            self.funcs.clear()
        else:
            raise Exception

Chain().func1().func2().func3().run()

Chain().func1().func2().run() # Exception
Chain().func1().func3().func2().run() # Exception
Chain().func3().func2().func1().run() # Exception

Kogrom
() автор топика
Ответ на: комментарий от Kogrom
def ordered_call(n):
    def deco(fn):
        def wrapper(self):
            assert self._allowed_fn == n
            self._allowed_fn += 1
            return fn(self)
        return wrapper
    return deco


class A:
    def __init__(self):
        self._allowed_fn = 1

    @ordered_call(1)
    def func1(self):
        print('func1')

    @ordered_call(2)
    def func2(self):
        print('func2')

    @ordered_call(3)
    def func3(self):
        print('func3')


o = A()
o.func1()
o.func2()
o.func3()
print('ok')

o = A()
o.func2()  # AssertionError
neumond
()
Ответ на: комментарий от neumond

Второй вариант хорош тем, что легко расширяется для встроенного DSL:

class A:
    def __init__(self):
        self._allowed_fn = 1

    @ordered_call(1)
    def func1(self):
        print('func1')

    @ordered_call(2)
    def func2a(self):
        print('func2 a')

    @ordered_call(2)
    def func2b(self):
        print('func2 b')

    @ordered_call(3)
    def func3(self):
        print('func3')


o = A()
o.func1()
o.func2a()
o.func3()
print('ok a')

o = A()
o.func1()
o.func2b()
o.func3()
print('ok b')

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

Kogrom
() автор топика