added some examples
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 10 Dec 2021 13:35:31 +0000 (07:35 -0600)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Fri, 10 Dec 2021 13:35:31 +0000 (07:35 -0600)
examples/19_do_blocks.onyx
examples/20_auto_return.onyx

index d1f648b44d86971ed010c60f0d969cdf645bd41e..80631ecb698ecede6365ea2e0fe07d5b907bc807 100644 (file)
@@ -1,9 +1,69 @@
-// Unfinished Example
+// "do"-blocks are a tiny feature found in some programming languages that you might find
+// useful in certain circumstances. They allow you to write a block of code in an expression,
+// and make the type inference system in Onyx even more powerful.
 
-#load "core/std"
+main :: (args: [] cstr) {
+    // Do-blocks are written like so. As you can probabily infer,
+    // the do-block allows you to write any statements you normally
+    // would in a block, except in an expression. As you can also
+    // see to specify the value that the do-block becomes, you
+    // write a "return" statement. The type of thing(s) being returned
+    // dictates the type of the do-block.
+    x := do {
+        a := 10;
+        b := 5;
+        return a * b;
+    };
 
-use package core
+    printf("x is a {} with value {}.\n", typeof x, x);
 
-main :: (args: [] cstr) {
-    
+
+    // There can be multiple return statements, in which case the
+    // first lexical return statement takes precedence over the rest,
+    // and provides the type. This is the same rule as auto-returned-typed
+    // which will be discussed in a later example.
+    y := do {
+        if x <= 10 do return 10.0f;
+        return 40; // This will cast to a f32, because the first return
+                   // was of type f32.
+    };
+
+    printf("y is a {} with value {}.\n", typeof y, y);
+
+
+    // In all honesty, I barely use this feature of the language because
+    // I forget that it exists. It isn't something that is very useful,
+    // and can lead to some code-smells, if do-blocks are used in strange
+    // places, like as arguments. However, during the Advent of Code 2021
+    // I had a real use-case for them and it looked like this:
+
+#if false {
+    left, right := do {
+        parts := string.split(line, #char "|");
+        defer memory.free_slice(^parts);
+        return parts[0], parts[1];
+    };
 }
+
+    // I had a string that I wanted to split into two parts, but wanted to
+    // free the resulting array, as I only needed the first two results.
+    // I could have written it like this instead:
+
+#if false {
+    left, right: str;
+    {
+        parts := string.split(line, #char "|");
+        left, right = parts[0], parts[1];
+        memory.free_slice(^parts);
+    }
+}
+
+    // As you can see, a normal block would have worked as well. Except,
+    // because left and right need to be declared outside that block, I
+    // would have had to explicitly type them. By using a do-block I did
+    // not have to give them a type, and got to free the memory.
+
+}
+
+#load "core/std"
+use package core
index d1f648b44d86971ed010c60f0d969cdf645bd41e..5a52db096b6aaaa787594f2a4866ff2c17c1c3e1 100644 (file)
@@ -1,9 +1,47 @@
-// Unfinished Example
+// Sometimes it can be a lot to explicitly write out the
+// return type of a function. In fact, there are times in
+// Onyx where it is actually impossible to write the return
+// type of a function, as you'll see later.
 
-#load "core/std"
+main :: (args: [] cstr) {
+    // If you don't want to explicitly write the return type
+    // of a function, you can use #auto:
+    f :: (x: i32) -> #auto {
+        return x * 2;
+    }
 
-use package core
+    y := f(10);
+    printf("Y({}): {}\n", typeof y, y);
 
-main :: (args: [] cstr) {
-    
+    // That's really all you need to know... It is just a
+    // convinent shorthand. However, like I said, there are
+    // some cases where it is impossible to actually write
+    // the type so #auto is the only solution. Take this
+    // example. It takes something that is iterable, and
+    // returns an array of whatever that iterable iterates
+    // over. Because the only way to pattern match the V
+    // variable is by using it as a parameter, there is an
+    // inner macro that serves that purpose. It would be
+    // impossible to write the return type of consume, because
+    // it would be "[] V", but where would the V variable
+    // come from? The only way to do this is use an auto-return
+    // type and let the compiler fill it in.
+
+    consume :: (it: $T) -> #auto where iter.Iterable(T) {
+        consume_inner :: macro (it: Iterator($V)) -> [] V {
+            arr: [..] V;
+            for v: it do arr << v;
+            return arr;
+        }
+
+        return consume_inner(iter.as_iterator(it));
+    }
+
+
+    arr := consume(range.{ 10, 1, -1 });
+    println(arr);
 }
+
+#load "core/std"
+use package core
+