/* Learn Chapel in Y Minutes This primer will go over basic syntax and concepts in Chapel. Last sync with official page: Sun, 08 Mar 2020 08:05:53 +0000 */ // Comments are C-family style // one line comment /* multi-line comment */ /* Basic printing */ write("Hello, "); writeln("World!"); // ``write`` and ``writeln`` can take a list of things to print. // Each thing is printed right next to the others, so include your spacing! writeln("There are ", 3, " commas (\",\") in this line of code"); // Different output channels: use IO; // Required for accessing the alternative output channels stdout.writeln("This goes to standard output, just like plain writeln() does"); stderr.writeln("This goes to standard error"); /* Variables */ // Variables don't have to be explicitly typed as long as // the compiler can figure out the type that it will hold. // 10 is an ``int``, so ``myVar`` is implicitly an ``int`` var myVar = 10; myVar = -10; var mySecondVar = myVar; // ``var anError;`` would be a compile-time error. // We can (and should) explicitly type things. var myThirdVar: real; var myFourthVar: real = -1.234; myThirdVar = myFourthVar; /* Types */ // There are a number of basic types. var myInt: int = -1000; // Signed ints var myUint: uint = 1234; // Unsigned ints var myReal: real = 9.876; // Floating point numbers var myImag: imag = 5.0i; // Imaginary numbers var myCplx: complex = 10 + 9i; // Complex numbers myCplx = myInt + myImag; // Another way to form complex numbers var myBool: bool = false; // Booleans var myStr: string = "Some string..."; // Strings var singleQuoteStr = 'Another string...'; // String literal with single quotes // Some types can have sizes. var my8Int: int(8) = 10; // 8 bit (one byte) sized int; var my64Real: real(64) = 1.516; // 64 bit (8 bytes) sized real // Typecasting. var intFromReal = myReal : int; var intFromReal2: int = myReal : int; // Type aliasing. type chroma = int; // Type of a single hue type RGBColor = 3*chroma; // Type representing a full color var black: RGBColor = (0,0,0); var white: RGBColor = (255, 255, 255); /* Constants and Parameters */ // A ``const`` is a constant, and cannot be changed after set in runtime. const almostPi: real = 22.0/7.0; // A ``param`` is a constant whose value must be known statically at // compile-time. param compileTimeConst: int = 16; // The ``config`` modifier allows values to be set at the command line. // Set with ``--varCmdLineArg=Value`` or ``--varCmdLineArg Value`` at runtime. config var varCmdLineArg: int = -123; config const constCmdLineArg: int = 777; // ``config param`` can be set at compile-time. // Set with ``--set paramCmdLineArg=value`` at compile-time. config param paramCmdLineArg: bool = false; writeln(varCmdLineArg, ", ", constCmdLineArg, ", ", paramCmdLineArg); /* References */ // ``ref`` operates much like a reference in C++. In Chapel, a ``ref`` cannot // be made to alias a variable other than the variable it is initialized with. // Here, ``refToActual`` refers to ``actual``. var actual = 10; ref refToActual = actual; writeln(actual, " == ", refToActual); // prints the same value actual = -123; // modify actual (which refToActual refers to) writeln(actual, " == ", refToActual); // prints the same value refToActual = 99999999; // modify what refToActual refers to (which is actual) writeln(actual, " == ", refToActual); // prints the same value /* Operators */ // Math operators: var a: int, thisInt = 1234, thatInt = 5678; a = thisInt + thatInt; // Addition a = thisInt * thatInt; // Multiplication a = thisInt - thatInt; // Subtraction a = thisInt / thatInt; // Division a = thisInt ** thatInt; // Exponentiation a = thisInt % thatInt; // Remainder (modulo) // Logical operators: var b: bool, thisBool = false, thatBool = true; b = thisBool && thatBool; // Logical and b = thisBool || thatBool; // Logical or b = !thisBool; // Logical negation // Relational operators: b = thisInt > thatInt; // Greater-than b = thisInt >= thatInt; // Greater-than-or-equal-to b = thisInt < a && a <= thatInt; // Less-than, and, less-than-or-equal-to b = thisInt != thatInt; // Not-equal-to b = thisInt == thatInt; // Equal-to // Bitwise operators: a = thisInt << 10; // Left-bit-shift by 10 bits; a = thatInt >> 5; // Right-bit-shift by 5 bits; a = ~thisInt; // Bitwise-negation a = thisInt ^ thatInt; // Bitwise exclusive-or // Compound assignment operators: a += thisInt; // Addition-equals (a = a + thisInt;) a *= thatInt; // Times-equals (a = a * thatInt;) b &&= thatBool; // Logical-and-equals (b = b && thatBool;) a <<= 3; // Left-bit-shift-equals (a = a << 10;) // Unlike other C family languages, there are no // pre/post-increment/decrement operators, such as: // // ``++j``, ``--j``, ``j++``, ``j--`` // Swap operator: var old_this = thisInt; var old_that = thatInt; thisInt <=> thatInt; // Swap the values of thisInt and thatInt writeln((old_this == thatInt) && (old_that == thisInt)); // Operator overloads can also be defined, as we'll see with procedures. /* Tuples */ // Tuples can be of the same type or different types. var sameTup: 2*int = (10, -1); var sameTup2 = (11, -6); var diffTup: (int,real,complex) = (5, 1.928, myCplx); var diffTupe2 = (7, 5.64, 6.0+1.5i); // Tuples can be accessed using square brackets or parentheses, and are // 1-indexed. writeln("(", sameTup[1], ",", sameTup(2), ")"); writeln(diffTup); // Tuples can also be written into. diffTup(1) = -1; // Tuple values can be expanded into their own variables. var (tupInt, tupReal, tupCplx) = diffTup; writeln(diffTup == (tupInt, tupReal, tupCplx)); // They are also useful for writing a list of variables, as is common in debugging. writeln((a,b,thisInt,thatInt,thisBool,thatBool)); /* Control Flow */ // ``if`` - ``then`` - ``else`` works just like any other C-family language. if 10 < 100 then writeln("All is well"); if -1 < 1 then writeln("Continuing to believe reality"); else writeln("Send mathematician, something's wrong"); // You can use parentheses if you prefer. if (10 > 100) { writeln("Universe broken. Please reboot universe."); } if a % 2 == 0 { writeln(a, " is even."); } else { writeln(a, " is odd."); } if a % 3 == 0 { writeln(a, " is even divisible by 3."); } else if a % 3 == 1 { writeln(a, " is divided by 3 with a remainder of 1."); } else { writeln(b, " is divided by 3 with a remainder of 2."); } // Ternary: ``if`` - ``then`` - ``else`` in a statement. var maximum = if thisInt < thatInt then thatInt else thisInt; // ``select`` statements are much like switch statements in other languages. // However, ``select`` statements don't cascade like in C or Java. var inputOption = "anOption"; select inputOption { when "anOption" do writeln("Chose 'anOption'"); when "otherOption" { writeln("Chose 'otherOption'"); writeln("Which has a body"); } otherwise { writeln("Any other Input"); writeln("the otherwise case doesn't need a do if the body is one line"); } } // ``while`` and ``do``-``while`` loops also behave like their C counterparts. var j: int = 1; var jSum: int = 0; while (j <= 1000) { jSum += j; j += 1; } writeln(jSum); do { jSum += j; j += 1; } while (j <= 10000); writeln(jSum); // ``for`` loops are much like those in python in that they iterate over a // range. Ranges (like the ``1..10`` expression below) are a first-class object // in Chapel, and as such can be stored in variables. for i in 1..10 do write(i, ", "); writeln(); var iSum: int = 0; for i in 1..1000 { iSum += i; } writeln(iSum); for x in 1..10 { for y in 1..10 { write((x,y), "\t"); } writeln(); } /* Ranges and Domains */ // For-loops and arrays both use ranges and domains to define an index set that // can be iterated over. Ranges are single dimensional integer indices, while // domains can be multi-dimensional and represent indices of different types. // They are first-class citizen types, and can be assigned into variables. var range1to10: range = 1..10; // 1, 2, 3, ..., 10 var range2to11 = 2..11; // 2, 3, 4, ..., 11 var rangeThisToThat: range = thisInt..thatInt; // using variables var rangeEmpty: range = 100..-100; // this is valid but contains no indices // Ranges can be unbounded. var range1toInf: range(boundedType=BoundedRangeType.boundedLow) = 1.. ; // 1, 2, 3, 4, 5, ... var rangeNegInfTo1 = ..1; // ..., -4, -3, -2, -1, 0, 1 // Ranges can be strided (and reversed) using the ``by`` operator. var range2to10by2: range(stridable=true) = 2..10 by 2; // 2, 4, 6, 8, 10 var reverse2to10by2 = 2..10 by -2; // 10, 8, 6, 4, 2 var trapRange = 10..1 by -1; // Do not be fooled, this is still an empty range writeln("Size of range '", trapRange, "' = ", trapRange.size); // Note: ``range(boundedType= ...)`` and ``range(stridable= ...)`` are only // necessary if we explicitly type the variable. // The end point of a range can be computed by specifying the total size // of the range using the count (``#``) operator. var rangeCount: range = -5..#12; // range from -5 to 6 // Operators can be mixed. var rangeCountBy: range(stridable=true) = -5..#12 by 2; // -5, -3, -1, 1, 3, 5 writeln(rangeCountBy); // Properties of the range can be queried. // In this example, printing the first index, last index, number of indices, // stride, and if 2 is include in the range. writeln((rangeCountBy.first, rangeCountBy.last, rangeCountBy.size, rangeCountBy.stride, rangeCountBy.contains(2))); for i in rangeCountBy { write(i, if i == rangeCountBy.last then "\n" else ", "); } // Rectangular domains are defined using the same range syntax, // but they are required to be bounded (unlike ranges). var domain1to10: domain(1) = {1..10}; // 1D domain from 1..10; var twoDimensions: domain(2) = {-2..2,0..2}; // 2D domain over product of ranges var thirdDim: range = 1..16; var threeDims: domain(3) = {thirdDim, 1..10, 5..10}; // using a range variable // Domains can also be resized var resizedDom = {1..10}; writeln("before, resizedDom = ", resizedDom); resizedDom = {-10..#10}; writeln("after, resizedDom = ", resizedDom); // Indices can be iterated over as tuples. for idx in twoDimensions do write(idx, ", "); writeln(); // These tuples can also be destructured. for (x,y) in twoDimensions { write("(", x, ", ", y, ")", ", "); } writeln(); // Associative domains act like sets. var stringSet: domain(string); // empty set of strings stringSet += "a"; stringSet += "b"; stringSet += "c"; stringSet += "a"; // Redundant add "a" stringSet -= "c"; // Remove "c" writeln(stringSet.sorted()); // Associative domains can also have a literal syntax var intSet = {1, 2, 4, 5, 100}; // Both ranges and domains can be sliced to produce a range or domain with the // intersection of indices. var rangeA = 1.. ; // range from 1 to infinity var rangeB = ..5; // range from negative infinity to 5 var rangeC = rangeA[rangeB]; // resulting range is 1..5 writeln((rangeA, rangeB, rangeC)); var domainA = {1..10, 5..20}; var domainB = {-5..5, 1..10}; var domainC = domainA[domainB]; writeln((domainA, domainB, domainC)); /* Arrays */ // Arrays are similar to those of other languages. // Their sizes are defined using domains that represent their indices. var intArray: [1..10] int; var intArray2: [{1..10}] int; // equivalent // They can be accessed using either brackets or parentheses for i in 1..10 do intArray[i] = -i; writeln(intArray); // We cannot access ``intArray[0]`` because it exists outside // of the index set, ``{1..10}``, we defined it to have. // ``intArray[11]`` is illegal for the same reason. var realDomain: domain(2) = {1..5,1..7}; var realArray: [realDomain] real; var realArray2: [1..5,1..7] real; // equivalent var realArray3: [{1..5,1..7}] real; // equivalent for i in 1..5 { for j in realDomain.dim(2) { // Only use the 2nd dimension of the domain realArray[i,j] = -1.61803 * i + 0.5 * j; // Access using index list var idx: 2*int = (i,j); // Note: 'index' is a keyword realArray[idx] = - realArray[(i,j)]; // Index using tuples } } // Arrays have domains as members, and can be iterated over as normal. for idx in realArray.domain { // Again, idx is a 2*int tuple realArray[idx] = 1 / realArray[idx[1], idx[2]]; // Access by tuple and list } writeln(realArray); // The values of an array can also be iterated directly. var rSum: real = 0; for value in realArray { rSum += value; // Read a value value = rSum; // Write a value } writeln(rSum, "\n", realArray); // Associative arrays (dictionaries) can be created using associative domains. var dictDomain: domain(string) = { "one", "two", "three"}; var dict: [dictDomain] int = ["one" => 1, "two" => 2, "three" => 3]; for key in dictDomain.sorted() do writeln(dict[key]); // Arrays can be assigned to each other in a few different ways. // These arrays will be used in the example. var thisArray : [0..5] int = [0,1,2,3,4,5]; var thatArray : [0..5] int; // First, simply assign one to the other. This copies ``thisArray`` into // ``thatArray``, instead of just creating a reference. Therefore, modifying // ``thisArray`` does not also modify ``thatArray``. thatArray = thisArray; thatArray[1] = -1; writeln((thisArray, thatArray)); // Assign a slice from one array to a slice (of the same size) in the other. thatArray[4..5] = thisArray[1..2]; writeln((thisArray, thatArray)); // Operations can also be promoted to work on arrays. 'thisPlusThat' is also // an array. var thisPlusThat = thisArray + thatArray; writeln(thisPlusThat); // Moving on, arrays and loops can also be expressions, where the loop // body's expression is the result of each iteration. var arrayFromLoop = for i in 1..10 do i; writeln(arrayFromLoop); // An expression can result in nothing, such as when filtering with an if-expression. var evensOrFives = for i in 1..10 do if (i % 2 == 0 || i % 5 == 0) then i; writeln(arrayFromLoop); // Array expressions can also be written with a bracket notation. // Note: this syntax uses the ``forall`` parallel concept discussed later. var evensOrFivesAgain = [i in 1..10] if (i % 2 == 0 || i % 5 == 0) then i; // They can also be written over the values of the array. arrayFromLoop = [value in arrayFromLoop] value + 1; /* Procedures */ // Chapel procedures have similar syntax functions in other languages. proc fibonacci(n : int) : int { if n <= 1 then return n; return fibonacci(n-1) + fibonacci(n-2); } // Input parameters can be untyped to create a generic procedure. proc doublePrint(thing): void { write(thing, " ", thing, "\n"); } // The return type can be inferred, as long as the compiler can figure it out. proc addThree(n) { return n + 3; } doublePrint(addThree(fibonacci(20))); // It is also possible to take a variable number of parameters. proc maxOf(x ...?k) { // x refers to a tuple of one type, with k elements var maximum = x[1]; for i in 2..k do maximum = if maximum < x[i] then x[i] else maximum; return maximum; } writeln(maxOf(1, -10, 189, -9071982, 5, 17, 20001, 42)); // Procedures can have default parameter values, and // the parameters can be named in the call, even out of order. proc defaultsProc(x: int, y: real = 1.2634): (int,real) { return (x,y); } writeln(defaultsProc(10)); writeln(defaultsProc(x=11)); writeln(defaultsProc(x=12, y=5.432)); writeln(defaultsProc(y=9.876, x=13)); // The ``?`` operator is called the query operator, and is used to take // undetermined values like tuple or array sizes and generic types. // For example, taking arrays as parameters. The query operator is used to // determine the domain of ``A``. This is useful for defining the return type, // though it's not required. proc invertArray(A: [?D] int): [D] int{ for a in A do a = -a; return A; } writeln(invertArray(intArray)); // We can query the type of arguments to generic procedures. // Here we define a procedure that takes two arguments of // the same type, yet we don't define what that type is. proc genericProc(arg1 : ?valueType, arg2 : valueType): void { select(valueType) { when int do writeln(arg1, " and ", arg2, " are ints"); when real do writeln(arg1, " and ", arg2, " are reals"); otherwise writeln(arg1, " and ", arg2, " are somethings!"); } } genericProc(1, 2); genericProc(1.2, 2.3); genericProc(1.0+2.0i, 3.0+4.0i); // We can also enforce a form of polymorphism with the ``where`` clause // This allows the compiler to decide which function to use. // Note: That means that all information needs to be known at compile-time. // The param modifier on the arg is used to enforce this constraint. proc whereProc(param N : int): void where (N > 0) { writeln("N is greater than 0"); } proc whereProc(param N : int): void where (N < 0) { writeln("N is less than 0"); } whereProc(10); whereProc(-1); // ``whereProc(0)`` would result in a compiler error because there // are no functions that satisfy the ``where`` clause's condition. // We could have defined a ``whereProc`` without a ``where`` clause // that would then have served as a catch all for all the other cases // (of which there is only one). // ``where`` clauses can also be used to constrain based on argument type. proc whereType(x: ?t) where t == int { writeln("Inside 'int' version of 'whereType': ", x); } proc whereType(x: ?t) { writeln("Inside general version of 'whereType': ", x); } whereType(42); whereType("hello"); /* Intents */ /* Intent modifiers on the arguments convey how those arguments are passed to the procedure. * in: copy arg in, but not out * out: copy arg out, but not in * inout: copy arg in, copy arg out * ref: pass arg by reference */ proc intentsProc(in inarg, out outarg, inout inoutarg, ref refarg) { writeln("Inside Before: ", (inarg, outarg, inoutarg, refarg)); inarg = inarg + 100; outarg = outarg + 100; inoutarg = inoutarg + 100; refarg = refarg + 100; writeln("Inside After: ", (inarg, outarg, inoutarg, refarg)); } var inVar: int = 1; var outVar: int = 2; var inoutVar: int = 3; var refVar: int = 4; writeln("Outside Before: ", (inVar, outVar, inoutVar, refVar)); intentsProc(inVar, outVar, inoutVar, refVar); writeln("Outside After: ", (inVar, outVar, inoutVar, refVar)); // Similarly, we can define intents on the return type. // ``refElement`` returns a reference to an element of array. // This makes more practical sense for class methods where references to // elements in a data-structure are returned via a method or iterator. proc refElement(array : [?D] ?T, idx) ref : T { return array[idx]; } var myChangingArray : [1..5] int = [1,2,3,4,5]; writeln(myChangingArray); ref refToElem = refElement(myChangingArray, 5); // store reference to element in ref variable writeln(refToElem); refToElem = -2; // modify reference which modifies actual value in array writeln(refToElem); writeln(myChangingArray); /* Operator Definitions */ // Chapel allows for operators to be overloaded. // We can define the unary operators: // ``+ - ! ~`` // and the binary operators: // ``+ - * / % ** == <= >= < > << >> & | ˆ by`` // ``+= -= *= /= %= **= &= |= ˆ= <<= >>= <=>`` // Boolean exclusive or operator. proc ^(left : bool, right : bool): bool { return (left || right) && !(left && right); } writeln(true ^ true); writeln(false ^ true); writeln(true ^ false); writeln(false ^ false); // Define a ``*`` operator on any two types that returns a tuple of those types. proc *(left : ?ltype, right : ?rtype): (ltype, rtype) { writeln("\tIn our '*' overload!"); return (left, right); } writeln(1 * "a"); // Uses our ``*`` operator. writeln(1 * 2); // Uses the default ``*`` operator. // Note: You could break everything if you get careless with your overloads. // This here will break everything. Don't do it. /* proc +(left: int, right: int): int { return left - right; } */ /* Iterators */ // Iterators are sisters to the procedure, and almost everything about // procedures also applies to iterators. However, instead of returning a single // value, iterators may yield multiple values to a loop. // // This is useful when a complicated set or order of iterations is needed, as // it allows the code defining the iterations to be separate from the loop // body. iter oddsThenEvens(N: int): int { for i in 1..N by 2 do yield i; // yield values instead of returning. for i in 2..N by 2 do yield i; } for i in oddsThenEvens(10) do write(i, ", "); writeln(); // Iterators can also yield conditionally, the result of which can be nothing iter absolutelyNothing(N): int { for i in 1..N { if N < i { // Always false yield i; // Yield statement never happens } } } for i in absolutelyNothing(10) { writeln("Woa there! absolutelyNothing yielded ", i); } // We can zipper together two or more iterators (who have the same number // of iterations) using ``zip()`` to create a single zipped iterator, where each // iteration of the zipped iterator yields a tuple of one value yielded // from each iterator. for (positive, negative) in zip(1..5, -5..-1) do writeln((positive, negative)); // Zipper iteration is quite important in the assignment of arrays, // slices of arrays, and array/loop expressions. var fromThatArray : [1..#5] int = [1,2,3,4,5]; var toThisArray : [100..#5] int; // Some zipper operations implement other operations. // The first statement and the loop are equivalent. toThisArray = fromThatArray; for (i,j) in zip(toThisArray.domain, fromThatArray.domain) { toThisArray[i] = fromThatArray[j]; } // These two chunks are also equivalent. toThisArray = [j in -100..#5] j; writeln(toThisArray); for (i, j) in zip(toThisArray.domain, -100..#5) { toThisArray[i] = j; } writeln(toThisArray); // This is very important in understanding why this statement exhibits a runtime error. /* var iterArray : [1..10] int = [i in 1..10] if (i % 2 == 1) then i; */ // Even though the domain of the array and the loop-expression are // the same size, the body of the expression can be thought of as an iterator. // Because iterators can yield nothing, that iterator yields a different number // of things than the domain of the array or loop, which is not allowed. /* Classes */ // Classes are similar to those in C++ and Java, allocated on the heap. class MyClass { // Member variables var memberInt : int; var memberBool : bool = true; // By default, any class that doesn't define an initializer gets a // compiler-generated initializer, with one argument per field and // the field's initial value as the argument's default value. // Alternatively, the user can define initializers manually as shown // in the following commented-out routine: // /* // proc init(val : real) { // this.memberInt = ceil(val): int; // } */ // Explicitly defined deinitializer. // If we did not write one, we would get the compiler-generated deinitializer, // which has an empty body. proc deinit() { writeln("MyClass deinitializer called ", (this.memberInt, this.memberBool)); } // Class methods. proc setMemberInt(val: int) { this.memberInt = val; } proc setMemberBool(val: bool) { this.memberBool = val; } proc getMemberInt(): int{ return this.memberInt; } proc getMemberBool(): bool { return this.memberBool; } } // end MyClass // Call compiler-generated initializer, using default value for memberBool. { var myObject = new owned MyClass(10); myObject = new owned MyClass(memberInt = 10); // Equivalent writeln(myObject.getMemberInt()); // Same, but provide a memberBool value explicitly. var myDiffObject = new owned MyClass(-1, true); myDiffObject = new owned MyClass(memberInt = -1, memberBool = true); // Equivalent writeln(myDiffObject); // Similar, but rely on the default value of memberInt, passing in memberBool. var myThirdObject = new owned MyClass(memberBool = true); writeln(myThirdObject); // If the user-defined initializer above had been uncommented, we could // make the following calls: // /* // var myOtherObject = new MyClass(1.95); // myOtherObject = new MyClass(val = 1.95); // writeln(myOtherObject.getMemberInt()); */ // We can define an operator on our class as well, but // the definition has to be outside the class definition. proc +(A : MyClass, B : MyClass) : owned MyClass { return new owned MyClass(memberInt = A.getMemberInt() + B.getMemberInt(), memberBool = A.getMemberBool() || B.getMemberBool()); } var plusObject = myObject + myDiffObject; writeln(plusObject); // Destruction of an object: calls the deinit() routine and frees its memory. // ``unmanaged`` variables should have ``delete`` called on them. // ``owned`` variables are destroyed when they go out of scope. } // Classes can inherit from one or more parent classes class MyChildClass : MyClass { var memberComplex: complex; } // Here's an example of generic classes. class GenericClass { type classType; var classDomain: domain(1); var classArray: [classDomain] classType; // Explicit initializer. proc init(type classType, elements : int) { this.classType = classType; this.classDomain = {1..elements}; // all generic and const fields must be initialized in "phase 1" prior // to a call to the superclass initializer. } // Copy-style initializer. // Note: We include a type argument whose default is the type of the first // argument. This lets our initializer copy classes of different // types and cast on the fly. proc init(other : GenericClass(?), type classType = other.classType) { this.classType = classType; this.classDomain = other.classDomain; this.classArray = for o in other do o: classType; // copy and cast } // Define bracket notation on a GenericClass // object so it can behave like a normal array // i.e. ``objVar[i]`` or ``objVar(i)`` proc this(i : int) ref : classType { return this.classArray[i]; } // Define an implicit iterator for the class // to yield values from the array to a loop // i.e. ``for i in objVar do ...`` iter these() ref : classType { for i in this.classDomain do yield this[i]; } } // end GenericClass // Allocate an owned instance of our class var realList = new owned GenericClass(real, 10); // We can assign to the member array of the object using the bracket // notation that we defined. for i in realList.classDomain do realList[i] = i + 1.0; // We can iterate over the values in our list with the iterator // we defined. for value in realList do write(value, ", "); writeln(); // Make a copy of realList using the copy initializer. var copyList = new owned GenericClass(realList); for value in copyList do write(value, ", "); writeln(); // Make a copy of realList and change the type, also using the copy initializer. var copyNewTypeList = new owned GenericClass(realList, int); for value in copyNewTypeList do write(value, ", "); writeln(); /* Modules */ // Modules are Chapel's way of managing name spaces. // The files containing these modules do not need to be named after the modules // (as in Java), but files implicitly name modules. // For example, this file implicitly names the ``learnChapelInYMinutes`` module module OurModule { // We can use modules inside of other modules. // Time is one of the standard modules. use Time; // We'll use this procedure in the parallelism section. proc countdown(seconds: int) { for i in 1..seconds by -1 { writeln(i); sleep(1); } } // It is possible to create arbitrarily deep module nests. // i.e. submodules of OurModule module ChildModule { proc foo() { writeln("ChildModule.foo()"); } } module SiblingModule { proc foo() { writeln("SiblingModule.foo()"); } } } // end OurModule // Using ``OurModule`` also uses all the modules it uses. // Since ``OurModule`` uses ``Time``, we also use ``Time``. use OurModule; // At this point we have not used ``ChildModule`` or ``SiblingModule`` so // their symbols (i.e. ``foo``) are not available to us. However, the module // names are available, and we can explicitly call ``foo()`` through them. SiblingModule.foo(); OurModule.ChildModule.foo(); // Now we use ``ChildModule``, enabling unqualified calls. use ChildModule; foo(); /* Parallelism */ // In other languages, parallelism is typically done with // complicated libraries and strange class structure hierarchies. // Chapel has it baked right into the language. // We can declare a main procedure, but all the code above main still gets // executed. proc main() { // A ``begin`` statement will spin the body of that statement off // into one new task. // A ``sync`` statement will ensure that the progress of the main // task will not progress until the children have synced back up. sync { begin { // Start of new task's body var a = 0; for i in 1..1000 do a += 1; writeln("Done: ", a); } // End of new tasks body writeln("spun off a task!"); } writeln("Back together"); proc printFibb(n: int) { writeln("fibonacci(",n,") = ", fibonacci(n)); } // A ``cobegin`` statement will spin each statement of the body into one new // task. Notice here that the prints from each statement may happen in any // order. cobegin { printFibb(20); // new task printFibb(10); // new task printFibb(5); // new task { // This is a nested statement body and thus is a single statement // to the parent statement, executed by a single task. writeln("this gets"); writeln("executed as"); writeln("a whole"); } } // A ``coforall`` loop will create a new task for EACH iteration. // Again we see that prints happen in any order. // NOTE: ``coforall`` should be used only for creating tasks! // Using it to iterating over a structure is very a bad idea! var num_tasks = 10; // Number of tasks we want coforall taskID in 1..num_tasks { writeln("Hello from task# ", taskID); } // ``forall`` loops are another parallel loop, but only create a smaller number // of tasks, specifically ``--dataParTasksPerLocale=`` number of tasks. forall i in 1..100 { write(i, ", "); } writeln(); // Here we see that there are sections that are in order, followed by // a section that would not follow (e.g. 1, 2, 3, 7, 8, 9, 4, 5, 6,). // This is because each task is taking on a chunk of the range 1..10 // (1..3, 4..6, or 7..9) doing that chunk serially, but each task happens // in parallel. Your results may depend on your machine and configuration // For both the ``forall`` and ``coforall`` loops, the execution of the // parent task will not continue until all the children sync up. // ``forall`` loops are particularly useful for parallel iteration over arrays. // Lets run an experiment to see how much faster a parallel loop is use Time; // Import the Time module to use Timer objects var timer: Timer; var myBigArray: [{1..4000,1..4000}] real; // Large array we will write into // Serial Experiment: timer.start(); // Start timer for (x,y) in myBigArray.domain { // Serial iteration myBigArray[x,y] = (x:real) / (y:real); } timer.stop(); // Stop timer writeln("Serial: ", timer.elapsed()); // Print elapsed time timer.clear(); // Clear timer for parallel loop // Parallel Experiment: timer.start(); // start timer forall (x,y) in myBigArray.domain { // Parallel iteration myBigArray[x,y] = (x:real) / (y:real); } timer.stop(); // Stop timer writeln("Parallel: ", timer.elapsed()); // Print elapsed time timer.clear(); // You may have noticed that (depending on how many cores you have) // the parallel loop went faster than the serial loop. // The bracket style loop-expression described // much earlier implicitly uses a ``forall`` loop. [val in myBigArray] val = 1 / val; // Parallel operation // Atomic variables, common to many languages, are ones whose operations // occur uninterrupted. Multiple threads can therefore modify atomic // variables and can know that their values are safe. // Chapel atomic variables can be of type ``bool``, ``int``, // ``uint``, and ``real``. var uranium: atomic int; uranium.write(238); // atomically write a variable writeln(uranium.read()); // atomically read a variable // Atomic operations are described as functions, so you can define your own. uranium.sub(3); // atomically subtract a variable writeln(uranium.read()); var replaceWith = 239; var was = uranium.exchange(replaceWith); writeln("uranium was ", was, " but is now ", replaceWith); var isEqualTo = 235; if uranium.compareAndSwap(isEqualTo, replaceWith) { writeln("uranium was equal to ", isEqualTo, " so replaced value with ", replaceWith); } else { writeln("uranium was not equal to ", isEqualTo, " so value stays the same... whatever it was"); } sync { begin { // Reader task writeln("Reader: waiting for uranium to be ", isEqualTo); uranium.waitFor(isEqualTo); writeln("Reader: uranium was set (by someone) to ", isEqualTo); } begin { // Writer task writeln("Writer: will set uranium to the value ", isEqualTo, " in..."); countdown(3); uranium.write(isEqualTo); } } // ``sync`` variables have two states: empty and full. // If you read an empty variable or write a full variable, you are waited // until the variable is full or empty again. var someSyncVar$: sync int; // varName$ is a convention not a law. sync { begin { // Reader task writeln("Reader: waiting to read."); var read_sync = someSyncVar$; writeln("Reader: value is ", read_sync); } begin { // Writer task writeln("Writer: will write in..."); countdown(3); someSyncVar$ = 123; } } // ``single`` vars can only be written once. A read on an unwritten ``single`` // results in a wait, but when the variable has a value it can be read indefinitely. var someSingleVar$: single int; // varName$ is a convention not a law. sync { begin { // Reader task writeln("Reader: waiting to read."); for i in 1..5 { var read_single = someSingleVar$; writeln("Reader: iteration ", i,", and the value is ", read_single); } } begin { // Writer task writeln("Writer: will write in..."); countdown(3); someSingleVar$ = 5; // first and only write ever. } } // Here's an example using atomics and a ``sync`` variable to create a // count-down mutex (also known as a multiplexer). var count: atomic int; // our counter var lock$: sync bool; // the mutex lock count.write(2); // Only let two tasks in at a time. lock$.writeXF(true); // Set lock$ to full (unlocked) // Note: The value doesn't actually matter, just the state // (full:unlocked / empty:locked) // Also, writeXF() fills (F) the sync var regardless of its state (X) coforall task in 1..5 { // Generate tasks // Create a barrier do { lock$; // Read lock$ (wait) } while (count.read() < 1); // Keep waiting until a spot opens up count.sub(1); // decrement the counter lock$.writeXF(true); // Set lock$ to full (signal) // Actual 'work' writeln("Task #", task, " doing work."); sleep(2); count.add(1); // Increment the counter lock$.writeXF(true); // Set lock$ to full (signal) } // We can define the operations ``+ * & | ^ && || min max minloc maxloc`` // over an entire array using scans and reductions. // Reductions apply the operation over the entire array and // result in a scalar value. var listOfValues: [1..10] int = [15,57,354,36,45,15,456,8,678,2]; var sumOfValues = + reduce listOfValues; var maxValue = max reduce listOfValues; // 'max' give just max value // ``maxloc`` gives max value and index of the max value. // Note: We have to zip the array and domain together with the zip iterator. var (theMaxValue, idxOfMax) = maxloc reduce zip(listOfValues, listOfValues.domain); writeln((sumOfValues, maxValue, idxOfMax, listOfValues[idxOfMax])); // Scans apply the operation incrementally and return an array with the // values of the operation at that index as it progressed through the // array from ``array.domain.low`` to ``array.domain.high``. var runningSumOfValues = + scan listOfValues; var maxScan = max scan listOfValues; writeln(runningSumOfValues); writeln(maxScan); } // end main()