Dela via


Grundläggande begrepp

I det här avsnittet beskrivs grundläggande begrepp som visas i de efterföljande avsnitten.

Värden

En enda datadel kallas för ett värde. I stort sett finns det två allmänna värdekategorier: primitiva värden, som är atomiska och strukturerade värden, som är konstruerade av primitiva värden och andra strukturerade värden. Till exempel värdena

1 
true
3.14159 
"abc"

primitiva eftersom de inte består av andra värden. Å andra sidan är värdena

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

konstrueras med primitiva värden och, när det gäller posten, andra strukturerade värden.

Uttryck

Ett uttryck är en formel som används för att konstruera värden. Ett uttryck kan skapas med hjälp av en mängd olika syntaktiska konstruktioner. Följande är några exempel på uttryck. Varje rad är ett separat uttryck.

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

Den enklaste formen av uttryck, som du ser ovan, är en literal som representerar ett värde.

Mer komplexa uttryck skapas från andra uttryck, som kallas underuttryck. Till exempel:

1 + 2

Uttrycket ovan består faktiskt av tre uttryck. Literalerna 1 och 2 är underuttryck för det överordnade uttrycket 1 + 2.

Att köra algoritmen som definieras av de syntaktiska konstruktioner som används i ett uttryck kallas för att utvärdera uttrycket. Varje typ av uttryck har regler för hur det utvärderas. Till exempel skapar ett literaluttryck som 1 ett konstant värde, medan uttrycket a + b tar de resulterande värdena som skapas genom att utvärdera två andra uttryck (a och b) och lägga ihop dem enligt någon uppsättning regler.

Miljöer och variabler

Uttryck utvärderas i en viss miljö. En miljö är en uppsättning namngivna värden som kallas variabler. Varje variabel i en miljö har ett unikt namn i miljön som kallas identifierare.

Ett toppnivåuttryck (eller rotuttryck) utvärderas i den globala miljön. Den globala miljön tillhandahålls av uttrycksutvärderingen i stället för att bestämmas utifrån innehållet i uttrycket som utvärderas. Innehållet i den globala miljön innehåller standardbiblioteksdefinitionerna och kan påverkas av exporter från avsnitt från vissa dokumentuppsättningar. (För enkelhetens skull förutsätter exemplen i det här avsnittet en tom global miljö. Det vill: det antas att det inte finns något standardbibliotek och att det inte finns några andra avsnittsbaserade definitioner.)

Den miljö som används för att utvärdera ett underuttryck bestäms av det överordnade uttrycket. De flesta överordnade uttryckstyper utvärderar ett underuttryck i samma miljö som de utvärderades i, men vissa använder en annan miljö. Den globala miljön är den överordnade miljö där det globala uttrycket utvärderas.

Till exempel utvärderar postinitieringsuttrycket underuttrycket för varje fält med en modifierad miljö. Den ändrade miljön innehåller en variabel för vart och ett av postfälten, förutom den som initieras. Om du inkluderar de andra fälten i posten kan fälten vara beroende av fältens värden. Till exempel:

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

På samma sätt utvärderar let-expression underuttrycket för varje variabel med en miljö som innehåller var och en av variablerna i let förutom den som initieras. Let-expression utvärderar uttrycket efter in med en miljö som innehåller alla variabler:

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

