Skip to content

Commit

Permalink
Claro Now Fully Supports Modules Exporting Static Values Initialized …
Browse files Browse the repository at this point in the history
…on Program Startup!

This is a massive development as it eliminates a large portion of the use cases that dependency injection frameworks get used for! Now, a module can simply declare that it statically exports a static value, and the entire program can use this singleton, deeply-immutable, non-reassignable value anywhere in the program. As a strong argument for the power of this functionality, I've included an update of the Buggy Buggies demo server that takes advantage of this new functionality to allow the use of a *single* statically initialized `HttpClient<BuggyBuggies>` instance throughout the entire server lifetime, whereas prior to this it was impossible for a Claro Server to maintain any state whatsoever, so clients had to be continuously re-initialized on the fly.

To take this example even further, to demonstrate how Claro's static values can eliminate the need for any dependency injection framework, I could use Bazel to select a static value provider at build time based on some flag like `--dev` or `--prod`. See: https://bazel.build/docs/configurable-attributes.
  • Loading branch information
JasonSteving99 committed Oct 11, 2023
1 parent 5052d29 commit 6557728
Show file tree
Hide file tree
Showing 11 changed files with 75 additions and 46 deletions.
4 changes: 2 additions & 2 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ http_file(
# In some way, it'd be nicer to make use of https://github.com/JasonSteving99/claro-lang/releases/latest/download/..
# instead of naming the release explicitly. However, this would make it impossible to cherrypick an old version and
# rebuild without manual work.
sha256 = "316f6a8399f5b9f5296458b3c9759330fe9b692dac90833a78f709329251db0e",
url = "https://github.com/JasonSteving99/claro-lang/releases/download/v0.1.277/claro-cli-install.tar.gz",
sha256 = "0d4a4eb0083f3b89b9ecfb740acbce3c1addfa5f3233e6693ec66db487f5942d",
url = "https://github.com/JasonSteving99/claro-lang/releases/download/v0.1.278/claro-cli-install.tar.gz",
)

