martian-computing

CS 498MC Martian Computing at the University of Illinois at Urbana–Champaign

View the Project on GitHub davis68/martian-computing

Polymorphism

Learning Objectives

Wet Gates

With a dry gate, when you pass in an argument and the code gets compiled, the type system will try to cast to the type specified by the gate; if you pass something that does not fit in the specified type, for example a cord instead of a cell you will get a nest failure. On the other hand, when you pass arguments to a wet gate, their types are preserved and type analysis is done at the definition site of the gate—in our case, fold in hoon.hoon—rather than the call site.

Wet gates are more general than dry gates: you can use a wet gate anywhere a dry gate would work. Wet gates are created with |* bartar. Wet gates behave more like a C-style macro because the argument isn’t converted into the sample type but directly present.

> =dry |=([a=* b=*] [b a])
> =wet |*([a=* b=*] [b a])
> (dry .~1.0 .~~~1.0)
[85.065.399.433.376.081.038.215.121.361.612.832.768 4.607.182.418.800.017.408]
> (wet .~1.0 .~~~1.0)
[.~~~1 .~1]

It is good practice to include a cast in all gates, even wet gates. But in many cases the desired output type depends on the input type. How can we cast appropriately? Often we can cast by example, using the input values themselves. (^+)

Thus for gates. More generally, what about cores? First, a definition of the core type:

[$core p=type q=coil]
+$  coil
  $:
    p=[p=(unit term) q=?(%wet %dry) r=?($gold $iron $lead $zinc)]
    q=type
    r=[p=seminoun q=(map term (pair what (map term hoon))]

(We’ll discuss the +$ lusbuc runes below.)

The full core stores both payload types. The type that describes the payload currently in the core is p. The type that describes the payload the core was compiled with is q.q.

Many functional tools like ++turn and ++rep:by frequently require wet gates in order to yield the intended effect.

Now for dry cores:

Dry polymorphism works by substituting cores. Typically, the the programmer uses one core as an interface definition, then replaces it with another core which does something useful.

In contrast, for wet cores:

For a wet arm, we ask: “suppose this core was actually compiled using p instead of q.q?” Would the Nock formula we generated for q.q actually work for a p payload? … We don’t actually recompile the arm at runtime. We actually run the formula generated for the original payload, q.q.

As a consequence, wet arms and wet cores behave a bit like C++ overloaded operators and templates or Haskell typeclasses. There aren’t fully different implementations though (as in C++ overloading), though, so there’s not a way to (for instance) just make ++add and ++add:rs compatible. (The “Lead Polymorphism” tutorial is very important in this regard.)

One thing to watch is that |% barcen produces dry cores, thus dry arms. To produce a wet core, you need to use |@ barpat instead.

Type Constructors

Gall apps frequently require type definitions, customarily located in a |% barcen core before the |_ barcab door. Frequently these are defined and validated using type constructor arms.

+$ lusbuc is a type constructor arm. It defines a custom type for use by the other arms in the core. It is sometimes used with the $% buccen union definition rune (irregular form ?(%foo %bar)).

Let’s imagine a Pokémon type. We’ll use a simple model of Pokémon with uniform capabilities by species (evolution, kind, whatever) but per-instance attributes for hit points, attack, etc.

+$  pokemon
  $%  id=@uw                    :: unique hash
      name=tape                 :: name of pokemon
      kind=species              :: kind of pokemon
      hp=@ud                    :: current health
      max-hp=@ud                :: normal maximum health
      attack=@ud                :: attack
      defense=@ud               :: defense
      speed=@ud                 :: speed
  ==
+$  species
  $%  name=%tas                 :: pokemon species name as tag
      type=(set type)           :: pokemon types
      moves=(set move)          :: species-specific abilities
  ==

We need to know what a species is, what a type is, and what an move is. Clearly these are limited to be chosen from a particular set of options. For instance, here is a type definition (in the Pokémon type sense):

+$  type  ?(%bug %dragon %ice %fighting %fire %flying %grass %ghost %ground %electric %normal %poison %psychic %rock %water)

Now, we have a bit of a problem, which is that these types really aren’t disjoint. That is, a Pokémon may have more than one of them. So really what we want isn’t a type in the Hoon sense, but a set of tags. (“$? should only be used on types that are disjoint, i.e., which have no values in common.”) The same goes for move.

+$ is a type constructor arm, so it doesn’t make sense to define a particular pokemon until one has already defined a species, which needs type (as above) and move:

+$  type  ?(%bug %dragon %ice %fighting %fire %flying %grass %ghost %ground %electric %normal %poison %psychic %rock %water)
+$  move  ?(%barrage %bite %bind %body-slam %comet-punch %constrict %conversion %cut %crunch %disable %dizzy-punch %double-team %growl)
=/  eevee  [%eevee `(set @tas)`(sy `(list @tas)`~[%normal]) `(set @tas)`(sy `(list @tas)`~[%growl %body-slam])]

(More realistically, we would load these from a configuration file like a JSON. We leave that exercise to the reader for the time being.)

Now we’re ready to build a unique Pokémon instance:

=/  unique-hash  `@uw`(~(raw og eny) 60)
=/  eevee-1  [unique-hash "Eevee" eevee 55 55 55 50 55]

Here is a full implementation of the above as a single generator.

All art by Robert McCall.

Questions

The Trapezoid Rule

The trapezoid rule solves a definite integral. It approximates the area under the curve by a trapezoid or (commonly) a series of trapezoids.

\[\int_a^b f(x) \, dx \approx \sum_{k=1}^N \frac{f(x_{k-1}) + f(x_k)}{2} \Delta x_k = \tfrac{\Delta x}{2}\left(f(x_0) + 2f(x_1)+2f(x_2)+ 2f(x_3)+2f(x_4)+\cdots+2f(x_{N-1}) + f(x_N)\right)\]

Produce a trapezoid-rule integrator which accepts a wet gate (function of a single variable) and a list of points and yields the integral as a floating-point value (@rs or @rd). This tool should be a library. You may use the Dojo or a generator to apply it.

Submit your library as a file trapezint.hoon.