[PYTHON] Mit algebraischen Datentypen und Mustervergleich

Der Titel ist unklar.

Motivation

[Algebraischer Datentyp](http://ja.wikipedia.org/wiki/%E4%BB%A3%E6%95%B0%E7%9A%84%E3%83%87%E3%83%BC% Was ich über E3% 82% BF% E5% 9E% 8B wissen wollte) [ADTs für Python] Ich denke, es ist geschrieben in (http://stupidpythonideas.blogspot.jp/2014/08/adts-for-python.html). Ich habe es jedoch nicht verstanden, selbst wenn ich zuerst die Erklärung von Wikipedia (Japanisch) gelesen habe, und ich habe einige Blogs und Codes gelesen, um mein Verständnis zu verbessern, also habe ich zuerst den Blog-Beitrag gelesen. Es kann nicht geleugnet werden, dass es einen Mangel gegeben hat.

Ich habe keinen einführenden und leicht verständlichen japanischen Datentyp für algebraische Datentypen gefunden, aber ich fand die folgende Erklärung einfach und leicht zu akzeptieren.

** Reichhaltige Datentypen ** Im Allgemeinen integrieren funktionale Sprachen Strukturen (direkte Produkte), kommunale Körperschaften (direkte Summen), Aufzählungstypen und rekursive Typen, um umfangreiche Datentypen bereitzustellen, die präzise ausgedrückt werden können. Mit diesem umfangreichen Datentyp können beispielsweise Baumstrukturen präzise und sicher definiert werden (ohne gefährliche Nullen). Diese Baumstruktur besteht natürlich aus persistenten Daten.

Darüber hinaus können Sie Daten intuitiv bedienen, indem Sie umfangreiche Datentypen und Mustervergleiche verwenden, was eine integrierte Funktion ist. Je reicher die Typen, desto einfacher und sicherer lässt sich das Problem lösen, sodass das Programmieren Spaß macht.

Algebraische Datentypen in Python

Python ist keine funktionale Programmiersprache und unterstützt keine algebraischen Datentypen.

Im letzten Sommer wurde viel über Pythons Typsystem diskutiert, als vorgeschlagen wurde, die Annotationssyntax vom Typ mypy in Python 3.5 zu integrieren. [ADTs für Python] von Andrew Barnert (http://stupidpythonideas.blogspot.jp/2014/08/adts-for-python.html) ist ebenfalls einer der Artikel, die zu dieser Zeit geschrieben wurden. Wenn Sie diesen Artikel lesen, sollten Sie ihn grundsätzlich selbst in Betracht ziehen.

Ich habe es als Zusammenfassung meiner Lektüre geschrieben, aber ich denke, dass mein Verständnis und meine Interpretation möglicherweise falsch sind. Wenn also etwas nicht stimmt, begrüße ich Sie.

Direkter Produkttyp (Produkttypen)

Viele Programmiersprachen repräsentieren direkte Produkttypen als Strukturen oder Objekte (wie bei der objektorientierten Programmierung).

Note the equivalency to the "implicit namedtuple literals" that people suggest for Python every few months.

In Python [namedtuple] Es wird gesagt, dass (http://docs.python.jp/3/library/collections.html#collections.namedtuple) dem direkten Produkttyp als Literal entspricht.

3.4


>>> from collections import namedtuple
>>> Stuff = namedtuple('Stuff', ['answer', 'manna'])
>>> type(Stuff)
<class 'type'>
>>> s = Stuff(42, 'spam')
>>> s
Stuff(answer=42, manna='spam')
>>> s.secret = 'cannot assign'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Stuff' object has no attribute 'secret'

Als Alternative zu namedtuple [_ _ * slots * __] Es wird ergänzt, dass die Verwendung von (http://docs.python.jp/3/reference/datamodel.html#slots) auch dem genannten Datensatztyp nahe kommt.

3.4


>>> class StuffSlots:
...     __slots__ = ('answer', 'manna')
...     def __init__(self, answer, manna):
...         self.answer = answer
...         self.manna = manna
... 
>>> s2 = StuffSlots(52, 'ham')
>>> s2.answer
52
>>> s2.manna
'ham'
>>> s2.secret = 'cannot assign'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'StuffSlots' object has no attribute 'secret'

Die Tatsache, dass es sich nicht um einen Taple handelt, kann jedoch theoretische Probleme haben, selbst wenn er praktisch gut ist. Der Grund dafür ist, dass nichts * Slots * dazu zwingt, Konstruktorargumente zuzuordnen, und es ist schwer vorstellbar, wie anonym eine Inline-Erweiterung codiert werden kann.

Versuchen wir, die oben genannten * StuffSlots * mit dem Konstruktor der Klasse * type * auszudrücken.

3.4


>>> StuffSlotsType = type('StuffSlotsType', (object,), dict(__slots__=('answer', 'manna'), __init__=lambda self, answer, manna: setattr(self, 'answer', answer) or setattr(self, 'manna', manna)))
>>> s3 = StuffSlotsType(62, 'egg')
>>> s3.answer
62
>>> s3.manna
'egg'
>>> s3.secret = 'cannot assign'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'StuffSlotsType' object has no attribute 'secret'
>>> s3.__slots__
('answer', 'manna')
>>> s3.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'StuffSlotsType' object has no attribute '__dict__'

Ich frage mich, ob es kompliziert ist, * __slots __ * und * __ init__ * richtig definieren zu müssen.

Direkter japanischer Typ (Summentypen)

Ich denke, es wird gesagt, dass eines der Merkmale der funktionalen Programmiersprache darin besteht, dass dies präzise ausgedrückt werden kann.

Python doesn't have sum types at all, except implicitly.

Ich weiß nicht, worauf sich dieses "außer dem Impliziten" bezieht, aber es gibt kein direktes Äquivalent in Python.

And explicit sum types, especially either tagged sums of anonymous products or sums of named products, are exactly what people are missing when they ask for ADTs in Python.

Als "expliziter" direkter Summentyp scheinen wir zu einem algebraischen Datentyp zu gelangen, der Python fehlt, wenn wir eine anonyme direkte Produktsumme oder eine benannte direkte Produktsumme betrachten.

Ich denke, dass "explizit" sich hier darauf bezieht, wie es in das vorhandene Python-Typsystem implementiert wird. Wir überlegen, wie dies mit zwei Ansätzen erreicht werden kann.

MyPy Union

Einführung in die Definition von * Vielleicht * wie Haskell. Im Originalartikel wird die Typvariable * T * verwendet und ** | ** (Pipe) anstelle von * Union *.

While this only gives us untagged unions, as noted above, tagged unions are equivalent to untagged unions of record types. So, if we wanted to define a Haskell-like Maybe, we could do this:

Just = namedtuple('Just', (('value', T,)))
Nothing = namedtuple('Nothing', ())
Maybe = Just | Nothing

> Also, without pattern matching, the only way to actually use this is with isinstance checks (or EAFP try/except):

> ```py3
    def spam(eggs: Maybe[int]):
        if isinstance(n, Just[int]):
            print('Spam with {} eggs'.format(eggs.value))
        else:
            print('The spam stands alone')

Ich habe nicht ausdrücklich angegeben, dass dies Pseudocode ist, aber dieser Code kann nicht in Python (mypy) ausgeführt werden.

Der tatsächlich ausführbare Python-Code lautet also wie folgt.

mypy_sum1.py


# -*- coding: utf-8 -*-
from typing import typevar, Generic, Union

T = typevar('T')

class Just(Generic[T]):

    __slots__ = ('value',)

    def __init__(self, value: T) -> None:
        self.value = value

class Nothing:
    """
    mypy claims by defining Nothing as below

    >>> Nothing = namedtuple('Nothing', ())
    List literal expected as the second argument to namedtuple()

    >>> Nothing = type('Nothing', (), dict(__slots__=()))
    Invalid type "__main__.Nothing"
    """
    __slots__ = ()

Maybe = Union[Just, Nothing]

def spam(eggs: Maybe):
    if isinstance(eggs, Just[int]):
        eggs.value += '+1'
        print('Spam with {} eggs[int].'.format(eggs.value))
    elif isinstance(eggs, Just[str]):
        eggs.value += 1
        print('Spam with {} eggs[str].'.format(eggs.value))
    else:
        print('The spam stands alone')

Ich bin mir nicht sicher, wie ich mit der Typprüfung * namedtuple * von mypy umgehen soll, daher verwende ich * __slots__ , um eine Alternative zum direkten Produkttyp ( Just * und * Nothing *) zu definieren. (Um Fehler bei der Typy-Typprüfung zu vermeiden).

Die Syntax von mypy definiert den direkten Summentyp wie folgt.

3.4


Maybe = Union[Just, Nothing]

Lassen Sie uns nun die Typprüfung ausführen.

(mypy)$ mypy sum_mypy1.py 
sum_mypy1.py: In function "spam":
sum_mypy1.py, line 29: Unsupported operand types for + ("int" and "str")
sum_mypy1.py, line 32: Unsupported operand types for + ("str" and "int")

Es ist schwer zu verstehen, da die Zeilennummer nicht beschrieben ist, aber ich konnte den Typfehler bei dem beabsichtigten Eier.Wert + = ... der Funktion * spam () * abfangen.

Über die Implementierung dieser * spam () * Funktion durch * isinstance *

There's nothing theoretically wrong here, but it certainly is ugly.

Theoretisch ist es nicht falsch, aber es ist unangenehm, sagt Andrew: weinen:

Abgesehen davon hat dieser Code die richtige Syntax für mypy, aber den falschen Code für Python.

3.4


>>> from sum_mypy1 import *
>>> spam(Just[int](3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../sum_mypy1.py", line 29, in spam
    eggs.value += '+1'
TypeError: unsupported operand type(s) for +=: 'int' and 'str'

Wenn Sie einen int-Typwert im Konstruktor verwenden, tritt ein Typfehler wie bei der Typprüfung auf.

3.4


>>> spam(Just[str]('ham'))
Spam with ham+1 eggs[int].

Es kann ausgeführt werden, wenn ein Wert vom Typ str als Konstruktor verwendet wird.

Dies liegt daran, dass * Just [T] * eine syntaktische Darstellung von mypy ist und im Python-Code * isinstance (Eier, Just [T]) * nur * isinstance (Eier, Just) * ist. Daher wird der Code ausgeführt, der * Just [int] * der Funktion * spam () * annimmt.

Die mypy-Syntax ist so implementiert, dass sie die Ausführung des vorhandenen Python-Codes nicht beeinträchtigt. Dies kann also die Grenze zwischen mypy und der dynamischen Typisierung von Python sein.

Swift-style Enum

Eine andere Möglichkeit, den direkten Summentyp auszudrücken, besteht darin, den Aufzählungstyp im Swift-Stil einzuführen.

Im Python-Modul enum funktioniert der folgende Code nicht, aber ich denke, die Absicht dieses Pseudocodes ist leicht zu verstehen.

3.4


class Barcode(Enum):
    UPCA: (int, int, int, int) = 1
    QRCode: str = 2

Ein Muster, in dem jedes Argument an den Konstruktor der Enum-Konstante übergeben wird, hat einen Namen.

3.4


class Barcode(Enum):
    UPCA: (LL: int, L: int, R: int, RR: int) = 1
    QRCode: str = 2

Rekursiver Typ

Eines der wichtigen Merkmale algebraischer Datentypen ist, dass sie rekursiv behandelt werden können.

Wenn die Liste durch einen algebraischen Datentyp dargestellt wird, wie unten gezeigt

List(Cons(head: T, tail: List[T]) | Nil())

Der Listentyp besteht aus dem Cons-Typ und dem Nil-Typ, * Nil () * ist eine Instanz des Nil-Typs und des List-Typs und * Cons (1, Nil ()) * ist der Cons [int] -Typ und der List [int] -Typ. Es ist auch eine Instanz.

Zu diesem Zeitpunkt ist es erforderlich, den Listentyp (Name) von Cons (Konstruktor von) auflösen zu können.

Für Python entweder die Vorwärtsreferenz (http://mypy.readthedocs.org/en/latest/kinds_of_types.html#class-name-forward-references) wie in mypy. Es wird angegeben, dass ein direkter Summentyp oder Datensatztyp definiert werden muss, der so behandelt werden kann, als ob der Typ definiert wäre.

Ist ein algebraischer Datentyp nur eine Teilmenge von Klassen?

Die Substanz von * namedtuple * und * enum * ist eine Klasse in Python. Und in Python sind alle Typen Klassen.

More importantly, ADTs are a strict subset of classes. The "strict subset" part is what makes them useful: they have a static structure, they have

Wichtig ist, dass der algebraische Datentyp * "strenge Teilmenge" * der Klasse ist. Was an dieser * strengen Teilmenge * nützlich ist, ist, dass sie eine statische Struktur hat ...?

Es tut mir leid, ich verstehe die englische Übersetzung und Absicht hier nicht. Lass es mich wissen, bitte. : Schrei:

Brauchen Python wirklich algetische Datentypen?

Die große Frage ist, ob Sie die in * namedtuple * oder * enum * implementierten algebraischen Datentypen anstelle von Klassen verwenden möchten.

If the language had pattern matching that only worked on ADTs, the answer might well be "quite often".

Wenn die Sprache einen Mustervergleich hat, der sich nur mit algebraischen Datentypen befasst, lautet die Antwort * "ziemlich oft" *.

But without pattern matching, or with pattern matching that worked on classes that chose to opt in (as described in my previous blog post), I think it wouldn't be that often.

Andrew glaubt jedoch, dass es nicht * "oft" * wäre, wenn es keinen Mustervergleich gibt oder wenn der Mustervergleich für eine Klasse mit ausgewähltem Opt-In funktioniert.

Der ursprüngliche Artikel führt einen Pseudocode ein, der den ML- oder Haskel-ähnlichen Mustervergleich nachahmt und JSON analysiert, um das Paradigma der algebraischen Datentypen und des Mustervergleichs zu erklären.

class Name((first: str, last: str)):
    def tojson(self):
        return JSON.object({'first': self.first, 'last': self.last})

    @classmethod
    def fromjson(cls, j):
        case j:
            of JSON.object as o:
                return cls(o['first'], o['last'])
        raise ValueError('{} is not a name'.format(j))

class Person((aliases: Sequence[Name], age: float)):
    def tojson(self):
        return JSON.object({'aliases': JSON.array(map(Name.tojson, self.aliases)), 'age': age})

    @classmethod
    def fromjson(cls, j):
        case j:
            of JSON.object as o:
                case o['aliases']:
                    of JSON.array as a:
                        return cls([Name.fromjson(name) for name in a], o['age'])
        raise ValueError('{} is not a person'.format(j))

Wenn es keinen Mustervergleich gibt, kann es üblich sein (?), Den Prozess des Durchlaufens einer solchen Baumstruktur mit [Besuchermuster] zu implementieren (http://qiita.com/t2y/items/3ecdc2643426a345d75a). ..

In dem am Anfang vorgestellten Einführungsartikel zur Funktionsprogrammierung

Daten können intuitiv durch umfangreiche Datentypen und Mustervergleiche bearbeitet werden, was eine integrierte Funktion ist.

Sie können sehen, wofür der Satz bestimmt ist.

Zusammenfassung

Wenn Sie das Konzept algebraischer Datentypen in der Funktionsprogrammierung zusammen mit dem Mustervergleich kennenlernen, werden Sie die Einfachheit und Effektivität ihrer Ausdruckskraft leicht verstehen.

So, is that a good enough use case to include ADTs in Python?

Recommended Posts

Mit algebraischen Datentypen und Mustervergleich
Mit algebraischen Datentypen und FizzBuzz
Mit algebraischen Datentypen und objektorientierter Programmierung
Verständnis der Datentypen und des Beginns der linearen Regression
Python-Variablen und Datentypen, die mit Chemoinfomatik gelernt wurden
cv2-Funktionen und Datentypen (OpenCV-Python-Bindung)
Punkt- und Figurendatenmodellierung
CSV-Daten extrahieren und berechnen