Udostępnij za pośrednictwem


Podstawowe pojęcia

W tej sekcji omówiono podstawowe pojęcia, które pojawiają się w kolejnych sekcjach.

Wartości

Pojedyncza część danych jest nazywana wartością. Ogólnie rzecz biorąc, istnieją dwie ogólne kategorie wartości: wartości pierwotne, które są niepodzielne i wartości ustrukturyzowane, które są konstruowane z wartości pierwotnych i innych wartości strukturalnych. Na przykład wartości

1 
true
3.14159 
"abc"

są pierwotne w tym, że nie składają się z innych wartości. Z drugiej strony wartości

{1, 2, 3} 
[ A = {1}, B = {2}, C = {3} ]

są konstruowane przy użyciu wartości pierwotnych i, w przypadku rekordu, inne wartości ustrukturyzowane.

Wyrażenia

Wyrażenie to formuła używana do konstruowania wartości. Wyrażenie można utworzyć przy użyciu różnych konstrukcji składniowych. Poniżej przedstawiono kilka przykładów wyrażeń. Każdy wiersz jest oddzielnym wyrażeniem.

"Hello World"             // a text value 
123                       // a number 
1 + 2                     // sum of two numbers 
{1, 2, 3}                 // a list of three numbers 
[ x = 1, y = 2 + 3 ]      // a record containing two fields: 
                          //        x and y 
(x, y) => x + y           // a function that computes a sum 
if 2 > 1 then 2 else 1    // a conditional expression 
let x = 1 + 1  in x * 2   // a let expression 
error "A"                 // error with message "A"

Najprostszą formą wyrażenia, jak pokazano powyżej, jest literał reprezentujący wartość.

Bardziej złożone wyrażenia są tworzone na podstawie innych wyrażeń nazywanych wyrażeniami podrzędnymi. Na przykład:

1 + 2

Powyższe wyrażenie składa się faktycznie z trzech wyrażeń. Literały 1 i 2 to podwyrażenia wyrażenia 1 + 2nadrzędnego .

Wykonywanie algorytmu zdefiniowanego przez konstrukcje składniowe używane w wyrażeniu jest nazywane obliczaniem wyrażenia. Każdy rodzaj wyrażenia ma reguły dotyczące sposobu jego obliczania. Na przykład wyrażenie literału, takie jak 1 , spowoduje wygenerowanie stałej wartości, podczas gdy wyrażenie a + b będzie przyjmować wynikowe wartości generowane przez obliczenie dwóch innych wyrażeń (a i b) i dodanie ich razem zgodnie z niektórymi zestawami reguł.

Środowiska i zmienne

Wyrażenia są oceniane w danym środowisku. Środowisko to zestaw nazwanych wartości nazywanych zmiennymi. Każda zmienna w środowisku ma unikatową nazwę w środowisku o nazwie identyfikator.

Wyrażenie najwyższego poziomu (lub katalogu głównego) jest oceniane w środowisku globalnym. Środowisko globalne jest dostarczane przez ewaluatora wyrażeń zamiast określania zawartości obliczanego wyrażenia. Zawartość środowiska globalnego zawiera standardowe definicje bibliotek i może mieć wpływ na eksporty z sekcji z niektórych zestawów dokumentów. (Dla uproszczenia przykłady w tej sekcji zakładają puste środowisko globalne. Oznacza to, że nie ma standardowej biblioteki i że nie ma żadnych innych definicji opartych na sekcjach).

Środowisko używane do oceny wyrażenia podrzędnego jest określane przez wyrażenie nadrzędne. Większość rodzajów wyrażeń nadrzędnych oblicza wyrażenie podrzędne w tym samym środowisku, w których zostały ocenione, ale niektóre używają innego środowiska. Środowisko globalne to środowisko nadrzędne, w którym obliczane jest wyrażenie globalne.

Na przykład wyrażenie-inicjatora-rekordu oblicza wyrażenie podrzędne dla każdego pola ze zmodyfikowanym środowiskiem. Zmodyfikowane środowisko zawiera zmienną dla każdego pola rekordu, z wyjątkiem inicjowanego. Uwzględnienie innych pól rekordu pozwala na to, aby pola zależały od wartości pól. Na przykład:

[  
    x = 1,          // environment: y, z 
    y = 2,          // environment: x, z 
    z = x + y       // environment: x, y
] 

Podobnie wyrażenie let oblicza wyrażenie podrzędne dla każdej zmiennej ze środowiskiem zawierającym każdą ze zmiennych let z wyjątkiem inicjowanego wyrażenia. Wyrażenie let oblicza wyrażenie następujące w pliku za pomocą środowiska zawierającego wszystkie zmienne:

