Fasulye Makinesi Retrospektif, bölüm 9

Fasulye Makinesi Retrospektif, bölüm 9


C#, F#, Scala ve benzerlerinden farklı olarak yerleşik herhangi bir kalıp eşleştirmesi olmayan bir dil olan Python’da kısa “örüntü eşleştirme” uygulamak istedim. Mantıksal olarak bir kalıp sadece bir yüklemdir: bir değer alan ve değer kalıpla “eşleşiyorsa” true, aksi takdirde false değerini döndüren bir işlev. Bu bölümün kodu burada.

Bu işe başladığımda iki şeyin farkına vardım. Birincisi, hata ayıklama amacıyla, dönüş değerinde yalnızca “doğru” veya “yanlış”tan daha fazla bilgiye sahip olmak güzel olabilir; özellikle karmaşık bir model eşleşmeyi başaramadığında ancak eşleşmesi gerekiyorsa, o zaman bir hata yapmışım demektir. Bu hatayı gidermek için aslında kalıpların yeni bir tür döndürmesini sağladım:

class MatchResult(ABC):
    test: Any
    submatches: Dict[str, "MatchResult")
    # ... and so on, boilerplate code for the rest of this.

Karmaşık bir model, bileşen modellerinin eşleşmemesi nedeniyle başarısız olabilir ve böylece hata sonucuna bilgi dahil edilir. Ayrıca alt sınıflar da yaptım Success Ve Fail.

Biliyor musun, tam da bu anda burada “Başarısız” kelimesini isim olarak kullandığımı fark ettim. Öyle olmalıydı Failure. Oh iyi.

İkincisi, her şey için fonksiyonları kullanmanın kesinlikten yoksun olacağını fark ettim. Baştan sona böyle bir kod yazmak istemedim:

is_binop = lambda x: Success(x) if isinstance(x, ast.BinOp) else Fail(x)

“Bu şey bir binop mu?” kavramı zaten tamamen BinOp tipi tarafından yakalanıyor; Bu nesneyi bir kalıbın gerekli olduğu yerde kullanabilir miyiz?

Farklı seçeneklerle uğraştıktan sonra sonunda şu tür disipline ulaştım:

class PatternBase(ABC):
    @abstractmethod
    def match(self, test: Any) -> MatchResult:
        pass
    def __call__(self, test: Any) -> MatchResult:
        return self.match(test)

Pattern = Union[PatternBase, int, str, float, type, list, None)

Bir model temel nesnesi, bir test değeri alan ve ayrıca çağırabileceğiniz bir işlev olarak değerlendirilebilecek bir eşleşme yüklemine sahiptir.

Bir kalıba ihtiyaç duyduğumuz her yerde, arayan kişi şunları sağlayabilir:

  • desen tabanının bir örneği
  • Hiçbiri, tamsayı, kayan nokta veya dize değeri; anlambilim “test verilen değere eşitse model eşleşir” şeklindedir.
  • bir tür; anlambilim “testin verilen türde olması durumunda model eşleşir”
  • kalıpların bir listesi; anlambilim şu şekildedir: “test aynı uzunlukta bir liste ise ve her liste öğesi karşılık gelen kalıpla eşleşiyorsa kalıp eşleşir”

Buraya kadar şu kalıpları ifade edebiliriz:

p = [ BinOp, 3, None ) 
# p matches a list of three items where the first is a binary op AST,
# the second is the number 3, and the third is the value None

Ancak “eşleşme örneklerini” nasıl ifade edebiliriz? Foo kimin bar özellik 3″ mü? Birden fazla kalıpla nasıl eşleşiriz sorusuna ilişkin daha genel bir sorunu çözerek başlayabiliriz. İşte ilk birleştiricimiz:

def match_every(*patterns: Pattern) -> Pattern:
  # ... the implementation is tedious to make it efficient,
  # but you get the idea; we produce a pattern which succeeds if
  # every given subpattern succeeds, and fails otherwise.

Test edilen değerin bir özelliği olan bir alt modelin eşleştirilmesine ilişkin spesifik problemi çözebiliriz:

class AttributeSubpattern(PatternBase):
    name: str
    subpattern: Pattern
    def match(self, test: Any) -> MatchResult:
        submatch = match(self.subpattern, getattr(test, self.name, None))
        submatches = {self.name: submatch}
        if submatch.is_success():
            return Success(test, submatches)
        return Fail(test, submatches)
    # ...

Aramak biraz hantal AttributeSubpattern bu yüzden bir eşanlamlı işlevi yaptım attribute ilave olarak. Bu sınıfın bir birleştirici olduğuna dikkat edin; bir modeli alır ve yeni bir model üretir.

Ve şimdi ilk ikisinden üçüncü bir birleştirici yapabiliriz: Bir nesnenin belirli bir türde olup olmadığını ve bir modelle eşleşen bir özniteliğe sahip olup olmadığını kontrol eden bir model istiyorum:

def type_and_attributes(typ: type, patterns: Dict[str, Pattern)) -> Pattern:
    t: List[Pattern) = [typ)
    tuples: List[Pattern) = [
        attribute(name, subpattern) for (name, subpattern) in patterns.items()
    )
    return match_every(*(t + tuples))

Ve şimdi söyleyebiliriz

is_addition = type_and_attributes(BinOp, {"op": Add})

Veya daha da iyisi, her zaman eşleşen bir model ve başka bir birleştirici oluşturalım:

class AnyPattern(PatternBase):
    def match(self, test: Any) -> MatchResult:
        return Success(test)

_any = AnyPattern()

def binop(
  op: Pattern = _any, 
  left: Pattern = _any, 
  right: Pattern = _any) -> Pattern:
  return type_and_attributes(
    BinOp, {"op": op, "left": left, "right": right})

is_addition = binop(Add)

Ayrıca aşağıdakiler için kalıpları veya birleştiricileri tanımladım:

  • Bu kalıplardan en az biriyle eşleşin
  • Listedeki ilk öğeyi bir kalıpla ve listenin sonunu başka bir modelle eşleştirin
  • Listedeki en az bir öğeyi belirli bir kalıpla eşleştirin
  • Listedeki tüm öğeleri aynı kalıpla eşleştirin
  • Bu desenin tersini eşleştirin
  • Tanımlayıcılar, işleçler vb. gibi yaygın olarak kullanılan AST düğümleri için yardımcı birleştiriciler.
  • Ve benzeri; Fikri anladın.

Araç kutumdaki bu araçlarla, “bu listedeki herhangi bir öğe tanımlayıcı değil mi?” gibi kalıpları hızla oluşturabiliyordum.

any_non_id = ListAny(negate(name()))

Desenler hangi kodun dönüştürülmesi gerektiğini tanımlar; kurallar onları dönüştürür. FAIC’de bir dahaki sefere AST dönüşümlerini kısaca temsil etmek için bir dizi kural ve kural birleştiricileri oluşturmaya bakacağız.



Source link

Postagens Similares

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *