'Enum' Handling

I'm curious what the current best practice is for handling 'enum'-like scenarios.

For my contrived example, I have an ORDER-FOOD function where you pick your dish, and that dish comes with onions.

order: func [
    'dish [word!]
    /sub 'veggie [word!]
][
    ...
]

You can substitute onions for one other vegetable, but obviously I only have so many vegetables at a given time, so I'd like to be sure that you either get onions or another available vegetable.

order enchiladas
order/sub burrito mushrooms

First thought is using 'default:

veggie: default ['onions]

That's fine so long as I have what the given vegetable is. What if I only have [onions zucchini artichoke carrot]?

There is the FIND approach:

if not find veggie-box veggie [
    veggie: onions
]

Then again, I'm always mindful of CSS's: if it doesn't exist, ignore it. This approach has allowed CSS to adapt as new methodologies exist. How display that has been around for as long as I can recall gains a grid value that permits all kinds of heretofore layout possibilities. Any browser out there that doesn't know what grid means will happily display content as if it was whatever the call to grid replaced. Similarly, all the properties associated with grid layout are just ignored by older browsers.

Not quite sure how that maps to my function, but it'd be interesting to say:

any [
    order/sub bibimbap bok-choy
    order/sub bibimbap carrot
    order burger
]

In the event of its availability one day, that I may have that fresh crunchy cabbage.

2 Likes

I don't know if you remember, but I wrote about developing an enum type as the first Rebol involvement, long ago:

"An Enumerated Type for Rebol2"

I'd have to think more about what my opinions on enums are these days.

The only enum-oriented thought I'd had was that WORD!s were a bit awkward to use for inert data, because if you forgot to quote them they would execute...and you didn't want them to. This made me think that when you were passing around things meant to serve as an identity you would use something else... e.g. an #issue.

With issues being stringlike, there's a bit more overhead to their comparison...which means words are faster to compare symbolically. This is part of why I thought @word could be useful, if it didn't have evaluator behavior.

Things have gotten murkier since...as alternate usages for @(...) have come up. But also, COMPOSE having features like being able to add ticks on composed things makes it a bit easier to preserve enum-ness:

 >> veggie: 'onions

 >> compose [x: '(veggie)]
 == [x: 'onions]

Well, we can just add working through more real examples into the infinite queue...

1 Like

I do. I figured that it'd be curious to follow up on that.

I was contemplating what to cook when putting this one together. I had forgotten that you'd used fruit in your original post.

Am actually contemplating a codec where there are several modes of decoding, e.g. [tokens sections document] but more possible.

While that is a fear, I'd still lean to vanilla words as first class currency.

1 Like

So we now have a bit more interesting fundamentals to power enums, because typecheck supports arbitrary quoting:

order: func [
    'dish ['burrito 'taco]  ; plain arg, literal
   ':sub ['onions 'salsa]  ; new refinement, literal
][
    ...
]

The MATCH primitive supports the same format as type spec blocks, and you can match any literal thing:

>> match ['foo 'bar '[x y]] 
== [x y]

>> match ['foo 'bar '[x y]] [y x]
== ~null~  ; anti

There also is ENSURE for erroring if something is a MATCH, and NON which passes through only values that don't match.

So we're pretty close to generalizing enumerated types...to include values that aren't just words!

You are probably right. But the good news is that in the generalization it doesn't matter.

But plain words are better in the enum spec as 'word than having to say '@word

The usual term for this is a ‘union type’.

1 Like

Sort of? If you say ~[]~ and 'pizza are in this context "types" that hold single elements, then you could argue that this is "unioning types". (quasiforms match the antiform, quoteds match one quote level dropped...so you can quote a quasi to match a quasi literally)

With FENCE! I think we'll have enough parts to make &(integer) a fundamental type, which will free up &[...] for using TYPE-BLOCK! to declare "union types"... you have this ability to go outside the WORD! space if you want to:

any-topping!: &['pepperoni 'onions 'olives 'tomatoes '<no-sauce>]

Then maybe &{...} could be "intersection types". &{even? integer!}

I was thinking numbers might mean length of. &{3 block!}

This is a cleaner version of what was originally proposed for the MATCH dialect. Maybe it's the TYPECHECK dialect (or simply, CHECK ?), and MATCH should be taken for DESTRUCTURE.

>> check [integer! tag!] 3
== 3

>> check [integer! tag!] "hello"
== ~null~  ; anti

>> check [~null~ integer!] null
** Error: use CHECK/META for testing keyword states

>> check/meta [~null~ integer!] null
== ~null~

>> check/meta [~null~ integer!] "hello"
== ~null~  ; anti

>> non integer! "hello"
== "hello"

>> non integer! 3
== ~null~  ; anti

>> ensure integer! 3
== 3

>> ensure integer! "hello"
** Error: "hello" is not &(integer)

>> prohibit integer! "hello"
== "hello"

>> prohibit integer! 3
== Error: 3 is &(integer)

Overall, what this is going to permit with dialected chains looks very promising, e.g. typechecking [{block! all:word?}].

Yep, precisely. Representing enumerations as unions of singleton types is normal practice in languages with this sort of type system (e.g. TypeScript). Ren-C doesn’t have any true type system, but the idea here is similar.