(Det visar sig att både record-initializer-expression och let-expression faktiskt definierar två miljöer, varav en inkluderar variabeln som initieras. Detta är användbart för avancerade rekursiva definitioner och beskrivs i identifierarreferenser .

För att skapa miljöer för underuttrycken "sammanfogas" de nya variablerna med variablerna i den överordnade miljön. I följande exempel visas miljöer för kapslade poster:

[
    a = 
    [ 

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

I följande exempel visas miljöerna för en post kapslad i en 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

Sammanslagning av variabler med en miljö kan leda till en konflikt mellan variabler (eftersom varje variabel i en miljö måste ha ett unikt namn). Konflikten löses på följande sätt: om namnet på en ny variabel som sammanfogas är detsamma som en befintlig variabel i den överordnade miljön, kommer den nya variabeln att ha företräde i den nya miljön. I följande exempel har variabeln x inre (djupare kapslade) företräde framför den yttre variabeln x.

[
    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
]  

Identifierarreferenser

En identifierarreferens används för att referera till en variabel i en miljö.

identifier-expression:
      identifier-reference
identifier-reference:
      exclusive-identifier-reference
      inclusive-identifier-reference

Den enklaste formen av identifierarreferens är en exklusiv-identifierarreferens:

exclusive-identifier-reference:
      identifierare

Det är ett fel för en exklusiv identifierarreferens att referera till en variabel som inte är en del av miljön för uttrycket som identifieraren visas i.

Det är ett fel för en exklusiv identifierarreferens att referera till en identifierare som för närvarande initieras om den refererade identifieraren definieras i ett postinitieringsuttryck eller let-expression. I stället kan en inkluderande identifierarreferens användas för att få åtkomst till miljön som innehåller identifieraren som initieras. Om en inclusive-identifier-reference används i andra situationer motsvarar den en exklusiv-identifierarreferens.

inclusive-identifier-reference:
      @ identifierare

Detta är användbart när du definierar rekursiva funktioner eftersom namnet på funktionen normalt inte skulle finnas i omfånget.

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

    x = Factorial(5) 
]

Precis som med ett postinitieringsuttryck kan en inclusive-identifier-reference användas i ett let-expression för att komma åt miljön som innehåller identifieraren som initieras.

Utvärderingsordning

Överväg följande uttryck som initierar en post:

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

När det här uttrycket utvärderas genererar det här uttrycket följande postvärde:

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

Uttrycket anger att för att kunna utföra A + B beräkningen för fältet Cmåste värdena för både fält A och fält B vara kända. Det här är ett exempel på en beroendeordning av beräkningar som tillhandahålls av ett uttryck. M-utvärderaren följer beroendeordningen som tillhandahålls av uttryck, men kan utföra de återstående beräkningarna i valfri ordning. Beräkningsordningen kan till exempel vara:

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

Eller så kan det vara:

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

Eller eftersom A och B inte är beroende av varandra kan de beräknas samtidigt:

    B = 2 + 2 samtidigt med A = 1 + 1
    C = A + B

Biverkningar

Att tillåta en uttrycksutvärdering att automatiskt beräkna beräkningsordningen för fall där det inte finns några explicita beroenden som anges av uttrycket är en enkel och kraftfull beräkningsmodell.

Den förlitar sig dock på att kunna ordna om beräkningar. Eftersom uttryck kan anropa funktioner, och dessa funktioner kan observera tillstånd utanför uttrycket genom att utfärda externa frågor, är det möjligt att konstruera ett scenario där beräkningsordningen spelar roll, men inte fångas i den partiella ordningen för uttrycket. En funktion kan till exempel läsa innehållet i en fil. Om den funktionen anropas upprepade gånger kan externa ändringar av filen observeras och därför kan omordning orsaka märkbara skillnader i programmets beteende. Beroende på en sådan observerad utvärderingsordning för ett M-uttrycks korrekthet orsakar ett beroende av vissa implementeringsalternativ som kan variera från en utvärderare till nästa eller till och med variera på samma utvärderare under olika omständigheter.

Oföränderlighet

När ett värde har beräknats är det oföränderligt, vilket innebär att det inte längre kan ändras. Detta förenklar modellen för utvärdering av ett uttryck och gör det enklare att resonera om resultatet eftersom det inte går att ändra ett värde när det har använts för att utvärdera en efterföljande del av uttrycket. Ett postfält beräknas till exempel bara när det behövs. När den har beräknats förblir den dock fast under postens livslängd. Även om försöket att beräkna fältet gav upphov till ett fel genereras samma fel igen vid varje försök att komma åt postfältet.

Ett viktigt undantag till den oföränderliga en gång beräknade regeln gäller för list-, tabell- och binärvärden som har strömmande semantik. Med strömmande semantik kan M transformera datauppsättningar som inte får plats i minnet på en gång. Med direktuppspelning skapas värdena som returneras när en viss tabell, lista eller ett binärt värde räknas upp på begäran varje gång de begärs. Eftersom uttrycken som definierar de uppräknade värdena utvärderas varje gång de räknas upp kan de utdata som de skapar skilja sig mellan flera uppräkningar. Detta innebär inte att flera uppräkningar alltid resulterar i olika värden, bara att de kan vara olika om datakällan eller M-logiken som används är icke-deterministisk.

Observera också att funktionsprogrammet inte är detsamma som värdekonstruktion. Biblioteksfunktioner kan exponera externt tillstånd (till exempel aktuell tid eller resultatet av en fråga mot en databas som utvecklas över tid), vilket gör dem icke-deterministiska. Även om funktioner som definieras i M inte exponerar något sådant icke-deterministiskt beteende, kan de om de definieras för att anropa andra funktioner som inte är deterministiska.

En slutlig källa till icke-determinism i M är fel. Fel stoppar utvärderingar när de inträffar (upp till den nivå där de hanteras av ett try-uttryck). Det är normalt inte observerbart om a + b det orsakade utvärderingen av a före b eller b tidigare a (ignorera samtidighet här för enkelhetens skull). Men om den underuttryck som utvärderades först ger upphov till ett fel kan det fastställas vilket av de två uttrycken som utvärderades först.