on master help sign in
revisions to polymorphism doc
commitf35a326b4f3ec7afe60f8164ffcefb53bdfa0909 authorjoshuareagan <josh@tlon.io> dateThu 13 Dec 2018, 22:07 (8 years ago) parenta3d2d5c
1 file changed+42−138
docs/tutorials/hoon/hoon-ch3-1.udon+42−138
docs/tutorials/hoon/hoon-ch3-1.udon+42−138
@@ -9,7 +9,7 @@
99
1010There are many cases in which one may want to write a function that accepts as input various different types of data. Such a function is said to be [polymorphic](https://en.wikipedia.org/wiki/Polymorphism_(computer_science%29). ("Polymorphism" = "many forms"; so polymorphic functions accept many forms of data as input.)
1111
12−One relatively trivial form of polymorphism involves sub-typing. A gate whose sample is a raw `noun` can accept any Hoon data structure as input -- it's just that the sample will only be treated as a noun. For example, consider the `copy` gate below:
12+One relatively trivial form of polymorphism involves sub-typing. A gate whose sample is a raw `noun` can accept any Hoon data structure as input -- it's just that the sample will only be treated as a raw noun. For example, consider the `copy` gate below:
1313
1414```
1515> =copy |=(a=* [a a])
@@ -24,7 +24,7 @@ One relatively trivial form of polymorphism involves sub-typing. A gate whose s
2424[[72 101 108 108 111 33 0] [72 101 108 108 111 33 0]]
2525```
2626
27−`copy` takes the input, `a`, and returns a cell of `[a a]`. Because every piece of Hoon data is a noun, anything can be taken as input. Every other type in Hoon is a sub-type of `noun`. But the output of `copy` is always going to be just a cell of nouns; the type information of the original input value is not preserved by the gate. Consider the `tape` in the last example -- `"Hello!"` was pretty-printed in the dojo as a raw noun, not as a string. So `copy` is a relatively uninteresting polymorphic function.
27+`copy` takes the input, `a`, and returns a cell of `[a a]`. Because every piece of Hoon data is a noun, anything can be taken as input. Every other type in Hoon is a sub-type of `noun`. But the output of `copy` is always going to be just a cell of nouns; the type information of the original input value is not preserved by the gate. Consider the `tape` in the last example: `"Hello!"` was pretty-printed in the dojo as a raw noun, not as a string. So `copy` is a relatively uninteresting polymorphic function.
2828
2929In this lesson we'll go over Hoon's support for more interesting polymorphic functions.
3030
@@ -32,7 +32,7 @@ In this lesson we'll go over Hoon's support for more interesting polymorphic fun
3232
3333Hoon supports type polymorphism at the core level. That is, each core has certain polymorphic properties that are tracked and maintained as metadata by Hoon's type system. (See `+$ type` of `hoon.hoon`.) These properties determine the extent to which a core can be used as an interface for values of various types.
3434
35−This is rather vaguely put so far. We can clarify by talking more specifically about the different kinds of polymorphism supported in Hoon. There are two kinds of type polymorphism for cores: [genericity](https://en.wikipedia.org/wiki/Generic_programming), to include certain kinds of of [parametric polymorphism](https://en.wikipedia.org/wiki/Parametric_polymorphism); and [variance polymorphism](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science%29), which involves making use of special sub-typing rules for cores.
35+This is rather vaguely put so far. We can clarify by talking more specifically about the kinds of polymorphism supported in Hoon. There are two kinds of type polymorphism for cores: [genericity](https://en.wikipedia.org/wiki/Generic_programming), to include certain kinds of of [parametric polymorphism](https://en.wikipedia.org/wiki/Parametric_polymorphism); and [variance polymorphism](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science%29), which involves making use of special sub-typing rules for cores.
3636
3737If you don't understand these very well, that's okay. We'll explain them. Let's begin with the former.
3838
@@ -40,23 +40,23 @@ If you don't understand these very well, that's okay. We'll explain them. Let'
4040
4141To review briefly, a *core* is a pair of `[battery payload]`. The *battery* contains the various arms of the core (code), and the *payload* contains all the data needed to evaluate those arms correctly (data). Every arm is evaluated with its parent core as the subject. A *gate* (i.e., a Hoon function) is a core with exactly one arm named `$` and with a payload of the form: `[sample context]`. The *sample* is reserved for the function's argument value, and the *context* is any other data needed to evaluate `$` correctly.
4242
43−There are two kinds of cores: *dry* and *wet*. Dry cores, which are more typical, _don't_ make use of *genericity*. Wet cores do. In order to understand generic functions in Hoon, you first have to understand the difference between dry and wet cores.
43+There are two kinds of cores: *dry* and *wet*. Dry cores, which are more typical, _don't_ make use of *genericity*. Wet cores do. In order to understand generic functions in Hoon, you have to understand the difference between dry and wet cores.
4444
45−The cores produced with the `|=`, `|-`, `|%`, and `|_` runes, among others, are all dry. (Dry is more typical.) The cores produced with the `|*` and `|@` runes are wet. (`|*` and `|@` are the wet versions of `|=` and `|%`, respectively.)
45+The cores produced with the `|=`, `|-`, `|%`, and `|_` runes, among others, are all dry. The cores produced with the `|*` and `|@` runes are wet. (`|*` and `|@` are the wet versions of `|=` and `|%`, respectively.)
4646
4747Briefly, the difference between dry and wet cores has to do with how they're treated at compile time, for type inference and type checking. During the type check, the product type of each arm is inferred. For computing a dry arm type, it's assumed that the arm's subject type is of the original parent core. For computing a wet arm type, the parent core type is used, except that the payload value may vary from the original type.
4848
49−Because of this difference, wet cores can be used to make functions that take different input types at different call sites. We'll go over the dry/wet distinction in more detail below and explain why it matters.
49+Because of this, wet gates can be used as functions that take different input types at different call sites. We'll go over the dry/wet distinction in more detail below and explain why it matters.
5050
5151### Dry Cores
5252
53−A core's payload can change its values swapped out for other values. How can we ensure that its arms are still able to run correctly?
53+A core's payload can change from its original value. In fact, this happens in the typical function call -- the default sample is replaced with an input value. How can we ensure that the core's arms are able to run correctly, that the payload type is still appropriate despite whatever changes it has undergone?
5454
55−There is a type check for each arm of a dry core, intended to verify that the arm's parent core has a payload of the appropriate type. In this section we'll describe that type check.
55+There is a type check for each arm of a dry core, intended to verify that the arm's parent core has a payload of the correct type. In this section we'll describe that type check, starting with typical gate usage.
5656
57−When an arm of a dry core is evaluated it takes its parent core as the subject, often with a modified sample _value_. But any change in sample _type_ should be conservative; the modified sample value must be of the same type as the default sample value (or possibly a subtype). When the `$` arm is evaluated it should have a subject of a type it knows how to use.
57+When the `$` arm of a dry gate is evaluated it takes its parent core -- the dry gate itself -- as the subject, often with a modified sample _value_. But any change in sample _type_ should be conservative; the modified sample value must be of the same type as the default sample value (or possibly a subtype). When the `$` arm is evaluated it should have a subject of a type it knows how to use.
5858
59−For example, consider the gate created by `add`, a function in the Hoon standard library. The structure of the `add` gate is as follows:
59+For illustration, consider the gate created by `add`, a function in the Hoon standard library. The structure of the `add` gate is as follows:
6060
6161```
6262 add
@@ -89,12 +89,12 @@ nest-fail
8989
9090This function call attempts to create a modified copy of the `add` gate, replacing the original sample `[0 0]` with `[12 "hello"]`, then evaluate the `$` arm of this modified core. (We ignore the `a` and `b` faces for now.) But this piece of code failed to compile; the type system knew to reject it.
9191
92−How does it know? The type system keeps track of two payload types for each core:
92+How does it know? The type system keeps track of two copies of the payload type for each core:
9393
94−+ The original payload type, i.e., the type of the payload when the core was first created. In the case of `add`, the original sample type is a cell of atoms, `[@ @]`. This original type information is always preserved, regardless of what value type is used for calling `add`.
95−+ The current payload type, i.e., the type of the payload after any substitutions or other changes have been made. In the last `add` call, `(add 12 "hello")`, this would have been a cell whose head is an `@ud`, and whose tail is a `tape`.
94++ The original payload type, i.e., the type of the payload when the core was first created. In the case of `add`, the original sample type is a cell of atoms, `[@ @]`. This original type information is always preserved, regardless of the argument values used to call `add`.
95++ The current payload type, i.e., the type of the payload after any substitutions or other changes have been made. In the last `add` call, `(add 12 "hello")`, the sample would be a cell whose head is an `@ud`, and whose tail is a `tape`.
9696
97−The type check at compile time compares these two types, and if the current payload type doesn't nest under the original payload, the compile fails with a `nest-fail` crash. (E.g., a cell of type `[@ud tape]` doesn't nest under `[@ @]`!) Otherwise, the original payload _type_ is used for evaluation, even though the original sample _value_ has been replaced. So the arm of a dry core is guaranteed to run with exactly the same subject type each time it is evaluated.
97+The type check at compile time compares these two types, and if the current payload type doesn't nest under the original payload, the compile fails with a `nest-fail` crash. (E.g., a cell of type `[@ud tape]` doesn't nest under `[@ @]`.) Otherwise, the original payload _type_ is used for evaluation, even though the original sample _value_ has been replaced. So the arm of a dry core is guaranteed to run with exactly the same subject type each time it is evaluated.
9898
9999The type check for each arm in a dry core can be understood as implementing a version of the ["Liskov substitution principle"](https://en.wikipedia.org/wiki/Liskov_substitution_principle). The arm works for some (originally defined) payload type `P`. Payload type `Q` nests under `P`. Therefore the arm works for `Q` as well, though for type inference purposes the payload is treated as a `P`. The inferred type of the arm is based on the assumption that it's evaluated with a subject type exactly like that of its original parent core -- i.e., whose payload is of type `P`.
100100
@@ -102,7 +102,9 @@ The type check for each arm in a dry core can be understood as implementing a ve
102102
103103The type checking and inference rules for arms are a bit different for wet cores. Whereas the dry arm is understood as having _exactly_ its parent core type for its subject, wet arms can be evaluated with payloads of various types.
104104
105−When a function call occurs on a wet arm, the [call site](https://en.wikipedia.org/wiki/Call_site) argument value type is used as the sample type -- not the original parent core sample type. Correspondingly, the inferred type of a wet arm depends, among other things, on the type of the argument value -- i.e., the modified sample value type of the core, not the original. Thus, the inferred product type typically varies from one call site to another.
105+Let's start again with a typical function call.
106+
107+When a function call occurs on a wet arm, the [call site](https://en.wikipedia.org/wiki/Call_site) argument value type is used as the sample type -- not the original parent core sample type. Correspondingly, the inferred type of a wet arm depends, among other things, on the type of the argument value -- i.e., the modified sample value type of the core, not the original sample. Thus, the inferred product type typically varies from one call site to another.
106108
107109The arms of wet cores must be able to handle subjects of various types -- they are interestingly polymorphic in a way that dry cores cannot be. The Hoon code that defines each wet arm must be [generic](https://en.wikipedia.org/wiki/Generic_programming) with respect to the sample type.
108110
@@ -138,7 +140,7 @@ This gate is dry (as are all cores produced with `|=`), and so the inferred type
138140
139141We see that the type information about the `tape` literal `"Hello!"` is preserved in the last function call, as desired. The original sample value of `wet-copy` is replaced with `"Hello!"`, and the original sample type is ignored in favor of the new sample type, `tape`. So the inferred arm type is a cell of `tape`s, which the dojo pretty-printer handles appropriately.
140142
141−In fact, `wet-copy` can take a Hoon value of any type you like!
143+In fact, `wet-copy` can take a Hoon value of any type you like.
142144
143145For another example, consider the following gate that switches the order of the head and tail of a cell:
144146
@@ -178,7 +180,7 @@ But `switch` can't take just any input:
178180
179181It must take a cell, not an atom. So it's certainly possible to write a wet gate that doesn't take all possible input types.
180182
181−It is good practice to include a cast with 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. For example, we can rewrite `switch` as:
183+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. For example, we can rewrite `switch` as:
182184
183185```
184186> =switch |*([a=* b=*] ^+([b a] [b a]))
@@ -193,7 +195,7 @@ As is the case with dry arms, there is a type-check associated with each wet arm
193195
194196In Chapter 2 we saw how to use the `+*` rune to define an arm that produced a more complex type from one or more simpler types. This rune provides a way of making use of *parametric polymorphism* in Hoon.
195197
196−For example, we have `list`s, `tree`s, and `set`s in Hoon, which are each defined in `hoon.hoon` with `+*` arms. (Take a moment to see for yourself!) Each `+*` arm is followed by an argument definition, surrounded in square brackets `[ ]`. After that subexpression comes another that defines a type, relative to the input value. For example, here is the definition of `list` from `hoon.hoon`:
198+For example, we have `list`s, `tree`s, and `set`s in Hoon, which are each defined in `hoon.hoon` with `+*` arms. (Take a moment to see for yourself.) Each `+*` arm is followed by an argument definition, inside brackets `[ ]`. After that subexpression comes another that defines a type, relative to the input value. For example, here is the definition of `list` from `hoon.hoon`:
197199
198200```
199201+* list [item]
@@ -205,7 +207,7 @@ For example, we have `list`s, `tree`s, and `set`s in Hoon, which are each define
205207 $@(~ [i=item t=(list item)])
206208```
207209
208−The `+*` rune is especially useful for defining [containers](https://en.wikipedia.org/wiki/Container_(abstract_data_type%29) of various kinds. A container is a kind of data structure for a collection of values of one or more given types. Indeed, `list`s, `tree`s, and `set`s are all examples of containers. You can have a `(list @)`, a `(list ^)`, a `(list *)`, and so on. Or a `(tree @)`, a `(tree ^)`, a `(tree *)`, etc. And the same for `set`.
210+The `+*` rune is especially useful for defining [containers](https://en.wikipedia.org/wiki/Container_(abstract_data_type%29) of various kinds. Indeed, `list`s, `tree`s, and `set`s are all examples of containers. You can have a `(list @)`, a `(list ^)`, a `(list *)`, and so on. Or a `(tree @)`, a `(tree ^)`, a `(tree *)`, etc. And the same for `set`.
209211
210212One nice thing about containers defined by `+*` is that they nest in the expected way. Intuitively a `(list @)` should nest under `(list *)`, because `@` nests under `*`. And so it does:
211213
@@ -259,7 +261,7 @@ Let's say you want to write a function that takes a gate as one (or more) of its
259261
260262It's quite useful to be able to pass cores as arguments. But let's think about how to write a function that accepts a core as input. How do we define a sample so that it accepts a core?
261263
262−We _could_ define the sample by example -- i.e., with `$_`, or `_` for short -- using a core. Consider the following:
264+We _could_ define the sample by example -- i.e., with `$_`, or `_` for short -- using a core. Consider the following, in which `mycore` is the example core and `apply` is function that accepts gates:
263265
264266```
265267> =mycore =>([12 13] |=(a=@ +(a)))
@@ -283,27 +285,27 @@ This works, but in fact `apply` is a very brittle function. It can only take as
283285nest-fail
284286```
285287
286−A core created in one place of your code isn't likely to have the same payload as a core produced elsewhere, unless you intentionally define it so that it does. This often isn't practical, so we need another way to indicate that we want a core as input.
288+A core created in one place of your code isn't likely to have the same payload as a core produced elsewhere, unless you intentionally define it so that it does. This generally isn't practical, so we need another way to indicate that we want a core as input.
287289
288−Why did the last example crash? To understand this, you need to understand the the different variance properties a core can have. These properties determine the nesting and type inference rules associated with that core.
290+Why did the last example crash? To understand this, you need to understand the the different variance properties a core can have. These properties determine the nesting and type inference rules associated with each core.
289291
290292The basic question to be answered is: when does one core type nest under another?
291293
292294The battery nesting rules for dry cores are relatively straightforward, and they're the same for all cores, regardless of variance properties. Let `A` and `B` be two cores. The type of `B` nests under the type of `A` only if two conditions are met: (i) the batteries of `A` and `B` have exactly the same tree shape, and (ii) the product type of each `B` arm nests under the product type of the corresponding `A` arm.
293295
294−The payload types of `A` and `B` must also be checked. Certain payload nesting rules apply to all cores, if `B` is to nest under `A`: (i) if `A` is dry (wet) then `B` must also be dry (wet); (ii) the original payload type of `A` must mutually nest with the current payload type of `A` (i.e., these types nest under each other); and (iii) the current payload type of `B` must nest under the original payload type of `B`.
296+The payload types of `A` and `B` must also be checked. Certain payload nesting rules apply to all cores, if `B` is to nest under `A`: (i) if `A` is wet then `B` must also be wet; (ii) the original payload type of `A` must mutually nest with the current payload type of `A` (i.e., these types nest under each other); and (iii) the current payload type of `B` must nest under the original payload type of `B`.
295297
296298### The Four Kinds of Cores: Gold, Iron, Zinc, and Lead
297299
298300But there are different sets of nesting rules for finishing the payload type check, depending on the variance properties of the core.
299301
300−There are four kinds of core variance properties: *gold* (invariant sample), *iron* (contravariant sample), *zinc* (covariant sample), and *lead* (bivariant sample). These properties are tracked by the Hoon type system.
302+There are four kinds of cores: *gold* (invariant payload), *iron* (contravariant sample), *zinc* (covariant sample), and *lead* (bivariant sample). Information about the kind of core is tracked by the Hoon type system.
301303
302−In brief:
304+Their nesting rules in brief:
303305
304−+ If `A` is a gold core, then core `B` nests under it only if `B` is gold and the sample of `B` doesn't vary in type from the sample of `A`. The sample `B` is *invariant* (i.e., neither co- nor contravariant) relative to the sample of an `A` it nests under. Gold cores have a read-write payload.
305−+ If `A` is an iron core, then core `B` nests under it only if `B` is gold or iron and the sample of `A` nests under the sample of `B`. Notice that the nesting direction of the samples is the opposite of the nesting direction of the cores. I.e., the sample types *contravary* with respect to the iron core types. Iron cores have a write-only sample and an opaque context (neither read nor write).
306−+ If `A` is a zinc core, then core `B` nests under it only if `B` is gold or zinc and the sample of `B` nests under the sample of `A`. Notice that the nesting direction of the samples is the same as the nesting direction of the cores. I.e., the sample types *co-vary* with respect to the zinc core types. Zinc cores have a read-only sample and an opaque context.
306++ If `A` is a gold core, then core `B` nests under it only if `B` is gold and the payload of `B` doesn't vary in type from the payload of `A`. The payload `B` is *invariant* (i.e., neither co- nor contravariant) relative to the payload of an `A` it nests under. Gold cores have a read-write payload.
307++ If `A` is an iron core, then core `B` nests under it only if `B` is gold or iron and the sample of `A` nests under the sample of `B`. Notice that the nesting direction of the samples is the opposite of the nesting direction of the cores. I.e., the sample types are *contravariant* with respect to the iron core types. Iron cores have a write-only sample and an opaque context. ("Opaque" means neither read nor write.)
308++ If `A` is a zinc core, then core `B` nests under it only if `B` is gold or zinc and the sample of `B` nests under the sample of `A`. Notice that the nesting direction of the samples is the same as the nesting direction of the cores. I.e., the sample types are *covariant* with respect to the zinc core types. Zinc cores have a read-only sample and an opaque context.
307309+ If `A` is a lead core, then core `B` nests under it without any additional payload check beyond those described in the previous section. It trivially follows that the payload type both co- and contravaries with respect to the lead core types (hence lead cores are *bivariant*). Lead cores have an opaque payload.
308310
309311Let's go through each kind more carefully.
@@ -367,7 +369,7 @@ Iron gates are particularly useful when you want to pass gates (having various p
367369
368370This generator is mostly very simple. Everything about it should be clear except possibly the first line.
369371
370−The first line defines the gate sample as an iron gate and gives it the face `a`. So the function as a whole is for taking some gate, calling it by passing it the value `10`, adding `20` to it, and returning the result. Let's try it out in the dojo:
372+The first line defines the sample as an iron gate and gives it the face `a`. The function as a whole is for taking some gate as input, calling it by passing it the value `10`, adding `20` to it, and returning the result. Let's try it out in the dojo:
371373
372374```
373375> +gatepass |=(a=@ +(a))
@@ -380,7 +382,7 @@ The first line defines the gate sample as an iron gate and gives it the face `a`
38038250
381383```
382384
383−But we still haven't fully explained the first line of the code. In particular, what does `_^|(|=(@ 15))` mean? The inside portion is clear enough -- `|=(@ 15)` produces a normal (i.e., gold) gate that takes an atom and returns `15`. The `^|` rune is used to turn gold gates to iron. (Reverse alchemy!) And the `_` character turns that iron gate value into a structure, i.e. a type. So the whole subexpression means, roughly: "the same type as an iron gate whose sample is an atom, `@`, and whose product is another atom, `@`". The context isn't checked at all. This is good, because that allows us to accept gates defined in drastically different environments. Let's try passing a gate with a different context:
385+But we still haven't fully explained the first line of the code. What does `_^|(|=(@ 15))` mean? The inside portion is clear enough -- `|=(@ 15)` produces a normal (i.e., gold) gate that takes an atom and returns `15`. The `^|` rune is used to turn gold gates to iron. (Reverse alchemy!) And the `_` character turns that iron gate value into a structure, i.e. a type. So the whole subexpression means, roughly: "the same type as an iron gate whose sample is an atom, `@`, and whose product is another atom, `@`". The context isn't checked at all. This is good, because that allows us to accept gates defined and produced in drastically different environments. Let's try passing a gate with a different context:
384386
385387```
386388> +gatepass =>([22 33] |=(a=@ +(a)))
@@ -398,13 +400,13 @@ There's a simpler way to define an iron sample. Revise the first line of `gatep
398400(add b 20)
399401```
400402
401−If you test it you'll find that the generator behaves the same as it did before the edits. The `$-` rune is used to create an iron gate structure, i.e., an iron gate type. The first expression defines the sample type of the iron gate, and the second subexpression defines the gate output type.
403+If you test it you'll find that the generator behaves the same as it did before the edits. The `$-` rune is used to create an iron gate structure, i.e., an iron gate type. The first expression defines the desired sample type, and the second subexpression defines the gate's desired output type.
402404
403405The sample type of an iron gate is contravariant. This means that, when doing a cast with some iron gate, the desired gate must have either the same sample type or a superset.
404406
405407Why is this a useful nesting rule for passing gates?
406408
407−Let's say you're writing a function `F` that takes as input some gate `G`. Let's also say you want `G` to be able to take as input any *mammal*. That's because the code of `F` is going to pass arbitrary *mammals* to `G`, so that `G` needs to know how to handle all *mammals* correctly. You can't pass `F` a gate that only takes *dogs* as input, because `F` might call it with a *cat*. But `F` can accept a gate that takes all *animals* as input, because a gate that can handle any *animal* can handle *any mammal*.
409+Let's say you're writing a function `F` that takes as input some gate `G`. Let's also say you want `G` to be able to take as input any *mammal*. The code of `F` is going to pass arbitrary *mammals* to `G`, so that `G` needs to know how to handle all *mammals* correctly. You can't pass `F` a gate that only takes *dogs* as input, because `F` might call it with a *cat*. But `F` can accept a gate that takes all *animals* as input, because a gate that can handle any *animal* can handle *any mammal*.
408410
409411Iron cores are designed precisely with this purpose in mind. The reason that the sample is write-only is that we want to be able to assume, within function `F`, that the sample of `G` is a *mammal*. But that might not be true when `G` is first passed into `F` -- the default value of `G` could be another *animal*, say, a *lizard*. So we restrict looking into the sample of `G` by making the sample write-only. The illusion is maintained and type safety secured.
410412
@@ -495,11 +497,11 @@ Let's clear up the dojo subject by unbinding `iron-gate`:
495497
496498#### Zinc Cores (Covariant)
497499
498−Zinc cores are like 'inverted' versions of iron cores.
500+Zinc cores are mirror versions of iron cores.
499501
500502Assume that `A` is a zinc core and `B` is some other core. The type of `B` nests under the type of `A` only if the original sample type of `B` nests under the original sample type of `A`. That is, the target core sample type must be equivalent or a subset of the cast core sample type. Furthermore, `B` must be either zinc or gold to nest under `A`.
501503
502−As with iron cores, the context of zinc cores is opaque -- they cannot be written-to or read-from. The sample of a zinc core is read-only. That means, among other things, that zinc cores cannot be used for function calls. Function calls in Hoon involve a change to the sample (the sample is replaced with the argument value), which is disallowed as type-unsafe for zinc cores. (Note: due to a bug this isn't yet enforced properly.)
504+As with iron cores, the context of zinc cores is opaque -- they cannot be written-to or read-from. The sample of a zinc core is read-only. That means, among other things, that zinc cores cannot be used for function calls. Function calls in Hoon involve a change to the sample (the default sample is replaced with the argument value), which is disallowed as type-unsafe for zinc cores.
503505
504506We can illustrate the casting properties of zinc cores with a few examples. The `^&` rune is used to convert gold cores to zinc:
505507
@@ -524,13 +526,16 @@ We can illustrate the casting properties of zinc cores with a few examples. The
524526nest-fail
525527```
526528
527−The first two casts succeed because the right-hand core sample type is either the same or more specific than the laft-hand core sample type. The last one fails because the right-hand sample type is a superset.
529+The first two casts succeed because the right-hand core sample type is either the same or a subset of the left-hand core sample type. The last one fails because the right-hand sample type is a superset.
528530
529−The arms of a zinc core can be computed, and the sample can be read. Let's test this with a zinc gate of our own:
531+Even though you can't function call a zinc core, the arms of a zinc core can be computed and the sample can be read. Let's test this with a zinc gate of our own:
530532
531533```
532534> =zinc-gate ^& |=(a=_22 (add 10 a))
533535
536+> (zinc-gate 12)
537+payload-block
538+
534539> a.zinc-gate
53554022
536541
@@ -538,118 +543,17 @@ The arms of a zinc core can be computed, and the sample can be read. Let's test
53854332
539544```
540545
541−Zinc cores are not as widely used as gold or even iron cores. But they do have one plausible use. Imagine a core that serves effectively as an object, roughly in the object-oriented sense. The arms are methods and 'attributes' are kept as state, somewhere in the sample. We might imagine there being a class of such objects, defined as a type of core with an appropriate identifier in the sample somewhere. It may be that one class of objects is to be understood as a subclass of another. We can enforce this relationship with the nesting rules of zinc cores.
542−
543−An example will clarify. Let's define `animal` and `mammal` as types.
544−
545−```
546−> =mammal ?(%cow %dog %cat)
547−
548−> =animal ?(mammal %lizard %chicken)
549−```
550−
551−These aren't quite comprehensive, but are enough to illustrate cores as objects with classes. Let's test them with casts, to make sure they nest in the expected way:
552−
553−```
554−> `animal`%cow
555−%cow
556−
557−> `mammal`%cow
558−%cow
559−
560−> `animal`%lizard
561−%lizard
562−
563−> `mammal`%lizard
564−nest-fail
565−```
566−
567−Good. Now let's define a `%cow` named `bess` as a zinc core. We'll write `bess` so that she can gain weight by eating food:
568−
569−```
570−> =bess ^& |_ [species=%cow weight=_100]
571− ++ this .
572− ++ eat |=(food=@ this(weight (add food weight)))
573− --
574−
575−> species.bess
576−%cow
577−
578−> weight.bess
579−100
580−
581−> =bess (eat.bess 20)
582−
583−> weight.bess
584−120
585−```
586−
587−Note that zinc cores can 'modify' themselves! (I.e., make a modified version of themselves, strictly speaking.) Nothing keeps you from treating a zinc core as a state machine. Only arms inside the zinc core can make modifications, however.
588−
589−Let's now define a `%lizard` named `leo` with the same eating function:
590−
591−```
592−> =leo ^& |_ [species=%lizard weight=_20]
593− ++ this .
594− ++ eat |=(food=@ this(weight (add food weight)))
595− --
596−
597−> species.leo
598−%lizard
599−
600−> weight.leo
601−20
602−
603−> =leo (eat.leo 7)
604−
605−> weight.leo
606−27
607−```
608−
609−Good. Now let's write a function that only accepts *mammals* as input, and then which gives what the new *mammal* `weight` would be if fed `15`. First we define an 'object class' by example, i.e. a zinc core named `mammal-core` that can serve as a prototype *mammal*:
610−
611−```
612−> =mammal-core ^& |_ [species=mammal weight=@]
613− ++ this .
614− ++ eat |=(food=@ this(weight (add food weight)))
615− --
616−
617−> =mammal-feed |=(a=_mammal-core (add 15 weight.a))
618−
619−> (mammal-feed bess)
620−135
621−
622−> (mammal-feed leo)
623−nest-fail
624−```
625−
626−(Note: the last example won't actually `nest-fail` in your dojo. This is a bug, however, and it will be corrected soon.)
627−
628−So you see that zinc cores can be used as prototypes of core classes, analogous to object classes in object-oriented languages.
629−
630546Let's clear up the dojo subject again:
631547
632548```
633549> =zinc-gate
634−
635−> =mammal-feed
636−
637−> =mammal-core
638−
639−> =leo
640−
641−> =bess
642−
643−> =mammal
644−
645−> =animal
646550```
647551
648552#### Lead Cores (Bivariant)
649553
650554Lead cores have more permissive nesting rules than either iron or zinc cores. There is no restriction on which payload types nest. That means, among other things, that the payload type of a lead core is both covariant and contravariant ('bivariant').
651555
652−In order to preserve type safety when working with lead cores, a severe restriction is needed. The whole payload of a lead core is opaque -- the payload can neither be written-to or read-from. For this reason, as was the case with zinc cores, lead cores cannot be called as functions. (Note: because of a type system bug, this is a lie. You can do function calls on lead gates right now. But it's bad for your soul and you shouldn't do it. I'll get that bug soon!)
556+In order to preserve type safety when working with lead cores, a severe restriction is needed. The whole payload of a lead core is opaque -- the payload can neither be written-to or read-from. For this reason, as was the case with zinc cores, lead cores cannot be called as functions.
653557
654558The arms of a lead core can still be evaluated, however. We can use the `^?` rune to convert a gold, iron, or zinc core to lead:
655559