let 

    x = 1,          // environment: y, z 
    y = 2,          // environment: x, z 
    z = x + y       // environment: x, y
in
    x + y + z       // environment: x, y, z

(Okazuje się, że zarówno wyrażenie-inicjator-rekordu , jak i wyrażenie-let faktycznie definiują dwa środowiska, z których jedna zawiera inicjowaną zmienną. Jest to przydatne w przypadku zaawansowanych definicji cyklicznych i opisano je w temacie Odwołania do identyfikatorów .

Aby utworzyć środowiska dla wyrażeń podrzędnych, nowe zmienne są "scalane" ze zmiennymi w środowisku nadrzędnym. W poniższym przykładzie przedstawiono środowiska dla zagnieżdżonych rekordów:

[
    a = 
    [ 

        x = 1,      // environment: b, y, z 
        y = 2,      // environment: b, x, z 
        z = x + y   // environment: b, x, y 
    ], 
    b = 3           // environment: a
]  

Poniższy przykład przedstawia środowiska dla rekordu zagnieżdżonego w ramach let:

Let
    a =
    [
        x = 1,       // environment: b, y, z 
        y = 2,       // environment: b, x, z 
        z = x + y    // environment: b, x, y 
    ], 
    b = 3            // environment: a 
in 
    a[z] + b         // environment: a, b

Scalanie zmiennych ze środowiskiem może powodować konflikt między zmiennymi (ponieważ każda zmienna w środowisku musi mieć unikatową nazwę). Konflikt jest rozwiązywany w następujący sposób: jeśli nazwa nowej zmiennej scalonej jest taka sama jak istniejąca zmienna w środowisku nadrzędnym, nowa zmienna będzie mieć pierwszeństwo w nowym środowisku. W poniższym przykładzie zmienna x wewnętrzna (bardziej głęboko zagnieżdżona) będzie mieć pierwszeństwo przed zmienną xzewnętrzną .

[
    a =
    [ 
        x = 1,       // environment: b, x (outer), y, z 
        y = 2,       // environment: b, x (inner), z 
        z = x + y    // environment: b, x (inner), y 
    ], 
    b = 3,           // environment: a, x (outer) 
    x = 4            // environment: a, b
]  

Odwołania do identyfikatorów

Odwołanie-identyfikator służy do odwoływania się do zmiennej w środowisku.

wyrażenie-identyfikatora:
      odwołanie do identyfikatora
odwołanie do identyfikatora:
      wyłączność-identyfikator-odwołanie
      odwołanie do identyfikatora inkluzywnego

Najprostszą formą odwołania do identyfikatora jest odwołanie-wyłączny-identyfikator:

wyłączność-identyfikator-odwołanie:
      identyfikator

Jest to błąd , który dotyczy wyłącznie odwołania do identyfikatora, aby odwoływać się do zmiennej, która nie jest częścią środowiska wyrażenia, w ramach którego pojawia się identyfikator.

Jest to błąd , aby odwoływać się do identyfikatora, który jest obecnie inicjowany, jeśli przywoływany identyfikator jest zdefiniowany wewnątrz wyrażenia-inicjatora rekordu lub wyrażenia let. Zamiast tego można użyć odwołania inkluzywnego identyfikatora w celu uzyskania dostępu do środowiska, które zawiera inicjowany identyfikator. Jeśli odwołanie do inkluzywnego identyfikatora jest używane w jakiejkolwiek innej sytuacji, jest to równoważne z odwołaniem wyłącznie do identyfikatora.

inkluzywne odwołanie do identyfikatora:
      @ identyfikator

Jest to przydatne podczas definiowania funkcji cyklicznych, ponieważ nazwa funkcji zwykle nie byłaby w zakresie.

[ 
    Factorial = (n) =>
        if n <= 1 then
            1
        else
            n * @Factorial(n - 1),  // @ is scoping operator

    x = Factorial(5) 
]

Podobnie jak w przypadku wyrażenia-inicjatora-rekordu, w wyrażeniu let można użyć inkluzywnego odwołania do identyfikatora w celu uzyskania dostępu do środowiska zawierającego inicjowany identyfikator.

Kolejność obliczania

Rozważ następujące wyrażenie, które inicjuje rekord:

[ 
    C = A + B, 
    A = 1 + 1, 
    B = 2 + 2 
]

Po obliczeniu to wyrażenie generuje następującą wartość rekordu:

[ 
    C = 6, 
    A = 2, 
    B = 4 
]

Wyrażenie stwierdza, że aby wykonać A + B obliczenia dla pola C, wartości pola i pola A B muszą być znane. Jest to przykład porządkowania zależności obliczeń udostępnianych przez wyrażenie. Ewaluator języka M przestrzega kolejności zależności dostarczonej przez wyrażenia, ale jest wolny do wykonywania pozostałych obliczeń w dowolnej wybranej kolejności. Na przykład kolejność obliczeń może być:

A = 1 + 1 
B = 2 + 2 
C = A + B

Lub może to być:

B = 2 + 2 
A = 1 + 1 
C = A + B

Lub, ponieważ A i B nie zależą od siebie, można je obliczyć jednocześnie:

    B = 2 + 2 równoczesnie z A = 1 + 1
    C = A + B

Efekty uboczne

Zezwalanie ewaluatorowi wyrażeń na automatyczne obliczanie kolejności obliczeń w przypadkach, gdy nie ma jawnych zależności określonych przez wyrażenie, jest prostym i zaawansowanym modelem obliczeniowym.

Jednak polega ona na możliwości zmiany kolejności obliczeń. Ponieważ wyrażenia mogą wywoływać funkcje, a te funkcje mogą obserwować stan zewnętrzny dla wyrażenia przez wydawanie zapytań zewnętrznych, można utworzyć scenariusz, w którym kolejność obliczeń ma znaczenie, ale nie jest przechwytywana w częściowej kolejności wyrażenia. Na przykład funkcja może odczytywać zawartość pliku. Jeśli ta funkcja jest wywoływana wielokrotnie, można zaobserwować zewnętrzne zmiany w tym pliku i w związku z tym zmiana kolejności może spowodować zauważalne różnice w zachowaniu programu. W zależności od takiej obserwowanej kolejności oceny dla poprawności wyrażenia M powoduje zależność od konkretnych wyborów implementacji, które mogą się różnić od jednego ewaluatora do następnego, a nawet mogą się różnić od tego samego ewaluatora w różnych okolicznościach.

Niezmienność

Po obliczeniu wartości jest niezmienna, co oznacza, że nie można jej już zmienić. Upraszcza to model oceny wyrażenia i ułatwia wnioskowanie o wyniku, ponieważ nie można zmienić wartości po jej użyciu do oceny kolejnej części wyrażenia. Na przykład pole rekordu jest obliczane tylko w razie potrzeby. Jednak po obliczeniu pozostaje on stały dla okresu istnienia rekordu. Nawet jeśli próba obliczenia pola zgłosiła błąd, ten sam błąd zostanie zgłoszony ponownie podczas każdej próby uzyskania dostępu do tego pola rekordu.

Ważnym wyjątkiem od niezmiennej reguły obliczeniowej jest lista, tabela i wartości binarne, które mają semantyka przesyłania strumieniowego. Semantyka przesyłania strumieniowego umożliwia M przekształcanie zestawów danych, które nie mieszczą się w pamięci jednocześnie. W przypadku przesyłania strumieniowego wartości zwracane podczas wyliczania danej tabeli, listy lub wartości binarnej są generowane na żądanie przy każdym żądaniu. Ponieważ wyrażenia definiujące wyliczone wartości są obliczane za każdym razem, gdy są wyliczane, dane wyjściowe, które tworzą, mogą być różne w wielu wyliczeniach. Nie oznacza to, że wiele wyliczeń zawsze powoduje różne wartości, ponieważ mogą być różne, jeśli używane źródło danych lub logika języka M nie jest deterministyczne.

Należy również pamiętać, że aplikacja funkcji nie jest taka sama jak konstrukcja wartości. Funkcje biblioteki mogą uwidaczniać stan zewnętrzny (np. bieżący czas lub wyniki zapytania względem bazy danych, która ewoluuje wraz z upływem czasu), co sprawia, że nie są deterministyczne. Chociaż funkcje zdefiniowane w języku M nie będą, w związku z tym, uwidaczniają takie niedeterministyczne zachowanie, mogą, jeśli są zdefiniowane w celu wywoływania innych funkcji, które nie są deterministyczne.

Ostatnim źródłem niedeterminizmu w języku M są błędy. Błędy zatrzymują oceny po ich wystąpieniu (do poziomu, na którym są obsługiwane przez wyrażenie try). Zwykle nie można zaobserwować, czy a + b przyczyną oceny było b wcześniejszea, czy b wcześniejsze a (ignorowanie współbieżności w tym miejscu dla uproszczenia). Jeśli jednak wyrażenie podrzędne, które zostało ocenione jako pierwsze, zgłasza błąd, można określić, które z dwóch wyrażeń zostało obliczone jako pierwsze.