[PYTHON] Avec les types de données algébriques et la correspondance de modèles

Le titre n'est pas clair.

Motivation

[Type de données algébriques](http://ja.wikipedia.org/wiki/%E4%BB%A3%E6%95%B0%E7%9A%84%E3%83%87%E3%83%BC% Ce que je voulais savoir sur E3% 82% BF% E5% 9E% 8B) [ADTs for Python] Je pense qu'il est écrit en (http://stupidpythonideas.blogspot.jp/2014/08/adts-for-python.html). Cependant, je n'ai pas compris même si j'ai lu l'explication de wikipedia (japonais) au début, et j'ai lu quelques blogs et codes pour améliorer ma compréhension, alors j'ai d'abord lu le billet de blog. On ne peut nier qu'il y ait eu pénurie.

Je n'ai pas trouvé de type de données japonais introductif et facile à comprendre pour les types de données algébriques, mais j'ai pensé que l'explication suivante était simple et facile à accepter.

** Types de données riches ** En général, les langages fonctionnels intègrent des structures (produits directs), des corps communaux (sommes directes), des types d'énumération et des types récursifs pour fournir des types de données riches qui peuvent être exprimés de manière concise. Avec ce type de données riche, par exemple, les structures arborescentes peuvent être définies de manière concise et sûre (sans valeurs nulles dangereuses). Bien entendu, cette arborescence est constituée de données persistantes.

De plus, vous pouvez exploiter les données de manière intuitive en utilisant des types de données riches et la correspondance de modèles, qui est une fonction intégrée. Plus les types sont riches, plus le problème sera facile et sûr à résoudre, ce qui rendra la programmation amusante.

Types de données algébriques en Python

Python n'est pas un langage de programmation fonctionnel et ne prend pas en charge les types de données algébriques.

Il y a eu beaucoup de discussions sur le système de types de Python l'été dernier lorsqu'il a été suggéré d'incorporer la syntaxe d'annotation de type mypy dans Python 3.5. [ADTs for Python] par Andrew Barnert (http://stupidpythonideas.blogspot.jp/2014/08/adts-for-python.html) est également l'un des articles écrits à cette époque. En gros, en lisant cet article, considérons-le par vous-même.

Je l'ai écrit comme un résumé de ce que j'ai lu, mais je pense que ma compréhension et mon interprétation peuvent être erronées, donc s'il y a quelque chose qui ne va pas, je vous souhaite la bienvenue.

Type de produit direct (types de produit)

De nombreux langages de programmation représentent des types de produits directs sous forme de structures ou d'objets (comme dans la programmation orientée objet).

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

En Python [namedtuple] On dit que (http://docs.python.jp/3/library/collections.html#collections.namedtuple) correspond au type de produit direct en tant que littéral.

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'

Aussi, comme alternative à namedtuple [__ * slots * __] Il est ajouté que l'utilisation de (http://docs.python.jp/3/reference/datamodel.html#slots) est également proche du type d'enregistrement nommé.

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'

Cependant, le fait qu'il ne s'agisse pas d'un taple peut poser des problèmes théoriques même s'il est pratiquement bon. Il est dit que la raison est qu'il n'y a rien qui force * slots * à mapper les arguments du constructeur, et il est difficile d'imaginer comment coder de manière anonyme une expansion en ligne.

Essayons d'exprimer * StuffSlots * mentionné ci-dessus avec le constructeur de la classe * type *.

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__'

Je me demande si c'est compliqué d'avoir à définir correctement * __slots __ * et * __ init__ *.

Type japonais direct (types de somme)

Je pense qu'on dit que l'une des caractéristiques du langage de programmation fonctionnelle est que cela peut être exprimé de manière concise.

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

Je ne sais pas à quoi fait référence ce «sauf implicite», mais il n'y a pas d'équivalent direct en 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.

En tant que type de somme directe "explicite", il semble que nous arrivions à un type de données algébrique qui manque à Python lorsque l'on considère une somme de produit directe anonyme ou une somme de produit directe nommée.

Je pense que "explicite" se réfère ici à la façon de l'implémenter dans le système de type Python existant. Nous examinons comment y parvenir à partir de deux approches.

MyPy Union

Présentation de la définition de * Peut-être * comme Haskell. Dans l'article d'origine, la variable de type * T * est utilisée et ** | ** (pipe) est utilisé à la place de * 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')

Je n'ai pas explicitement déclaré qu'il s'agissait d'un pseudo code, mais ce code ne peut pas être exécuté en Python (mypy).

Le code Python exécutable réel est donc le suivant.

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')

Je ne sais pas comment gérer la vérification du type * namedtuple * de mypy, donc j'utilise * __slots__ * pour définir une alternative au type de produit direct (* Just * et * Nothing *). (Pour éviter les erreurs du vérificateur de type mypy).

La syntaxe de mypy définit le type de somme directe comme suit.

3.4


Maybe = Union[Just, Nothing]

Maintenant, exécutons le vérificateur de type.

(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")

C'est difficile à comprendre parce que le numéro de ligne n'est pas décrit, mais j'ai pu attraper l'erreur de type à la fonction ʻeggs.value + = ... ʻ de la fonction * spam () *.

À propos de l'implémentation de cette fonction * spam () * par * isinstance *

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

Ce n'est pas faux en théorie, mais c'est gênant, dit Andrew: pleurez:

En passant, ce code a la syntaxe correcte pour mypy, mais le mauvais code pour 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'

Lors de la prise d'une valeur de type int dans le constructeur, une erreur de type se produit comme le vérificateur de type,

3.4


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

Il peut être exécuté en prenant une valeur de type str comme constructeur.

C'est parce que * Just [T] * est une représentation syntaxique de mypy, et en code Python * isinstance (eggs, Just [T]) * n'est que * isinstance (eggs, Just) *. Par conséquent, le code qui suppose * Just [int] * de la fonction * spam () * est exécuté.

La syntaxe mypy est implémentée de sorte qu'elle n'affecte pas l'exécution du code Python existant, donc cela peut être la frontière entre mypy et le typage dynamique de Python.

Swift-style Enum

Une autre façon d'exprimer le type de somme directe consiste à introduire le type enum de style Swift.

Dans le module enum de Python, le code suivant ne fonctionne pas, mais je pense que l'intention de ce pseudo code est facile à comprendre.

3.4


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

Un modèle dans lequel chaque argument passé au constructeur de la constante enum a un nom.

3.4


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

Type récursif

L'une des caractéristiques importantes des types de données algébriques est qu'ils peuvent être traités de manière récursive.

Lorsque la liste est représentée par un type de données algébrique comme indiqué ci-dessous

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

Le type de liste comprend le type Cons et le type Nil, * Nil () * est une instance de type Nil et de type List, et * Cons (1, Nil ()) * est le type Cons [int] et le type List [int]. C'est aussi un exemple.

A ce moment, il est nécessaire de pouvoir résoudre le type List (nom) depuis Cons (constructeur de).

Pour Python, soit la référence forward (http://mypy.readthedocs.org/en/latest/kinds_of_types.html#class-name-forward-references) comme cela se fait dans mypy. Il est indiqué qu'il est nécessaire de définir un type de somme directe ou un type d'enregistrement pouvant être traité comme si le type était défini.

Un type de données algébrique n'est-il qu'un sous-ensemble de classes?

La substance de * namedtuple * et * enum * est une classe en Python. Et en Python, tous les types sont des classes.

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

L'important est que le type de données algébrique soit * "strict subset" * de la classe. Ce qui est utile à propos de ce * sous-ensemble strict *, c'est qu'il a une structure statique ...?

Je suis désolé, je ne comprends pas la traduction anglaise et l'intention ici. S'il vous plaît, faites-moi savoir. : cri:

Python a-t-il vraiment besoin de types de données algétiques?

La grande question est de savoir si vous souhaitez utiliser les types de données algébriques implémentés dans * namedtuple * ou * enum * au lieu des classes.

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

Si le langage a une correspondance de motifs qui ne traite que des types de données algébriques, la réponse est * "assez souvent" *.

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.

Cependant, Andrew pense que ce ne serait pas * "souvent" * s'il n'y a pas de correspondance de modèle ou si la correspondance de modèle fonctionne sur une classe avec l'option opt-in sélectionnée.

L'article original présente un pseudo-code qui imite la correspondance de modèles de type ML ou Haskel qui analyse JSON pour expliquer le paradigme des types de données algébriques et de la correspondance de modèles.

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))

S'il n'y a pas de correspondance de motif, il peut être courant (?) D'implémenter le processus de traversée d'une telle structure arborescente avec Motif de visiteur. ..

Dans l'article d'introduction sur la programmation de fonctions présenté au début

Les données peuvent être manipulées de manière intuitive par des types de données riches et la correspondance de modèles, qui est une fonction intégrée.

Vous pouvez dire à quoi la phrase est destinée.

Résumé

Si vous apprenez le concept de types de données algébriques dans la programmation de fonctions avec la correspondance de modèles, vous comprendrez facilement la simplicité et l'efficacité de leur expressivité.

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

Recommended Posts

Avec les types de données algébriques et la correspondance de modèles
Avec les types de données algébriques et FizzBuzz
Avec les types de données algébriques et la programmation orientée objet
Comprendre les types de données et le début de la régression linéaire
Variables Python et types de données appris avec la chimio-automatique
fonctions cv2 et types de données (liaison python OpenCV)
Modélisation de données point et figure
Extraire les données csv et calculer