# ClaroDocs is built atop Google's Closure Templates in order to ensure that I'm not generating unsafe html since the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ endpoint_handlers MyDemoService {
root res <- http::getOk200HttpResponseForHtml(@formattedHtml);
node formattedHtml <- Utils::handleBuggyResponseAsHtmlStrParts(@joinResponse)[0];
node joinResponse <- BuggyBuggies::friendsJoin(
# TODO(steving) Migrate to using a singleton client once Claro's Modules can export static (singleton) values.
BuggyBuggies::getClient("https://buggy-buggies.gigalixirapp.com"),
BuggyBuggies::HTTP_CLIENT,
gameId,
handle
);
Expand All @@ -49,8 +48,7 @@ endpoint_handlers MyDemoService {
root res <- http::getOk200HttpResponseForJson(@formattedJson);
node formattedJson <- EndpointHandlers::getBestMovesHandler(@parsedWorld);
node parsedWorld <- BuggyBuggies::worldInfo(
# TODO(steving) Migrate to using a singleton client once Claro's Modules can export static (singleton) values.
BuggyBuggies::getClient("https://buggy-buggies.gigalixirapp.com"),
BuggyBuggies::HTTP_CLIENT,
gameId,
playerSecret
);
Expand All @@ -60,8 +58,7 @@ endpoint_handlers MyDemoService {
root res <- http::getOk200HttpResponseForJson(@formattedJson);
node formattedJson <- Utils::handleBuggyResponseAsHtmlStrParts(@resetResponse)[0];
node resetResponse <- BuggyBuggies::reset(
# TODO(steving) Migrate to using a singleton client once Claro's Modules can export static (singleton) values.
BuggyBuggies::getClient("https://buggy-buggies.gigalixirapp.com"),
BuggyBuggies::HTTP_CLIENT,
gameId,
playerSecret
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

function getClient(url: string) -> HttpClient<BuggyBuggies> {
return http::getHttpClient(url);
provider static_HTTP_CLIENT() -> HttpClient<BuggyBuggies> {
# This now only needs to happen once throughout the entire program.
return http::getHttpClient("https://buggy-buggies.gigalixirapp.com");
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ HttpService BuggyBuggies {
reset: "/api/game/{gameId}/player/{secret}/reset"
}

function getClient(url: string) -> HttpClient<BuggyBuggies>;
# Now there's a single static definition of which client will be used for sending reqs to the Buggy Buggies server.
static HTTP_CLIENT: HttpClient<BuggyBuggies>;

# This type models the JSON response from the Buggy-Buggies service as a Claro type to enable parsing the response into
# something that you can work with programmatically with strict type validation on the edge.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ claro_module(
],
deps = {
"Agent": "//src/java/com/claro/claro_programs/demo_server/buggy_buggies/buggy_agent:buggy_agent",
"BuggyBuggiesClient": "//src/java/com/claro/claro_programs/demo_server/buggy_buggies/buggy_buggies_service:buggy_buggies_client",
"BuggyBuggies": "//src/java/com/claro/claro_programs/demo_server/buggy_buggies/buggy_buggies_service:buggy_buggies_client",
"Utils": "//src/java/com/claro/claro_programs/demo_server/buggy_buggies/utils:utils",
"Pos": "//src/java/com/claro/claro_programs/demo_server/buggy_buggies/data_structures:position",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ graph function gameMoveHandler(gameId: string, playerSecret: string, dir: string
root formattedHtml <- Utils::reduce(@htmlStrParts, "", lambda (accum, curr) -> { return "{accum}{curr}"; });
node htmlStrParts <- Utils::handleBuggyResponseAsHtmlStrParts(@moveBuggyResponse);
node moveBuggyResponse <-
BuggyBuggiesClient::move(
# TODO(steving) Migrate to using a singleton client once Claro's Modules can export static (singleton) values.
BuggyBuggiesClient::getClient("https://buggy-buggies.gigalixirapp.com"),
BuggyBuggies::move(
BuggyBuggies::HTTP_CLIENT,
gameId,
playerSecret,
dir
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
function getBestMovesHandler(buggyResponse: oneof<string, std::Error<string>>) -> string {
match (buggyResponse) {
case _:string ->
var moveResponse = unwrap(BuggyBuggiesClient::getParsedMoveResponse(buggyResponse)).result;
var moveResponse = unwrap(BuggyBuggies::getParsedMoveResponse(buggyResponse)).result;
match (moveResponse) {
case _:BuggyBuggiesClient::MoveResponse ->
case _:BuggyBuggies::MoveResponse ->
Agent::parseWorldMap(moveResponse.result.world)
|> Agent::dijkstra(^, Pos::Position({x = moveResponse.result.you.x, y = moveResponse.result.you.y}))
|> var computedMoves = ["\"{dir}\"" | dir in Agent::movesFromBestPath(^)];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@

graph function startNewGameHandler(handle: string) -> future<string> {
root joinRes <- Utils::handleBuggyResponseAsHtmlStrParts(@moveBuggyResponse)[0];
node moveBuggyResponse <-
BuggyBuggiesClient::hostGame(
# TODO(steving) Migrate to using a singleton client once Claro's Modules can export static (singleton) values.
BuggyBuggiesClient::getClient("https://buggy-buggies.gigalixirapp.com"),
handle
);
node moveBuggyResponse <- BuggyBuggies::hostGame(BuggyBuggies::HTTP_CLIENT, handle);
}
5 changes: 4 additions & 1 deletion src/java/com/claro/claro_programs/modules/test_main.claro
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ TestDep2::addFancyInt(2, 3)
|> myPrint(^, ":multi_hop_module_api via Addition via TestDep2");

# Demonstrate that dep module types with generic type params can actually be instantiated.
print(TestDep2::List([1]));
print(TestDep2::List([1]));

# Demonstrate that static values exported by dep modules can be read.
print("Here's the definition of `TestDep::PI`: {TestDep::PI}");
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,27 @@ private ImmutableList<ContractDefinitionStmt> setupModuleDepBindings(
}

// After having successfully parsed all dep module files and registered all of their exported types, it's time to
// finally register all of their exported procedure signatures.
// finally register their remaining exports.
ImmutableMap<String, SerializedClaroModule> parsedClaroModuleProtos = parsedClaroModuleProtosBuilder.build();

// Register the exported static values.
for (Map.Entry<String, SerializedClaroModule> moduleDep : parsedClaroModuleProtos.entrySet()) {
// Setup the regular exported procedures.
for (SerializedClaroModule.ExportedStaticValue depExportedStaticValue :
moduleDep.getValue().getExportedStaticValuesList()) {
String disambiguatedStaticValueIdentifier =
String.format(
"%s$%s",
depExportedStaticValue.getName(),
moduleDep.getValue().getModuleDescriptor().getUniqueModuleName()
);
scopedHeap.observeStaticIdentifierValue(
disambiguatedStaticValueIdentifier, Types.parseTypeProto(depExportedStaticValue.getType()));
scopedHeap.initializeIdentifier(disambiguatedStaticValueIdentifier);
}
}

// Register the exported procedures.
for (Map.Entry<String, SerializedClaroModule> moduleDep : parsedClaroModuleProtos.entrySet()) {
// Setup the regular exported procedures.
for (SerializedClaroModule.Procedure depExportedProc :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.claro.intermediate_representation.types.Type;
import com.claro.intermediate_representation.types.Types;
import com.claro.internal_static_state.InternalStaticStateUtil;
import com.claro.module_system.module_serialization.proto.SerializedClaroModule;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
Expand Down Expand Up @@ -221,31 +220,20 @@ public Type getValidatedExprType(ScopedHeap scopedHeap) throws ClaroTypeExceptio

@Override
public StringBuilder generateJavaSourceBodyOutput(ScopedHeap scopedHeap) {
scopedHeap.markIdentifierUsed(this.identifier);
if (scopedHeap.getIdentifierData(this.identifier).isStaticValue) {
System.err.println(
"TESTING!!! FOUND STATIC ID REF: " + this.identifier + " " + this.optionalDefiningModuleDisambiguator);
}
ScopedHeap.IdentifierData identifierData = scopedHeap.getIdentifierData(this.identifier);
identifierData.used = true;
return new StringBuilder(
this.alternateCodegenString.orElse(
() -> {
if (scopedHeap.getValidatedIdentifierType(this.identifier).baseType().equals(BaseType.ATOM)
&& scopedHeap.getIdentifierData(this.identifier).isTypeDefinition) {
if (identifierData.type.baseType().equals(BaseType.ATOM) && identifierData.isTypeDefinition) {
// Here it turns out that we actually need to codegen a lookup into the ATOM CACHE.
Optional<SerializedClaroModule.UniqueModuleDescriptor> uniqueModuleDescriptor =
ScopedHeap.getModuleNameFromDisambiguator(
this.optionalDefiningModuleDisambiguator.orElse("$THIS_MODULE$"))
.map(moduleName ->
ScopedHeap.currProgramDepModules.rowMap().get(moduleName)
.values().stream().findFirst().get());
return String.format(
"%s%sATOM_CACHE[%s]",
uniqueModuleDescriptor.map(m -> m.getProjectPackage() + '.').orElse(""),
this.optionalDefiningModuleDisambiguator.map(s -> s + '.').orElseGet(
() -> {
String definingModuleDisambiguator =
ScopedHeap.getDefiningModuleDisambiguator(Optional.empty());
if (definingModuleDisambiguator.isEmpty()) {
return definingModuleDisambiguator;
}
return definingModuleDisambiguator + '.';
}),
"%sATOM_CACHE[%s]",
getFullySpecifiedIdentifierNamespace(),
InternalStaticStateUtil.AtomDefinition_CACHE_INDEX_BY_MODULE_AND_ATOM_NAME.build().get(
this.optionalDefiningModuleDisambiguator.orElseGet(
() -> ScopedHeap.getDefiningModuleDisambiguator(Optional.empty())),
Expand All @@ -256,12 +244,38 @@ public StringBuilder generateJavaSourceBodyOutput(ScopedHeap scopedHeap) {
// Nested comprehension Exprs depend on a synthetic class wrapping the nested identifier refs to
// workaround Java's restriction that all lambda captures must be effectively final.
return "$nestedComprehensionState." + this.identifier;
} else if (identifierData.isStaticValue) {
// To ensure that static values can be referenced across dep module monomorphization boundaries, I need
// to fully specify their namespace at all times.
return getFullySpecifiedIdentifierNamespace() +
this.optionalDefiningModuleDisambiguator
.map(unused -> {
int identifierEndIndex = this.identifier.indexOf('$');
if (identifierEndIndex == -1) {
return this.identifier;
}
return this.identifier.substring(0, identifierEndIndex);
})
.orElse(this.identifier);
}
return this.identifier;
}
).get());
}

// Returns empty only if referencing an identifier from the current compilation unit, and the current compilation unit
// is in fact the top-level claro_binary(). Else, returns the actual UniqueModuleDescriptor to explicitly reference
// currently identifier.
private String getFullySpecifiedIdentifierNamespace() {
return ScopedHeap.getModuleNameFromDisambiguator(
this.optionalDefiningModuleDisambiguator.orElse("$THIS_MODULE$"))
.map(moduleName ->
ScopedHeap.currProgramDepModules.rowMap().get(moduleName)
.values().stream().findFirst().get())
.map(m -> String.format("%s.%s.", m.getProjectPackage(), m.getUniqueModuleName()))
.orElse("");
}

@Override
public Object generateInterpretedOutput(ScopedHeap scopedHeap) {
scopedHeap.markIdentifierUsed(this.identifier);
Expand Down

0 comments on commit 6557728

Please sign in to comment.