Skip to content

Commit

Permalink
Continue Updating Claro Docs to Use Auto-Validated Examples
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonSteving99 committed Feb 22, 2024
1 parent ba686b4 commit 2fced72
Show file tree
Hide file tree
Showing 21 changed files with 360 additions and 25 deletions.
1 change: 1 addition & 0 deletions mdbook_docs/src/SUMMARY.tmpl.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
- [Reusing Module APIs](./metaprogramming/code_reuse/reusing_module_apis/reusing_module_apis.generated_docs.md)
- [Reusing Source Code](./metaprogramming/code_reuse/reusing_source_code/reusing_source_code.generated_docs.md)
- [Abstract Modules](./metaprogramming/code_reuse/abstract_modules/abstract_modules.generated_docs.md)
- [Swapping Dependencies](./metaprogramming/swapping_deps/swapping_deps.generated_docs.md)

---

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# Abstract Modules

## Override Flexibility

# TODO(steving) Make an example of an animal in "WitnessProtection". That doesn't share the name.
4 changes: 4 additions & 0 deletions mdbook_docs/src/metaprogramming/code_reuse/code_reuse.tmpl.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ It will take a bit of conceptual groundwork (unlearning long-held assumptions) t
in exchange, you'll be given some powerful new ways to think about "_what_" a Claro program is.
</div>

Rather than trying to impose specific _code organization_ design patterns on you (e.g. Java trying to force use of
inheritance) Claro instead aims to be flexible enough to allow you full control of using _and encoding_ your own
organizational design patterns (potentially including inheritance if you felt so inclined).

Probably the most fundamental idea that you'll need to internalize to fully understand Claro's larger design in a deep
way is the relationship that a file containing Claro source code actually has with the final resulting program. This is
a subtle point. It's very possible to write a good amount of Claro code without noticing anything unusual in this
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
load("//:rules.bzl", "claro_module", "claro_binary")
load("//mdbook_docs:docs_with_validated_examples.bzl", "doc_with_validated_examples")

exports_files(
["animals_example.claro"],
visibility = ["//mdbook_docs/src/metaprogramming/code_reuse/reusing_source_code:__pkg__"],
)

