Jakie jest dobre wytłumaczenie zasady korespondencji Tennent?

21

Trudno mi było zrozumieć, na czym polega ta zasada i dlaczego jest tak ważna w projektowaniu języka.

Zasadniczo stwierdza, że ​​każde wyrażenie exprw języku powinno być dokładnie takie samo jak ten konstrukt:

(function () { return expr; })()

Słyszałem też, że Ruby przestrzega tej zasady, podczas gdy Python nie. Nie rozumiem, dlaczego to prawda, czy w ogóle to prawda.

Andrzej
źródło
3
Nie rozumiem, dlaczego jest to nie na temat, czy ktoś mógłby mi to wyjaśnić?
Andrew
3
Jest kilka problemów; Poruszyłem go i przesyłam do Programistów, gdzie takie dyskusje są nieco bardziej mile widziane.
Zgrane
1
Ruby nie przestrzega tej zasady: Załóżmy, że exprpobiera bieżący ślad stosu.
Landei

Odpowiedzi:

18

Nigdy wcześniej nie słyszałem o „zasadzie korespondencji Tennenta”, a tym bardziej o jej znaczeniu w projektowaniu języka. Wydaje się, że przeglądanie tych wyrażeń prowadzi do jednego bloga Neal Gafter z 2006 roku, który określa, co on uważa za to i jak według niego powinno to dotyczyć również zamknięć. I większość innych na forach wydaje się odnosić do wpisu Gaftera.

Oto jednak wzmianka o „TCP” Douglasa Crockforda (nazwisko, które znam i któremu ufam): http://java.sys-con.com/node/793338/ . Częściowo

Są pewne rzeczy, których nie można ująć w ten sposób, takie jak zwroty i instrukcje break, które zdaniem zwolenników zasady korespondencji Tennent (lub TCP) są objawem nieprzyjemnego zapachu. Yow! Projektowanie języka jest już wystarczająco trudne bez konieczności radzenia sobie z halucynacjami węchowymi. Aby lepiej zrozumieć problem, kupiłem egzemplarz książki Tennent z 1981 r., Zasady języków programowania.

Okazuje się, że zasada korespondencji ma charakter opisowy , a nie nakazowy . Używa go do analizy (już zapomnianego) języka programowania Pascal, pokazując zgodność między definicjami zmiennych a parametrami procedury. Tennent nie uznaje problemu braku korespondencji zwrotów .

Wydaje się więc, że nazwa „Zasada Korespondencji Tennenta” jest niewłaściwie używana, a wszystko, o czym mówi Neal, powinno być nazywane „Wyobrażonym i możliwym uogólnionym TCP Gaftera”… lub coś w tym rodzaju. W każdym razie nie wystarczy, aby schować się za zasłoną z wyczerpanego nakładu na książkę

Nas Banov
źródło
1
+1 za „Wyobrażony i prawdopodobnie uogólniony TCP
Gaftera
9

Widzę to jako część ogólnej zasady, że dobrze zaprojektowany język robi to, czego programista naturalnie oczekiwałby. Jeśli mam blok kodu, który chcę przekształcić w zamknięcie, i zawijam ten blok za pomocą odpowiedniej składni, nie myśląc o poszczególnych wierszach kodu, to oczekuję, że blok ten zrobi to samo w zamknięciu, ponieważ zrobił inline. Jeśli niektóre instrukcje używają słowa kluczowego „to” (być może niejawnie), a język sprawia, że ​​„to” użyte w zamknięciu odnosi się do anonimowej klasy użytej do jego reprezentacji, a nie do klasy, która definiuje metodę definiującą zamknięcie, wówczas znaczenie te instrukcje się zmieniły, mój blok kodu nie robi już tego, co myślę, i muszę wyśledzić błąd i dowiedzieć się, jak zmienić kod, aby działał w zamknięciu.

Problem można również złagodzić za pomocą IDE z inteligentnymi narzędziami do refaktoryzacji, które mogą wyodrębniać zamknięcia, wykrywać potencjalne problemy, a nawet automatycznie dostosowywać wyodrębniony kod w celu rozwiązania problemów.

JGWeissman
źródło
3

Claus Reinke: w sprawie „Projektu języka opartego na zasadach semantycznych”
Tennenta przedstawia interesującą interpretację zasad:
„Korespondencja to zasada, która pozwala nam powiedzieć, że

let(this=obj, x=5) { .. }  

i

