The Canonical List of Non-Negotiables

I realized this is something that maybe hasn't been attempted, but would be very useful. That is a list of concrete code samples where if Rebol won't run that code looking that way, then it is dead to you.

I'd imagine it's easier to say things like "it's dead to me if it requires me to install Java". But there must be something other than the meta-properties of the language which people consider foundational? Actual code--looking and acting an actual way?

I'm not looking for controversial things here--rather things generally agreed on as good. But naming some of them out loud might have benefit. I'll start.

COMPOSE, UNSPACED, ETC. VAPORIZING CONDITIONALS

The lack of conditional vaporization bugged me to no end about Rebol2:

rebol2>> compose [<a> (if false [<b>]) <c>]
== [<a> none <c>]

rebol2>> rejoin ["a" if false ["b"] "c"]
== "anonec"

You had to throw in an EITHER with an empty branch:

rebol2>> compose [<a> (either false [<b>] []) <c>]
== [<a> <c>]

rebol2>> rejoin ["a" either false ["b"] [] "c"]
== "ac"

So I advocated vaporizing NONE! by default for a while...facing resistance from those wanting to use them as placeholders in blocks, adamant that NONE! was a value.

This tension spawned a new philosophy based around a NULL state...which had no datatype and could never be put in blocks. This state was then used to uniquely represent the failure of a conditional (while successful conditionals evaluating to null incidentally were "voidified"):

>> compose [<a> (if false [<b>]) <c>]
== [<a> <c>]

>> unspaced ["a" if false ["b"] "c"]
== "ac"

I don't want to belabor how it's implemented too much (even though I'm 99.99% settled with NULL as the right answer, and correct result for all failed conditionals). But I don't think it should be harder than this. so no opt if condition [...] or other markings.

If some brilliant replacement plan eliminated the idea of failed conditional returning something other than the absence of a value, then those would just have to dissolve in COMPOSE unless you use COMPOSE/ONLY or something. But I'm not holding my breath. NULL is the ticket, here.

DEFINITIONAL RETURN

Novices using Rebol2 or Red aren't really clear on how their RETURN works. (Or doesn't work, as the case my be.) RETURN climbs the stack until it finds a function that is willing to accept returns.

  • Functions in Rebol2/Red that won't accept returns: IF, WHILE, or pretty much any native
  • Functions in Rebol2/Red that will accept returns: any user FUNC you write

To give a brutally simple example, you cannot implement UNLESS in terms of IF:

 rebol2>> foo: func [x] [if not x = 10 [return "not 10"] return "it's 10!"]
 rebol2>> foo 20
 == "not 10"

 rebol2>> unless: func [cond block] [if not cond block]
 rebol2>> bar: func [x] [unless x = 10 [return "not 10"] return "it's 10!"]
 rebol2>> bar 20
 == "it's 10"  ; D'oh

That UNLESS, because it's a FUNC and not a native, is a candidate for receiving RETURN. So the UNLESS itself returned "not 10" instead of returning from bar. Execution continued and the `return "it's 10!" ran. I maintain that correct behavior constitutes another must-have, and I was by no means alone in this, nor the first to say so.

>> unless: function [cond block] [if not cond (block)]  ; see note re: group!
>> bar: function [x] [unless x = 10 [return "not 10"] return "it's 10!"]
>> bar 20
== "not 10"

I do not consider annotating UNLESS to say "I'm the kind of thing that doesn't catch returns" to be remotely acceptable. I'd sooner throw out the project than go that route. Addressing definitional returns wasn't at all trivial...even though conceptually it was understood what needed to be done. It was one of the first things I tried to do in open-sourced R3-Alpha. The rearranging I had to do in order to understand the code well enough to accomplish it laid the groundwork for many features to come.

So this belongs in the non-negotiable list.

