Ford Turbo, the current incarnation of Ford, has some exciting features. In retrospect some of those features were the result of irrational exuberance. Many have been in Ford since the beginning.
Ford Pinto is an attempt to take a more measured look at which features Ford actually needs and to support only those features. The overall plan involves passing through Ford Pinto into Ford Focus, which will include performance tuning targeted at the major Ford use cases.
What is Ford actually used for? Building files, and doing mark operations, basically. The primary simplification I'm making to Ford in this revision is to separate those two interfaces. Ford runes other than simple imports have been removed, and there is now a strict layering where building a source file does not depend on mark builds.
I've made several other changes to Ford to ease implementation, maintenance, usage, and performance tuning.
+$ ford
|%
++ able
|%
:: $task: request to ford
::
:: %make: submit a build request
:: desk: filesystem context
:: case: ~ if live, or pinned filesystem version
:: fiz: hoon files to build
:: maz: marks to build into $dais's
:: caz: mark conversion gates to construct
:: %drop: cancel build
::
+$ task
$% $: %make
=desk
case=(unit case:clay)
fiz=(set path)
maz=(set mark)
caz=(set [a=mark b=mark])
==
[%drop ~]
==
:: $gift: response from ford
::
:: %made: build result
:: cass: version at which this result was built
:: fiz: (re)built hoon files
:: maz: (re)built mark $dais's
:: caz: (re)built mark conversion gates
::
+$ gift
$% $: %made
=cass:clay
fiz=(map path (each vase tang))
maz=(map mark (each dais tang))
caz=(map [a=mark b=mark] (each $-(vase vase) tang))
== ==
--
:: $dais: processed mark core
::
+$ dais
$_ ^|
|_ sam=vase
++ bunt sam
++ diff |~(new=_sam [form=form diff=*vase])
++ form *mark
++ join |~([a=vase b=vase] *(unit [form=_form diff=vase]))
++ mash |~([a=[ship desk diff=vase] b=[ship desk diff=vase]] diff=*vase)
++ pact |~(diff=vase sam)
++ vale |~(noun sam)
++ volt |~(noun sam)
--
:: $pile: preprocessed hoon source file
::
+$ pile
$: =mont
gen=hoon
==
:: $mont: imports declaration
::
:: /- imports from /sur
:: /+ imports from /lib
:: /= imports from some other path, wrapping face around result
::
+$ mont
$: sur=(list taut)
lib=(list taut)
raw=(list [=face =path])
==
:: $taut: /lib or /sur file import
::
+$ taut [face=(unit term) pax=term]
--
Ford's interface has been segregated into %make builds that build a Hoon source file, %cast builds that produce gates for casting from one mark to another, and %mark builds that produce $dais data structures representing compiled mark cores.
Note that now, instead of performing mark operations itself, Ford now only produces a conversion gate or $dais data structure for a mark, and the client will perform the operations itself using that result. One pleasant result of this change is that there are no longer any vases as inputs to Ford builds.
Ford runes have been removed due to popular demand and in an attempt to achieve better layering; now a Hoon file can import a /sur file with /-, a /lib file with /+, and an arbitrary Hoon file with /=. We might add in some Ford runes later as we need them; it won't break the model.
A build is scoped to a desk, within the current ship. Resource dependencies can only be expressed within the current beak. Blocking .^
will no longer be supported. Some other mechanism will need to be written to fetch remote Clay hashes -- it could be moved into :hood, for example. I think Dojo should get special syntax for it so an async Clay scry can be used as a Dojo source.
Errors are not referentially transparent, just like in old (pre-Turbo) Ford. If a build doesn't succeed, Ford does not specify whether the build might succeed if run again. No clients ever made use of that information from Ford Turbo, and it's not clear how they would.
Since a %make build asks for a set of files to be built, the response is
a map from filepath to result, where the result is (each vase tang)
.
The first result of a live %make build contains all results, and then
each subsequent result contains only files and results where the
results differ from the previous iteration. This pattern, where the
live result map only contains items that have changed since the previous
iteration of the build, is shared by the %mark and %cast builds.
A %mark build result is a map from mark name to (each dais tang)
. A
$dais is a data structure representing a processed mark coret that
provides enough information to easily bunt the mark, validate a noun to
this mark, or perform a version-control operation: +diff, +join, +mash,
or +pact.
A %cast build result is a map from a pair of marks to a conversion gate that takes in a vase of a datum to convert and outputs a vase of the result. An alternative design would produce a vase of a statically-typed gate, but we think this will be more convenient for clients.
Every Ford build must be able to complete in a single Arvo event. Ford can send moves to Clay repeatedly, but it can't block Clay's commit progress. No remote resources, resources from the future, resources from other vanes, or other zany features are supported.
Another way to express this constraint is to say that if Ford and Clay were part of the same vane (and maybe they should be, eventually), a live Ford build must be able to execute synchronously during one activation of that vane.
Ford Turbo tried pretty hard to be a function of the whole scry namespace, at least internally. This never really panned out, and I don't think it's actually desirable. I think we'll all be better off admitting that Ford should only depend on Clay.
Previous Fords have had to do a fair amount of asynchronous work and caching in order to canonicalize Clay paths in /ren, /mar, /lib, and /sur to replace every fas with a hep. This is too specific and introduces unnecessary complexity.
Ford Pinto will scry Clay for the canonicalized version of a /lib, /sur, or /mar path; Clay will assert on insertion that a new /lib or /sur file's canonical path does not conflict with that of an existing file.
Execution of a build could be deferred into its own event for error containment and provenance. If a once build produces a deterministic error, Ford Pinto captures the error trace and sends it back to the requester as a build failure. If the once build crashes nondeterministically, when Ford is woken up with the error notice, it will produce a failure for that build, containing the error report.
For atomicity, Clay probably should not defer Ford builds. Userspace builds might want to, though, so that the agent that requested the build knows it was Ford that crashed, not its own internals. Gall could potentially intercept Ford requests from agents and defer them to guarantee this property.
If Ford Pinto weighs in at more than 2.000 lines of code, there might be something wrong.
The internals use monadic code for the internal build logic to increase code density and help prevent bugs. Hopefully this does not increase Ford's compile time to an unacceptable level... it seems fine so far.
Ditching renderers and other Ford runes turns Ford from a monadic build system to an applicative build system, which should aid in analyzing and caching builds.
It might be better to allow a
case:clay
in the%once
request. It's more explicitly referentially transparent, and there's no reason Ford shouldn't be able to request older Clay files and build them. This would actually let you request future resources, but Ford would just block until that resource exists. Builds at any revision other than the current one are likely to be very slow, but I have a feeling looking up older data will always be slower than looking up current data, since we'll optimize the common path of current data.