((function(x) { .. }).call(obj,5))  

powinny być równoważne i że wszystko, co możemy zrobić na formalnych listach parametrów, powinniśmy również móc to zrobić w deklaracjach i odwrotnie. ”[patrz także Reinke poniżej].

RD Tennent: Metody projektowania języka oparte na zasadach semantycznych
„Dwie metody projektowania języka oparte na zasadach wywodzących się z denotacyjnego podejścia do programowania semantyki języka programowania są opisane i zilustrowane przez aplikację do języka Pascal. Zasady są, po pierwsze, zgodnością między parametrycznym a mechanizmy deklaratywne, a po drugie zasada abstrakcji dla języków programowania dostosowana do teorii mnogości. Pojawia się kilka użytecznych rozszerzeń i uogólnień Pascala poprzez zastosowanie tych zasad, w tym rozwiązanie problemu z parametrem macierzy oraz możliwość modularyzacji ”.

Claus Reinke: „O programowaniu funkcjonalnym, projektowaniu języka i wytrwałości” w Haskell

Kris
źródło
Mikołaj Reinke: "On programowania funkcyjnego, projektowanie język, a wytrwałość" na Haskell w community.haskell.org/~claus/publications/fpldp.html
Kris
2

Aby odpowiedzieć na pytanie, dlaczego CP Tennent jest tak ważny dla projektowania języka, chciałbym zacytować Neala Gaftera :

Zasady Tennent są bardzo silne, ponieważ ich naruszenia ujawniają się w języku jako wady, nieprawidłowości, niepotrzebne ograniczenia, nieoczekiwane interakcje lub komplikacje i tak dalej.

Każde naruszenie TCP może w przyszłości zaszkodzić programiście, gdy spodziewa się, że zamknięcia będą działać jak kod nie-zamknięty, ale stwierdzi, że tak nie jest.

Thiton
źródło
1

RE Python nie przestrzega tej zasady. Ogólnie rzecz biorąc, działa zgodnie z zasadą. Podstawowy przykład:

>>> x = ['foo']
>>> x
['foo']
>>> x = (lambda: ['foo'])()
>>> x
['foo']

Jednak Python definiuje wyrażenia i instrukcje osobno. Ponieważ ifrozgałęzień, whilepętli, destrukcyjnego przypisania i innych instrukcji nie można w ogóle używać w lambdawyrażeniach, litera zasady Tennent nie ma do nich zastosowania. Mimo to ograniczenie się do używania tylko wyrażeń w języku Python nadal powoduje utworzenie pełnego systemu Turinga. Dlatego nie uważam tego za naruszenie zasady; a raczej, jeśli narusza zasadę, żaden język, który definiuje oświadczenia i wyrażenia osobno, nie może być zgodny z tą zasadą.

Ponadto, jeśli treść lambdawyrażenia przechwytuje ślad stosu lub wykonuje inne introspekcje w maszynie wirtualnej, może to powodować różnice. Ale moim zdaniem nie powinno to być traktowane jako naruszenie. Jeśli expri (lambda: expr)() koniecznie kompiluje się do tego samego kodu bajtowego, wówczas zasada naprawdę dotyczy kompilatorów, a nie semantyki; ale jeśli można je skompilować do innego kodu bajtowego, nie należy oczekiwać, że stan maszyny wirtualnej będzie identyczny w każdym przypadku.

Przy użyciu składni rozumowania można spotkać niespodziankę, chociaż uważam, że nie jest to również naruszenie zasady Tennent. Przykład:

>>> [x for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [lambda: x for x in xrange(10)]]  # surprise!
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
>>> # application of Tennent principle to first expression
... [(lambda: x)() for x in xrange(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [f() for f in [(lambda x: lambda: x)(x) for x in xrange(10)]]  # force-rebind x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> map(lambda f:f(), map(lambda x: lambda: x, xrange(10)))  # no issue with this form
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Zaskoczenie wynika z tego, jak definiowane są wyrażenia listowe. Powyższe rozumienie „niespodzianki” jest równoważne z tym kodem:

>>> result = []
>>> for x in xrange(10):
...   # the same, mutable, variable x is used each time
...   result.append(lambda: x)
... 
>>> r2 = []
>>> for f in result:
...   r2.append(f())
... 
>>> r2
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]

Patrząc w ten sposób powyższe rozumienie „niespodzianki” jest mniej zaskakujące i nie stanowi naruszenia zasady Tennent.

jagoda
źródło