LANG: 0 should not be a "branch inhibitor"
hostilefork opened this issue ยท 2 comments
Hello! Long time no C (mid day or other time ๐)
Was told about your progress on the GUI, via someone sending a hackernews link. You're famous! :-)
I was very pleased to see you had decided on {...}
as a list type. I have come to believe this is very important!
(Not sure what state of development you consider yourself to be in, but if you think things are still malleable, calling {...}
a FENCE! and [...]
a BLOCK! seems really nice, and would help us standardize our terms...)
I think there are promising aspects to using Go as a substrate, I definitely think green-threading and such is good, and would hope to learn from what you find.
But I did do some digging, to try and understand what you are doing:
https://forum.rebol.info/t/rye-language/1768/6
The first thing that jumped out at me, is something I would urge you to go back on:
if 0 { print "Moon" } ; doesn't print anything
0 being "falsey" breaks the usefulness of ANY and ALL and such, crippling the ability to write idiomatic code:
value: all [<<condition1>> <<condition 2>> some-number]
I can think of a lot of reasons to make it "truthy", and 0 reasons to make it falsey. If picking things to change about the language I definitely wouldn't change this.
Might I convince you to back that out, and, hopefully re-engage the Ren-C design... (which is getting very, very good!)
I'm hoping that Rebol-derivatives might standardize more things, even if they cater to variant audiences (Go programmers, Nim programmers, etc.)
Thanks for your feedback Mr. Fork :). I think your proposal makes sense, and I was always cringing a little when I had to write "Truthy" in documentation or tutorial and thought that this might have to change at some point.
The reason I initially didn't implement boolean true / false values is that I remember from rebol (at least I had some confusion) of what is true
... why does it look like every other word, but it isn't. Maybe it was just me but it seemed inconsistent or at least confusing. I wanted no exceptions if possible so true / false in Rye are just words that are bound to builtin functions that return 1 and 0.
Since I'm not sure if there is really something odd about true / false in Rebol I went trying now, correct me if I don't understand something ...
>> print true
true
>> print 'true
true
>> type? true
== logic!
>> type? :true
== logic!
>> type? first [ true ]
== word!
>> type? first reduce [ true ]
== logic!
Ok, so maybe it does make sense, it looks to me from this that true is a word, like a constant, that is bound to logic value true. The confusion comes maybe that logic value has the same spelling in REPL as the word and that it's easy to mix them up?
Your argument about branch inhibitor makes sense to me. I wasn't thinking about that but yes, it's certanly a source of bugs and limitations and inconsistent at the end. And I want the language to be flexible, but environment and values explicit so I want to go away from all truthines and falsines. Concretely, with this I would redo for example if
to only accept boolean and you have to be explicit of what is true / false ... like if is-zero x { }
or if is-empty b { }
instead of implicit conversions that might or might not make sense in certain situation.
So true and false would still be functions, because I don't have builtin constants in Rye runtime for now. But they would return a "env.Boolean". In Rye console the returned value is printed with type information so it will be [Boolean: true] which would avoid the confusion in repl.
I will read the other links you sent in next days. I see you mentioned goroutines ... if you haven't seen yet, here is some related information: https://ryelang.org/blog/posts/rye-concurrency-go/
About { }
and [ ]
. Those were the same for a long time, but recently when doing Fyne code I found a to me definitive use for [ ]
, which is like reduce [ ]
in Rebol. I had reduce for hof-s (map,reduce,fold,...) and had problems finding a word for it either way ... I tried evaluate, eval (overused in diff context), vals, valuate, ... I think it's currently vals otherwise and ( )
is basically do [ ]
in Rebol terms, this one also behaves as () in Rebol I think and can be used for grouping / priority / etc.
Note please that in Rye every token (also []{}(()) has to be space separated so [1+2]
is (multiple) loader error(s). It has to be [ 1 + 2 ]
, which is probably weird comming directly from rebol (it's more of a Factor thing), but so far I'm staying with it, because I have some conventions for words like (rule)
and * at .word*
has special behaviour for example.
The reason I initially didn't implement boolean true / false values is that I remember from rebol (at least I had some confusion) of what is true ... why does it look like every other word, but it isn't. Maybe it was just me but it seemed inconsistent or at least confusing.
It's not just you. It is indeed very inconsistent. Ren-C solves this problem (and others) in a fully generic way, using what are called isotopes. There are some states which variables can hold which are not able to be stored in blocks. This has been applied to solving many different problems:
https://forum.rebol.info/t/a-justification-of-generalized-isotopes/1918
So, the "antiform" of the word "null" is the only thing that acts as a "branch inhibitor" in Ren-C. There is a complementary antiform called "okay" if you want a canon "branch trigger" that can't be stored in a block.
But if you want to use words to represent logic...like TRUE and FALSE...you can do so. But IF and friends don't test those. So you would have to say if flag = 'true [...] or if flag = 'false [...]. And there are narrower functions TRUE? and FALSE? which ensure the thing you are passing is only in the set [true false]
. Of course this means there's nothing special about those words: I find that YES and NO often make a lot more sense.
This has worked out very well. In places that are user-facing--where a variable's state might be readily exposed--using words makes sense. In other places, like the value of refinements that can only be on or off, easily testing them is more important so they use the ~okay~
vs. ~null~
antiforms. You can convert them with BOOLEAN or BOOL
>> 1 = 2
== ~null~ ; anti
>> append [setting:] 1 = 2
** Error: Cannot append antiform ~null~ to blocks
>> boolean 1 = 2
== false
>> 1 = 1
== ~okay~ ; anti
>> append [setting:] bool 1 = 1
== [setting: true]
I wanted no exceptions if possible so true / false in Rye are just words that are bound to builtin functions that return 1 and 0.
Making conditionals test against a null state (where that null state cannot be stored in a block) has resolved the historically annoying problems. You don't have random failures when writing:
block1: [...]
block2: []
while [item: try take block1] [append block2 item] ; TAKE without TRY errors on empty block
I think it's an important invariant--that should succeed for any state of block1.
Concretely, with this I would redo for example if to only accept boolean and you have to be explicit of what is true / false ... like if is-zero x { } or if is-empty b { } instead of implicit conversions that might or might not make sense in certain situation.
The specific strategy of isotopes (quasiforms/antiforms) is not something you'd have to copy completely. But you could just say some states can't be put in blocks--only held in variables. Though I think that would possibly lead you down the path of thinking that the right "API" for getting at what these forms are is to think of them as variants of plain values.
In practice, I think a lot of things work out better when there is only one branch inhibitor...you can write things like all [x = 1 y = 2] and not worry about the failing state of that coming back as false vs. none/null, because there's only one such state.
Array splicing is one of the many things that work out better if you have a splice type that itself cannot be stored in blocks.
>> replace/all [[a b] a b a b] [a b] [c d e]
== [[c d e] a b a b]
>> replace/all [[a b] a b a b] spread [a b] [c d e]
== [[a b] [c d e] [c d e]]
>> replace/all [[a b] a b a b] [a b] spread [c d e]
== [c d e a b a b]
>> replace/all [[a b] a b a b] spread [a b] spread [c d e]
== [[a b] c d e c d e]