Homunculus Devlog 2018-06-02

By Max Woerner Chase

In this post:


I've decided on a course of action to take with the Visitor code: pull back from dependency injection stuff, and see how far I can get with, like, distinct ClassVisitor and ValueVisitor subclasses of an abstract Visitor class.

Okay, implementing that part was easy. Next up, redoing the GameStates stuff. Okay, first, rewrite the deserializer to use getattr. Then: redo every enum state as NamedTuples with a single "name" field, and stick an instance in the enum class, which should no longer be an enum class.

It's late and intolerably muggy, so I'll just go that far for now. ... And I broke serialization, because now the different states are no longer instances of GameStates. I think I'll just get this over with and make a separate serializer for each, each targeting its own yaml field. And then I realized that I only ever actually serialize one GameState, because you can't save while it's not your turn, you're dead, or you're in a menu. So that's sort of convenient for me.

Okay, time to try converting the Visitors. Wasn't too big a deal; I just had to be on my toes.

And then I went and replaced the equality checks with isinstance checks, and got the instances using the constructor (complete with default values) instead of the holder class, which is enough to let me remove the holder class and not worry about that. Now, each isinstance, and some of the Visitors, represent possible methods I'd want to define on the classes.

However, I need to take a break from this rewrite, because I need to think about how I'm going to handle inheritance. typing.NamedTuple is a little obnoxious when it comes to integrating with Python's type system. I'm really tempted to break out dataclasses, which are coming up in the soon-to-be-released Python 3.7, but have an earlier-versions-of-Python-3 package on PyPI.

Anyway, thinking about using preview versions of stuff that hits general release in a few weeks is probably a sign that I should break here. Let's take a look at the new Visitor classes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# To test: just import.

import abc
import typing


class Visitor(abc.ABC):

    def __init__(self, default=None):
        self.state_methods = {}
        self.default = default or self._default

    @abc.abstractmethod
    def matches(self, state):
        raise NotImplementedError

    def _default(self, state, *args, **kwargs):
        raise NotImplementedError(f'Not implemented for {state}')

    def bind(self, state):
        def inner_bind(function):
            self.state_methods[state] = function
            return function
        return inner_bind

    def visit(self, state, *args, **kwargs):
        for match in self.matches(state):
            if match in self.state_methods:
                visitor = self.state_methods[match]
                break
        else:
            visitor = self.default
        return visitor(state, *args, **kwargs)


class ClassVisitor(Visitor):

    def matches(self, state):
        yield from state.__class__.__mro__


class ValueVisitor(Visitor):

    def matches(self, state):
        yield state

This is probably better than last week. I'll have to think about it more.


Next week, I take stock of the current state of things, and see where I can do myself the most good with rewrites. Looking over the tutorial, I see that it might make sense to start following it again, especially since I seem to have done some similar things as it, independently.