「ひらがなのしりとり圏」 in Python

完全実装付きでもう一度お送りします、しりとりの圏 - 檜山正幸のキマイラ飼育記JavaScriptによるしりとりの圏の実装をPythonに移植しながら読んだ。圏論がわかった気がする!

僕の理解が正しければ

  • 対象aに対する恒等射」は一つとは限らない
    • 対象「あ」に対する恒等射が「あああ」でもよい
    • わかった、id(x)がcomposeに対して単位元になるという条件から「あ」じゃないといけないんだな。firstとlastに関してだけ考えていた。


僕は文字と文字列の区別がないPythonでやってしまったので誤解を招くかもしれない。ObjectクラスとMorphismクラスを作って別物であることを明確にした方がいいかもしれない

理解できた気がするので、これを見ないでもう一度0から実装する。

# -*- encoding: utf-8 -*-
"""
「ひらがなのしりとり圏」 in Python
http://d.hatena.ne.jp/m-hiyama/20090424/1240552575
のPythonへの移植
"""

def _force_unicode(s):
    if isinstance(s, str):
        s = unicode(s, "utf-8")
    return s

def _print(s):
    if isinstance(s, unicode):
        s = s.encode("utf-8")
    print s
    
    
def _is_hiragana(x):
    """ひらがな1文字かどうかを返すユーティリティ関数
    >>> _is_hiragana('あ')
    True
    >>> _is_hiragana('あうー')
    False
    >>> _is_hiragana('x')
    False
    """
    if not isinstance(x, basestring): return False
    x = _force_unicode(x)
    if len(x) != 1: return False
    if u'ぁ' <= x <= u'わ': return True
    if x in u"をんー": return True
    return False


class Category(object): 
    u"圏というものが共通に持つべき性質: 今回は空"

class SiritoriCategory(Category):
    u"ひらがなしりとり圏の実装"


    @staticmethod
    def is_object(x):
        """引数xがこの圏の「対象(object)」であるかどうか。
        ひらがなしりとり圏のobjectは1文字のひらがな。"""
        return _is_hiragana(x)

    @staticmethod
    def is_morphism(x):
        """引数xがこの圏の「射(morphism)」であるかどうか。
        ひらがなしりとり圏のobjectはひらがなで構成された空でない文字列。
        >>> SiritoriCategory.is_morphism("たぬき")
        True
        >>> SiritoriCategory.is_morphism("たぬaき")
        False
        >>> SiritoriCategory.is_morphism("")
        False
        """
        if not isinstance(x, basestring): return False
        if len(x) == 0: return False
        if all(_is_hiragana(char) for char in _force_unicode(x)): return True
        return False

    @staticmethod
    def get_domain(morph):
        """射(morphism)の域(domain)を求める
        >>> _print(SiritoriCategory.get_domain("たぬき"))

        """
        assert SiritoriCategory.is_morphism(morph)
        return _force_unicode(morph)[0]

    @staticmethod
    def get_codomain(morph):
        """射(morphism)の余域(codomain)を求める
        >>> _print(SiritoriCategory.get_codomain("たぬき"))

        """
        assert SiritoriCategory.is_morphism(morph)
        return _force_unicode(morph)[-1]

    @staticmethod
    def compose(m1, m2):
        """射(morphism)の合成(compose)を行う
        >>> _print(SiritoriCategory.compose("たぬき", "きつね"))
        たぬきつね
        """
        assert SiritoriCategory.is_morphism(m1)
        assert SiritoriCategory.is_morphism(m2)
        m1 = _force_unicode(m1)
        m2 = _force_unicode(m2)
        assert m1[-1] == m2[0]
        return m1 + m2[1:]

    @staticmethod
    def id(x):
        """object xを受け取って恒等射を返す
        >>> _print(SiritoriCategory.id("あ"))
        あああ
        """
        assert SiritoriCategory.is_object(x)
        return x

def _test():
    import doctest
    doctest.testmod()

if __name__ == "__main__":
    _test()