doc_with_validated_examples(
name = "reusing_module_apis",
doc_template = "reusing_module_apis.tmpl.md",
Expand Down Expand Up @@ -57,6 +62,7 @@ doc_with_validated_examples(
outs = [name],
srcs = [build_file],
cmd = "cat $(SRCS) | sed '/^exports_files/d; s/\\/\\/stdlib.*sounds/:animal_sounds/g; /visibility/d' > $(OUTS)",
visibility = ["//mdbook_docs/src/metaprogramming/code_reuse/reusing_source_code:__pkg__"],
)
for name, build_file in {
"unusable_BUILD_without_exports": "//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis/animals_unusable:BUILD",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# BUILD
load("@claro-lang//:rules.bzl", "claro_module", "claro_binary")
exports_files(["BUILD", "animal.claro_module_api"], visibility = ["//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis:__pkg__"])
exports_files(["BUILD", "animal.claro_module_api"], visibility = ["//mdbook_docs/src/metaprogramming/code_reuse:__subpackages__"])

genrule(
name = "dog_api",
Expand All @@ -15,7 +15,7 @@ claro_module(
deps = {"AnimalSounds": "//stdlib/utils/abstract_modules/example/animal:animal_sounds"},
# This Module is referenced in this Module's API so must be exported.
exports = ["AnimalSounds"],
visibility = ["//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis:__pkg__"],
visibility = ["//mdbook_docs/src/metaprogramming/code_reuse:__subpackages__"],
)

genrule(
Expand All @@ -31,6 +31,6 @@ claro_module(
deps = {"AnimalSounds": "//stdlib/utils/abstract_modules/example/animal:animal_sounds"},
# This Module is referenced in this Module's API so must be exported.
exports = ["AnimalSounds"],
visibility = ["//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis:__pkg__"],
visibility = ["//mdbook_docs/src/metaprogramming/code_reuse:__subpackages__"],
)
# ...
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# animals_example.claro
var happyDog = Dog::create("Milo", true);
var unhappyDog = Dog::create("Fido", false);
var cat = Cat::create("Garfield", "This is worse than Monday morning.");
var animals: [oneof<Cat::State, Dog::State>] = [
Dog::create("Milo", true),
Dog::create("Fido", false),
Cat::create("Garfield", "This is worse than Monday morning.")
];

var animals: [oneof<Cat::State, Dog::State>] = [happyDog, unhappyDog, cat];
for (animal in animals) {
print(AnimalSounds::AnimalSounds::makeNoise(animal));
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ claro_binary(
main_file = "animals_example.claro",
deps = {
"AnimalSounds": ":animal_sounds",
"Cat": ":Cat",
"Dog": ":Dog",
"Cat": ":cat",
"Dog": ":dog",
},
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# BUILD
load(":animals.bzl", "Animal")
exports_files(["animals.bzl", "BUILD"], visibility = ["//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis:__pkg__"])
exports_files(glob(["*"]), visibility = ["//mdbook_docs/src/metaprogramming/code_reuse:__subpackages__"])

Animal(name = "dog", srcs = ["dog.claro"])
Animal(name = "cat", srcs = ["cat.claro"])
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@ Can be implemented multiple times, by more than one Module:

{{EX2}}

**In general, the Build targets declared above will be totally sufficient!** But, to make this example more compelling,
the API definition above declares that any Module implementing the API will export a type that includes a name field,
but may configure its own internal state as it wishes. If you read the API closely, however, you may notice that as
presently defined there'd be no way for any dependent Module to actually interact with this API as defined, because
there's no way to instantiate the `opaque newtype InternalState`[^1]. So, in order to actually make this API useful,
implementing Modules would need to somehow explicitly export some Procedure that gives dependents the ability to
instantiate the `InternalState`. You'll notice that care has been taken to make sure that Claro's API syntax is flexible
enough to allow for multiple APIs to be conceptually (or in this case, literally) concatenated to create one larger API
for a Module to implement. So that's exactly what we'll do here, with each module exporting an additional procedure from
its API to act as a "constructor" for its `opaque` type.
**In general, the Build targets declared above will be totally sufficient!**

## Going Deeper
To make this example more compelling, the API definition above declares that any Module implementing the API will export
a type that includes a name field, but may configure its own internal state as it wishes. If you read the API closely,
however, you may notice that as presently defined there'd be no way for any dependent Module to actually interact with
this API as defined, because there's no way to instantiate the `opaque newtype InternalState`[^1]. So, in order to
actually make this API useful, implementing Modules would need to somehow explicitly export some Procedure that gives
dependents the ability to instantiate the `InternalState`. You'll notice that care has been taken to make sure that
Claro's API syntax is flexible enough to allow for multiple APIs to be conceptually (or in this case, literally)
concatenated to create one larger API for a Module to implement. So that's exactly what we'll do here, with each module
exporting an additional procedure from its API to act as a "constructor" for its `opaque` type.

{{EX3}}

Expand All @@ -48,6 +50,12 @@ And now, importantly, multiple Modules implementing the same API can coexist in

{{EX5}}

<div class="warning">

_Read more about [Dynamic Dispatch](../../../generics/contracts/dynamic_dispatch/dynamic_dispatch.generated_docs.md) if
you're confused how the above Contract Procedure call works._
</div>

## Expressing the Above Build Targets More Concisely

Now, you'd be right to think that the above Build target declarations are extremely verbose. And potentially worse, they
Expand Down Expand Up @@ -81,7 +89,7 @@ like so:

{{EX7}}

And then, the macro can be used from `BUILD` files like so:
And then, the macro can be used from `BUILD` files like so[^2]:

{{EX8}}

Expand All @@ -97,4 +105,8 @@ TODO(steving) Fill out this section describing how this is effectively Dependenc
than depending on heavyweight DI frameworks.

---
[^1]: For more context, read about [Opaque Type's](../../../module_system/module_apis/type_definitions/opaque_types/opaque_types.generated_docs.md).
[^1]: For more context, read about [Opaque Type's](../../../module_system/module_apis/type_definitions/opaque_types/opaque_types.generated_docs.md).

[^2]: In practice, if you want a Bazel Macro to be reusable outside the Build package in which its `.bzl` file is
defined, you'll need to use fully qualified target label. E.g. `//full/path/to:target` rather than `:target`, as the
latter is a "relative" label whose meaning is dependent on the Build package it's used in.
107 changes: 106 additions & 1 deletion mdbook_docs/src/metaprogramming/code_reuse/reusing_source_code/BUILD
Original file line number Diff line number Diff line change
@@ -1,8 +1,113 @@
load("//:rules.bzl", "claro_module", "claro_binary")
load("//mdbook_docs:docs_with_validated_examples.bzl", "doc_with_validated_examples")
load("//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis/macro_animals:animals.bzl", "Animal")

doc_with_validated_examples(
name = "reusing_source_code",
doc_template = "reusing_source_code.tmpl.md",
examples = [],
examples = [
{
"example": "//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis:animals_example.claro",
"deps": {
"AnimalSounds": "//stdlib/utils/abstract_modules/example/animal:animal_sounds",
"Cat": "//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis/animals:cat",
"Dog": "//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis/animals:dog",
}
},
{
"example": "//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis:macro_BUILD_without_exports",
"executable": False,
"codeblock_css_class": "python",
},
{
"example": "cat.claro",
"executable": False,
"codeblock_css_class": "claro",
},
{
"example": ":demo_output",
"executable": False,
},
{
"example": "get_message_with_name.claro",
"hidden_setup": ["setup.claro", "cat-defer.claro"],
"deps": {
"AnimalSounds": "//stdlib/utils/abstract_modules/example/animal:animal_sounds",
},
"append_output": False,
},
{
"example": "example_shared_src_BUILD",
"executable": False,
"codeblock_css_class": "python",
},
{
"example": "cat-defer.claro",
"hidden_setup": ["setup.claro", "get_message_with_name.claro"],
"deps": {
"AnimalSounds": "//stdlib/utils/abstract_modules/example/animal:animal_sounds",
},
"append_output": False,
},
{
"example": "default_animal_sounds_impl.claro",
"hidden_setup": ["setup.claro", "cat-inheritance.claro"],
"deps": {
"AnimalSounds": "//stdlib/utils/abstract_modules/example/animal:animal_sounds",
},
"append_output": False,
},
{
"example": "cat-inheritance.claro",
"hidden_setup": ["setup.claro", "default_animal_sounds_impl.claro"],
"deps": {
"AnimalSounds": "//stdlib/utils/abstract_modules/example/animal:animal_sounds",
},
"append_output": False,
},
{
"example": "example_inheritance_BUILD",
"executable": False,
"codeblock_css_class": "python",
},
],
)

Animal(name = "cat", srcs = ["cat-impl.claro"])
Animal(name = "dog", srcs = ["dog.claro"])

claro_binary(
name = "demo",
main_file = "//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis:animals_example.claro",
deps = {
"AnimalSounds": "//stdlib/utils/abstract_modules/example/animal:animal_sounds",
"Cat": ":cat",
"Dog": ":dog",
}
)
genrule(
name = "demo_output",
outs = ["demo_output.txt"],
srcs = [":demo_deploy.jar"],
cmd = "$(JAVA) -jar $(SRCS) > $(OUTS)",
tools = ["@bazel_tools//tools/jdk:current_java_runtime"],
toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"],
)

genrule(
name = "copy",
outs = ["{0}.claro_module_api".format(name) for name in ["animal", "cat_cons", "dog_cons"]],
srcs = [
"//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis/macro_animals:animal.claro_module_api",
"//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis/macro_animals:cat_cons.claro_module_api",
"//mdbook_docs/src/metaprogramming/code_reuse/reusing_module_apis/macro_animals:dog_cons.claro_module_api",
],
cmd = "cp $(SRCS) $(RULEDIR)",
)

genrule(
name = "cat_impl",
outs = ["cat-impl.claro"],
srcs = ["cat.claro"],
cmd = "cat $(SRCS) | sed 's/\\$$//g' > $(OUTS)",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# cat.claro
$$newtype InternalState : struct { favoriteInsult: string }
$$
implement AnimalSounds::AnimalSounds<State> {
function makeNoise(cat: State) -> string {
var noise = unwrap(unwrap(cat).internal).favoriteInsult;
return getMessageWithName(noise, cat); # Analogous code repeated in dog.claro.
}
}
$$
$$function create(name: string, favoriteInsult: string) -> State {
$$ return State({
$$ name = name,
$$ internal = InternalState({favoriteInsult = favoriteInsult})
$$ });
$$}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# cat.claro
$$newtype InternalState : struct { favoriteInsult: string }
$$
function makeNoiseImpl(cat: State) -> string {
# No more code duplication.
return unwrap(unwrap(cat).internal).favoriteInsult;
}
$$
$$function create(name: string, favoriteInsult: string) -> State {
$$ return State({
$$ name = name,
$$ internal = InternalState({favoriteInsult = favoriteInsult})
$$ });
$$}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# cat.claro
newtype InternalState : struct { favoriteInsult: string }

implement AnimalSounds::AnimalSounds<State> {
function makeNoise(cat: State) -> string {
# Cats are mean, they're going to say mean things no matter what.
var noise = unwrap(unwrap(cat).internal).favoriteInsult;
return "{noise} - says {unwrap(cat).name}"; # Analogous code repeated in dog.claro.
}
}
$$
$$function create(name: string, favoriteInsult: string) -> State {
$$ return State({
$$ name = name,
$$ internal = InternalState({favoriteInsult = favoriteInsult})
$$ });
$$}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# default_animal_sounds_impl.claro
implement AnimalSounds::AnimalSounds<State> {
function makeNoise(state: State) -> string {
return "{makeNoiseImpl(state)} - says {unwrap(state).name}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# dog.claro
newtype InternalState : struct { isHappy: boolean }

implement AnimalSounds::AnimalSounds<State> {
function makeNoise(dog: State) -> string {
var noise: string;
if (unwrap(unwrap(dog).internal).isHappy) {
noise = "Woof!";
} else {
noise = "Grrrr...";
}
return "{noise} - says {unwrap(dog).name}"; # Analogous code in cat.claro.
}
}

function create(name: string, isHappy: boolean) -> State {
return State({
name = name,
internal = InternalState({isHappy = isHappy})
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# BUILD
load(":animals.bzl", "Animal")

# An example of **LITERALLY** reusing code.
Animal(name = "dog", srcs = ["dog.claro", "default_animal_sounds_impl.claro"])
Animal(name = "cat", srcs = ["cat.claro", "default_animal_sounds_impl.claro"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# BUILD
load(":animals.bzl", "Animal")

# An example of **LITERALLY** reusing code.
Animal(name = "dog", srcs = ["dog.claro", "get_message_with_name.claro"])
Animal(name = "cat", srcs = ["cat.claro", "get_message_with_name.claro"])
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# get_message_with_name.claro
function getMessageWithName(message: string, state: State) -> string {
var name = unwrap(state).name; # All animal States have a top-level `name` field.
return "{message} - says {name}";
}
Loading

0 comments on commit 2fced72

Please sign in to comment.