(Note: The reason you have to put a group! around block (or say :block) is due to soft-quoted branching, and I argue for the tradeoff here. I would not consider that one of my non-negotiable points for this list, though I've offered what I believe to be some compelling arguments. I do--however--consider FUNCTION instead of FUNC to be a non-negotiable way of writing this, with current leaning that FUNC is a full synonym for FUNCTION)

...and...

That's only two off the top of my head. But my idea was that we can keep adding posts to this thread whenever someone thinks of something. What has to work else it's "dead to you"? @IngoHohmann, @rgchris, @gchiu, @Mark-hi, @BlackAttr... ?

Like I say: please avoid functionality concepts like "has to talk to ODBC"...unless you have a very specific code sample that looks exactly right for how it needs to look for some case.

3 Likes

I don't have any non-negotiables at this stage of things. In the R2 past I've had minor annoyances, and I'm sure as I use Ren-C there will be things that I wish were more simple or compact.

I don't know that anything I have is above negotiation, I suppose. I've used Rebol long enough and am generally productive with it and would prefer something resembling that language at the end of day.

I have my biases:

  • Data/messaging come first, and words without qualifying symbols be the premium currency.
  • Source should as much as possible resemble the molded representation of said source loaded.
  • Now that PARSE returns a position rather than a confirmation, I'm not sure I'd tolerate going back.

Will likely revisit these after mulling them over a bit, don't consider them to be dogmatic.

Rebol tries to straddle being a soft, human programming language against something that is more rigorous and consistent. Ren-C does move the needle more toward the latter and I consider that almost universally better, with one or two areas of reservation.

My other thoughts would relate to the meta, so I'll hold on to those (save to say, if you are going to introduce a Java dependency, then Rebol being dead might just be a mercy).

Update:

I'm not saying these are non-negotiable and it may be the form and not the substance that I object to. I know these are not necessarily behavioural facets of the language, but as we don't have a page for that, I would like to have them on record:

  • I don't like the @inert form. I have a strong preference for @name string sub-type (or just a variation of the email type).

  • I'm uncomfortable with new considerations for the /refinement form.

  • I've spotted <*> in some areas of source. I'm not sure what it means, but it looks intimidating.

  • I need to better understand the rationale behind type-of returning quoted types. My preference on the face of it would be type-of 1 be the same as type-of '''''1 with a different operator determining quotedness.

2 Likes

It's just a tag I thought looked good for use with the COMPOSE feature of a skippable FILE! or TAG! that shows you which slots to compose. I think that's pretty great. If you want you can pick another tag.

(The predicate functionality is currently disabled, pending more research on how predicates will be done system-wise...)

There's definitely a lot to think through regarding this. I'll be the first to say that.

But I feel like /foo/bar being legal is feeling fairly close to non-negotiable for me. And if /foo/bar is legal, I don't want that to be a PATH! and /foo to be a REFINEMENT!.

I wish there was more experience with this. :-/ I mentioned that I did some tinkering on NewPath stuff, which raises questions...like maybe if perhaps GROUP!s inside of @paths should evaluate:

file: @/usr/(first [local bin])/subdir
; e.g. file is now @/usr/local/subdir

xxx: '@/usr/(first [local bin])/subdir
; e.g. file is now @/usr/(first [local bin])/subdir

The inert forms bring in a missing piece for the language. Not having an inert form of WORD!/PATH!/GROUP! is too much a sacrifice to make just for a relatively unsophisticated string type. And @[type] is the best idea I have for applying an otherwise questionably-useful form to act as a concrete structural representation for datatypes.

This might be something you find you appreciate more with time. Hopefully you can be happy enough by virtue of getting back #issue as a string type, and there is %file ... that's pretty good coverage.

It was historically the case that quoted values returned a distinct type, so this isn't a new behavior.

rebol2>> (type? first [x])
== word!  ; new plan would be @[word]

rebol2>> (type? first ['x])
== lit-word!   ; new plan would be @['word]

Today the KIND OF operation conflates all QUOTED!s:

>> kind of first [x]
== #[datatype! word!]  ; new plan probably `@[word]` vs. e.g. `word` or `@word`

>> kind of first ['x]
== #[datatype! quoted!]

>> kind of first [''''(a b c)]
== #[datatype! quoted!]

And if you want to, you can say kind of dequote value to get what you are asking for (will likely be the same answer as type of dequote value but would make it more clear that you wouldn't be getting any quotes on the answer itself).

I would counter that I am skeptical of the use cases for such an indiscriminate "I don't care if it's quoted or not" test. I just don't see it coming up that you want to ignore how many quote levels there are and treating everything the same regardless. The cases where this would make sense seem covered by DEQUOTE.

I don't have anything absolutely non-negotiable. But if I will never have user defined types, maybe I will tempted back toward Python (ok, that's a bluff... :wink:

Bluff or not, maybe this points to an answer of having us understand exactly what makes a Python user defined type tick. What can and can't they do?

When you think about what a DATATYPE! was before, it was just a WORD! except it had no special rendering (which I think is quite bad). This is why I have the alternate proposal of using the otherwise-not-so-useful @[name] the inert form as a datatype in contexts that want to use it

I mentioned in chat that what we do have are better tools to attack the problem. There's some work and it's reasonably clever as a mechanical foundation, but the dots have to be connected. We need strong definitions of what is and isn't expected to be possible--and looking to the weaknesses in other languages and saying "they can't do that, so how could we be expected to?" may be the answer.

I'll again point out that in Rebol's relatives, the "Common Lisp Object System" is really what we've got to compare to. I don't know how acceptable doing exactly what they did should be. But I don't know where to look for a better answer.

Have you mocked-up custom user-defined types or pseudo-types that you want in a dialect?

Sometimes that can be a way to prototype a datatype-- build a toy interpreter/dialect. You may run into a lot of thorny issues that need to be thought through.

1 Like