FREE BOOK

Chapter 1 - Introduction to "M"

Posted by Addison Wesley Free Book | General November 03, 2009
The "Oslo" Modeling Language (M) is a modern, declarative language for working with data. M lets users write down how they want to structure and query their data using a convenient textual syntax that is convenient to both author and read.

1.2.4 Declaring Fields

Fields are named units of storage that hold values. M allows you to initialize the value of a .eld as part of an entity initializer. However, M does not specify any mechanism for changing the value of a .eld once it is initialized. In M, we assume that any changes to .eld values happen outside the scope of M.

A .eld declaration can indicate that there is a default value for the .eld. Field declarations that have a default value do not require conformant entities to have a corresponding .eld speci.ed. (We sometimes call such .eld declarations optional .elds.) For example, consider this type de.nition:

type Point3d {
X : Number;
Y : Number;
Z = -1 : Number; // default value of negative one
}

Because the Z .eld has a default value, the following type test will succeed:

{ X = 100, Y = 200 } in Point3d

Moreover, if we apply a type ascription operator to the value:

({ X = 100, Y = 200 } : Point3d)

we can now access the Z .eld like this:

({ X = 100, Y = 200 } : Point3d).Z

This expression will yield the value -1.

If a .eld declaration does not have a corresponding default value, conformant entities must specify a value for that .eld. Default values are typically written down using the explicit syntax shown for the Z .eld of Point3d. If the type of a .eld is either nullable or a zero-to-many collection, then there is an implicit default value for the declaring .eld of null for optional and {} for the collection. For example, consider this type:

type PointND {
X : Number;
Y : Number;
Z : Number?; // Z is optional
BeyondZ : Number*; // BeyondZ is optional too
}

Again, the following type test will succeed:

{ X = 100, Y = 200 } in PointND

and ascribing the PointND to the value will allow us to get these defaults:

({ X = 100, Y = 200 } : PointND).Z == null
({ X = 100, Y = 200 } : PointND).BeyondZ == { }

The choice of using a nullable type versus an explicit default value to model optional .elds typically comes down to style.

1.2.5 Declaring Computed Values

Calculated values are named expressions whose values are computed rather than stored. Here's an example of a type that declares a computed value, IsHigh:

type PointPlus {
X : Number;
Y : Number;
// a computed value
IsHigh() : Logical { Y > 0 }
}

Note that unlike .eld declarations which end in a semicolon, computed value declarations end with the expression surrounded by braces.

Like .eld declarations, a computed value declaration may omit the type ascription, as this example does:

type PointPlus {
X : Number;
Y : Number;
// a computed value with no type ascription
InMagicQuadrant() { IsHigh && X > 0 }
IsHigh() : Logical { Y > 0 }
}

When no type is explicitly ascribed to a computed value, M will infer the type automatically based on the declared result type of the underlying expression. In this example, because the logical-and operator used in the expression was declared as returning a Logical, the InMagicQuadrant computed value also is ascribed to yield a Logical value.

The two computed values we just de.ned and used didn't require any additional information to calculate their results other than the entity value itself. A computed value may optionally declare a list of named parameters whose actual values must be speci.ed when using the computed value in an expression. Here's an example of a computed value that requires parameters:

type PointPlus {
X : Number;
Y : Number;
// a computed value that requires a parameter
ithinBounds(radius : Number) : Logical {
X * X + Y * Y <= radius * radius
}
InMagicQuadrant() { IsHigh && X > 0 }
IsHigh() : Logical { Y > 0 }
}

To use this computed value in an expression, you must provide values for the parameters:

({ X = 100, Y = 200 } : PointPlus).WithinBounds(50)

When calculating the value of WithinBounds, M will bind the value 50 to the symbol radius-this will cause the WithinBounds computed value to evaluate to false. It is useful to note that both computed values and default values for .elds are part of the type de.nition, not part of the values that conform to the type. For example, consider these three type de.nitions:

type Point {
X : Number;
Y : Number;
}
type RichPoint {
X : Number;
Y : Number;
Z = -1 : Number;
IsHigh() : Logical { X < Y }
}
type WeirdPoint {
X : Number;
Y : Number;
Z = 42 : Number;
IsHigh() : Logical { false }
}

Because RichPoint and WeirdPoint have only two required .elds (X and Y), we can state the following:

{ X=1, Y=2 } in RichPoint
{ X=1, Y=2 } in WeirdPoint

However, the IsHigh computed value is only available when we ascribe one of these two types to the entity value:

({ X=1, Y=2 } : RichPoint).IsHigh == true
({ X=1, Y=2 } : WeirdPoint).IsHigh == false

Because IsHigh is purely part of the type and not the value, when we chain the ascription like this:

(({ X=1, Y=2 } : RichPoint) : WeirdPoint).IsHigh == false

the outer-most ascription that determines which function is called.

A similar principle is at play with respect to how default values work. Again, the default value is part of the type, not the entity value. When we write the following expression:

({ X=1, Y=2 } : RichPoint).Z == -1

the underlying entity value still only contains two .eld values (1 and 2 for X and Y respectively). Where default values differ from computed values is when we chain ascriptions. Consider this expression:

(({ X=1, Y=2 } : RichPoint) : WeirdPoint).Z == -1

Because the RichPoint ascription is applied .rst, the resultant entity has a .eld named Z whose value is -1; however, there is no storage allocated for the value. (It's part of the type's interpretation of the value.) When we apply the WeirdPoint ascription, we're applying it to the result of the .rst ascription, which does have a .eld named Z, so that value is used to specify the value for Z-the default value speci.ed by WeirdPoint is not needed.

1.2.6 Constraints on Entity Types

Like all types, a constraint may be applied to an entity type using the where operator. Consider the following type de.nition:

type HighPoint {
X : Number;
Y : Number;
} where X < Y;

In this example, all values that conform to the type HighPoint are guaranteed to have an X value that is less than the Y value. That means that the following expressions:

{ X = 100, Y = 200 } in HighPoint
! ({ X = 300, Y = 200 } in HighPoint)

both evaluate to true.

Now consider the following type de.nitions:

type Point {
X : Number;
Y : Number;
}
type Visual {
Opacity : Number;
}
type VisualPoint {|
DotSize : Number;
}

where value in Point && value in Visual;

The third type, VisualPoint, names the set of entity values that have at least the numeric .elds X, Y, Opacity, and DotSize.

Because it is a common desire to factor member declarations into smaller pieces that can be easily composed, M provides explicit syntax support for this. We can rewrite the VisualPoint type de.nition using that syntax:

type VisualPoint : Point, Visual {
DotSize : Number;
}

To be clear, this is just shorthand for the preceding long-hand de.nition that used a constraint expression. Both of these de.nitions are equivalent to this even longerhand de.nition:

type VisualPoint {
X : Number;
Y : Number;
Opacity : Number;
DotSize : Number;
}

Again, the names of the types are just ways to refer to types-the values themselves have no record of the type names used to describe them.

Total Pages : 7 34567

comments