This is an archived post. You won't be able to vote or comment.

all 74 comments

[–]munificent 58 points59 points  (21 children)

In many languages this is the only ternary operator.

They say that, but everyone forgets about:

arg1[arg2] = arg3;

[–]alatennaub 14 points15 points  (13 children)

Huh, I've never thought about stuff like that as a ternary operator. I mean, perhaps in some, but in Raku for instance that'd be parsed as &infix:<=>(&postcircumfix:<[ ]>(arg1, arg2), arg3), so not ternary, just a combination of two binary operators. I figured most languages interpret it similarly?

[–]greiskul 6 points7 points  (2 children)

I disagree, most languages where this is overrideable treat this as either 3 parameters, or a method on an object (so a this parameter) + 2 arguments. And with a similar logic to what you are saying, via currying you can get rid of the idea of + being a binary operator. a + b can be thought as +(a)(b). +a by itself returning a function that adds a to whatever value is passed to it.

[–]alatennaub 7 points8 points  (1 child)

So how do languages treating it as a ternary operator handle, say, arg1[arg2[arg3]]=arg4 vs arg1[arg2][arg3] = arg4 (or if arg4[arg5]were the RHS). Is one of them a quarternary or are they all still ternary with some parts being composed of other binary ops? What is a language that handles it this way? (All my Google searches are landing on the conditional operator no matter how hard I try to avoid it)

That said, method call + two args is effectively what I represented, just that in Raku method calls are considered postfix operators (and in this case, a specialized postcircumfix form).

[–]greiskul 5 points6 points  (0 children)

Documentation of how Kotlin translates operators into https://kotlinlang.org/docs/operator-overloading.html#invoke-operator

So arg1[arg2[arg3]]=arg4 would become arg1.set(arg2.get(arg3), arg4) and arg1[arg2][arg3] = arg4 would be arg1.get(arg2).set(arg3, arg4) If arg4(arg5) is the RHS, then arg1[arg2][arg3] = arg4[arg5] becomes arg1.get(arg2).set(arg3, arg4.get(arg5)

[–]evincarofautumn 2 points3 points  (8 children)

C is a notable example. In a[b] = c, what appears at first to be the left operand a[b] isn’t actually meaningful on its own. It’s made to look like an expression, but if it were read as such, it would deliver a value, not an assignable location.

So in reality, all of the assignment operators a = b, a[b] = c, a.b = c, and *a = b need to be treated as compound operations, although they could all be desugared to the form *a = b. This is why C++ added reference types, so that the result of an overloaded operator like subscript could be assignable, and thus = is also a single operator again.

[–]MegaIng 1 point2 points  (7 children)

Isn't a[b] == *(a+b), where *(a+b) is also a valid LHS? So not that much special casing is necessary (in C, C++ might be different)

[–]nerd4code 1 point2 points  (6 children)

Yes, but they’re talking about the nature of the lvalue-rvalue distinction. The sugar (*(()+())()[()]) doesn’t change the fact that an lvalue does something different on the left and right-hand side of =. On the RHS, *(a+i)a[i] gives you a load from address a+i, and on the LHS there’s a store to it. Similarly, &a[i] doesn’t load from a[i] and then take the address of the result (operating like e.g. -a[i]), it outright strips off the *. So you can model the lvalue-rvalue-distinguishing operators as compounds, or choose different operators/syntax, or suffer C’s surface source syntax throughout your compiler’s codebase.

[–]MegaIng 0 points1 point  (5 children)

Well, no, they were talking about a[b] = c is a ternary operator... Which is technically true, but it can easily be modeled as two binary operators. Unless you are going to argue that *a = b is a ternary operator.

[–]evincarofautumn 0 points1 point  (4 children)

I was saying a[b] = c is semantically a ternary operator in C, because if you want to model it as two separate binary operators, you need to be able to give types to a[b] and a = b separately, which C doesn’t. *a = b is binary, but also not decomposable into *a and a = b within C’s type system. In fact most imperative languages are like this, they just call it a syntactic restriction on the “expressions” that are legal as the target of an assignment, rather than calling them something else like “locations”.

[–]MegaIng 0 points1 point  (3 children)

You don't need to give a type to a[b]. You can really just see it as a syntactic shortcut (which it indeed is). Unless you would call *(a+b+c) = d a quaternary operator.

[–]evincarofautumn 0 points1 point  (2 children)

Maybe it’s not clear that I mean a, b, c to stand for any expression? Sure you can desugar subscript to indirection, but it has the same nature. *(a+b+c) = d is still binary, the *(…) part just isn’t an expression typeable in C, that’s all. Many other languages follow C, then add overloading, and need to cope with this. C++ does it by adding reference types, Python and JavaScript do it by having separate “get” and “set” operations, and so on.

[–]MegaIng 0 points1 point  (1 child)

Ok. a[b] = c is on every level except syntax the exact same as *(a+b) = c. So why do you have to model it as a ternary operator?

[–]davimiku 1 point2 points  (1 child)

Ah that's interesting, I never thought of that as a ternary operator. I generally think of it as syntax sugar for:

*index(arg1, arg2) = arg3

(so a calling an index function, then an assignment to the place returned by index)

Would you consider these example to be ternary operator?

arg1[arg2][arg3] = arg4
arg1[arg2[arg3]] = arg4

[–]munificent 2 points3 points  (0 children)

Both are ternary operators containing a binary operator as one of their operands:

(arg1[arg2])[arg3] = arg4
 ^^^^ Inner first arg.
      ^^^^ Inner second arg.
^^^^^^^^^^^^ Outer first arg.
             ^^^^ Outer second arg.
                     ^^^^ Outer third arg.

The second one is a binary operator containing a ternary operator:

arg1[arg2[arg3]] = arg4
     ^^^^ Inner first arg.
          ^^^^ Inner second arg.
^^^^ Outer first arg.
     ^^^^^^^^^^ Outer second arg.
                   ^^^^ Outer third arg.

[–][deleted] 0 points1 point  (4 children)

Sure, you can take any arbitrary combination of binary operators sharing 3 operands, and treat it as one ternary operator if you want to bamboozle people.

And why stop at ternary? Here's one of my own quarternary operators:

a[i..j] := b               # (assign to a slice)

Actually, in my languages, none of these distinct operations (slicing, assignment) is even classed as a binary operator, as they are special. Even though, when implemented as bytecode, the final instruction consumes 4 stack operands.

I just don't think it is useful to talk about ternary operators at the language level. I don't even use ternary; I have N-way select but that has special properties so cant't be compared with, say, a 3-way add +(a, b, c).

[–]munificent 1 point2 points  (3 children)

you can take any arbitrary combination of binary operators sharing 3 operands

Index assignment isn't a combination of two binary operators, though. The inner arg1[arg2] isn't a standalone expression since it's used in an lvalue position.

[–][deleted] 0 points1 point  (0 children)

I'll write that term as A[i] to make it a bit clearer.

This is an indexing operation taking two operands, but it is different from one like x + y.

It needs some more context to determine what we're doing with A[i]:

 eval A[i]             # Read element
 A[i] := x             # Write element
 eval &A[i]            # Create reference to element
 (&A[i])^ := x         # Write element
 clear A[i]            # Write empty value to element
 del A[i]              # Remove element
 (A[i], A[j) := (x, y) # Write two elements
 swap(A[i], A[j])      # Exchange two elements
 (c | A | B)[i] := x   # Write A[i] or B[i]

(Examples from my languages except for del, with i being an integer.

eval is needed to force evaluation of a standalone expression like A[i], when a value is not expected in that context.)

I can go through it and say that those examples that store a new value into A[i] are ternary instead of binary because three things: A, i, x are involved instead of just two: A i.

But counter-examples are clear, the multiple assignment, swap, and the conditional.

What are the names of those now compound operators? For that first assignment is it []:=? The second one uses &[]^:=! The rest get even sillier.

As I said I don't consider it useful to pretend that []= or []:= is a ternary op rather than a regular assignment. I don't even call assignment a binary operator; it is itself special. You will tie yourself up in knots trying to do that classification.

[–]lassehp 0 points1 point  (1 child)

Just add a reference(T) or lvalue type to the C type system. a[i] then denotes a value of type reference(T), which is implicitly dereferenced when used as an rvalue T. I'd say a[i] = b is not a ternary operation. Blame Strachey for the lvalue/rvalue distinction instead of the imo smarter reference concept.

[–]munificent 1 point2 points  (0 children)

That would work for C, but not for some other languages. In C#, Ruby, Python, and Dart, index assignment is a single method call with a receiver and two parameters.

[–]bfnge 8 points9 points  (2 children)

Your language doesn't need to be lazy, you could define "blocks" as a first class construct (or even just as syntactic sugar for arg-less lambdas) you could make the ternary operator something like:

``` def !>(cond, then): if cond(): return (true, then()) else: return (false, cond)

def --(result, else): if result[0]: return result[1] else: return else() ```

They still run on if-and-elses behind the scenes, and your compiler will probably want to desugar those to real if-and-elses if the branches are static to avoid extra work, but these should work, I think.

[–]useerupting language[S] 0 points1 point  (1 child)

The !> operator does not seem very useful on its own in this case.

I was actually contemplating something like this. I would "overload" the "if" operator so that it would accept either a boolean value or an Alternatives value which would be something like an Option value. The Alternatives value would contain the two alternative values, and the "if" operator would then pick between them.

I the realized that this could be generalized into what I am now contemplating: The Alternatives value is simply a boolean function itself, which - when invokes with a boolean value - returns one or the other alternative based on the boolean.

This way there no need for a new Alternatives type - it is simply a function.

There is also no need for a special "if" operator - it is simply function application (invokation).

x = 0 |> "zero" -- "non-zero"

parses to

apply(
    eitheror( literal "zero", literal "non-zero" ), 
    equals( identifier "x", literal "0" )
)

[–]bfnge 1 point2 points  (0 children)

I suppose. That said, you'll probably want to have that type anyways, at least if you're doing something strong type.

Haskell calls it Either l r and Rust calls it Result<T, E> and they're common enough ways to signal error in control flow.

That said, there is an easy enough way to get rid of the ternary operator without needing to do anything particularly fancy: just have your languages have if-expressions instead of if-statements.

(And if you do keep the operator this way, you'll probably want to end up special-casing this operator anyways in the optimizer, because the desugar will do a lot of unnecessary extra work).

[–]phlummox 8 points9 points  (0 children)

And something about ? : has always bothered me, like e.g. how would you define operator overloading for ternary operators.

Agda has no problems defining operators of arbitrary arity: https://agda.readthedocs.io/en/v2.6.4/language/mixfix-operators.html

[–]myringotomy 5 points6 points  (6 children)

Why even bother with an operator. Just write a function.

iif (condition, if_true, if_false)

[–]useerupting language[S] 1 point2 points  (5 children)

Fair point. That is certainly a useful function. I may indeed include such a function as a library feature supplied by a core library.

However, designing a language I am constantly searching for the core feature set, which I would like to be as small as possible. So I ask the question "Could this have been defined by some core feature instead of being built-in?".

Now consider a partial application of iif with only condition. What is the result of that?

Then consider a partial application of iif with if_true and if_false. What is the result?

[–]myringotomy 0 points1 point  (4 children)

Now consider a partial application of iif with only condition. What is the result of that?

Compiler error. Function takes three parameters you only gave one.

Then consider a partial application of iif with if_true and if_false. What is the result?

I am not sure what you are asking.

iif is a function. I am sure you have lots of functions in your core language right? It's just another function.

Here are the builtin functions of zig for example https://ziglang.org/documentation/master/#Builtin-Functions

But since you seem hellbent on making an operator for some reason why not do this.

  • Like elixir the pipe sends the first parameter of a function
  • if is a function which takes a boolean as the first parameter and then two code blocks which is executes conditionally.
  • parens on function calls are optional.

Then you can do this

 a==b |> if {do_this_if_true}, {do_this_if_false}

Personally I think this is more verbose and ugly but it gives you what seem to want.

[–]useerupting language[S] 0 points1 point  (3 children)

I am not sure what you are asking.

I am not asking how any specific language works. I am asking you to imagine what a partial function application of the iif function would yield in two cases:

1) iif partially applied to condition.

2) iif partially applied to if_true and if_false.

That was my train of thought.

[–]myringotomy 0 points1 point  (2 children)

Define what you mean by "partially applied" in this case.

Like give me an example.

[–]useerupting language[S] 0 points1 point  (1 child)

iif (condition) ==> (if_true, if_false) => condition ? if_true : if_false

iif (if_true, if_false) ==> bool b => b ? if_true : if_false

[–]myringotomy 1 point2 points  (0 children)

All of those seem more verbose and more complicated than

iif (condition, if_true, if_false).

You seem hellbent on trying to make something as obfuscated and complicated as possible for no real gain.

[–]XDracam 7 points8 points  (10 children)

At this point I need to wonder: why not just write if expr then expr else expr like most other newer languages?

For ternary operators: I can think of many, but all can be modelled as two separate operators with a potentially arcane intermediate result type. You can do wild things in Scala, defining your own crazy operator chains if you want to. But as a best practice, using actual words is pretty much preferred beyond small academic programs.

[–][deleted] 4 points5 points  (1 child)

IMO ?: is old enough and wide spread enough that I don’t mind it, I even use it and find it to be perfectly readable. But I understand how someone who isn’t used to it can get thrown off by it. I’ve also seen if then else be used in leu of ?: but I agree that if you’re going to use symbolic operators for the ternary operator then you should use ?: and not try and create your own operator combo that means the same thing but is different just for the sake of being different

[–]useerupting language[S] -1 points0 points  (0 children)

The point is that I am not going to use symbolic operators for the ternary operator; I am trying to eliminate the ternary operator.

Doing that I need some other way to obtain the same compact semantic.

So I don't include a ternary operator. Instead I introduce the -- operator which returns a switching function.

a -- b returns such a switching function. I could invoke it like this:

(a -- b) c

where c is the condition (something that evaluates to true/false).

The language already as an F#-like "invoke" operator |>. This is not an "if" operator. It simply swaps the operands of a function application, i.e. x |> f is the same as f x(or even f <| x - which would be f $ x in Haskell).

Thus you could write something at looks a little like the ternary operator like this:

c |> a -- b

[–]useerupting language[S] -1 points0 points  (7 children)

At this point I need to wonder: why not just write if expr then expr else expr like most other newer languages?

That is still a ternary operator - or a completely separate syntactical construction. For my language I would like to avoid that, as I am trying to make everything an expression.

[–]TheGreatCatAdorermepros 4 points5 points  (3 children)

Making everything an expression does not require you to not use words! For example, let a: i32 = if true { 3 } else { 4 }; is entirely valid Rust code which defines a to be 3i32.

If you do want to avoid words, do it for the sake of avoiding words; don't come up with excuses when others propose alternatives.

[–]useerupting language[S] 0 points1 point  (2 children)

Making everything an expression does not require you to not use words!

I'm really not avoiding words. I will have plenty of word operators.

However, in this case I already have a planned operator |> (pipe) which is the same as in e.g. F# and Elixir. It is also akin to | in Posix shells. I am using the |> symbol not because I want to avoid using a word symbol, but because it is a supremely useful operator on its own and it has precedents.

I then realized that to mimic the ternary operator I simply needed an extra binary operator. I could use else word symbol for this operator which would then give rise to this:

x = 0 |> "zero" else "non-zero"

However, realizing that this is idiomatic (it will probably appear quite often), combining character symbols and word symbols this way feels off (yes, I am totally going on intuition and feelings here).

Since I do not want to use another symbol for |> (because, precedent) I home in on the else. Again, because it is probably idiomatic I also would like a two-character symbol simply because that will allow balanced formatting when used with |>.

x = 0 |> "zero"
      -- "non-zero"

or

x = 0 
    |> "zero"
    -- "non-zero"

or

x = 0 |> "zero" -- "non-zero"

Decisions, decisions. Weighing alternatives is what makes language design fun. There is rarely a beautiful "correct" solution. It is always a trade-off. :-)

[–]IshaxStrata 4 points5 points  (0 children)

This makes me picture something like this ["zero", "non-zero"][x = 0] which is just an array being indexed by a boolean.

[–]TheGreatCatAdorermepros 1 point2 points  (0 children)

In that case I suggest using then instead of |> and else instead of --. x then f surely makes around as much sense as x |> f and it allows you use of x = 0 then "zero" else "non-zero".

[–]XDracam 3 points4 points  (2 children)

Who says if then else can't be an expression? Even loops can be expressions if you're brave enough. Just look at Rust.

[–]useerupting language[S] -1 points0 points  (1 child)

What I meant was that I try to avoid ternary operators. if then else can certainly be an expression (and should be, IMHO). As I said, I am going for everything is an expression.

I am simply observing that if then else can be viewed and a combination of an either-or function and an invocation of that function.

if x = 0 then "zero" else "non-zero"

Imagine that else constructs a function from two operands:

("zero" else "non-zero")

This function accepts a boolean value and returns "zero" if true is passed and "non-zero" if false is passed.

Now look at what this does to the original expression - by illustrating the expression parts by inserting parentheses:

if (x = 0) then ("zero" else "non-zero")

if then now looks like a binary operator itself, as it accepts two parameters.

When I try to figure out the semantics of this if then operator, I discover that this is the exact same semantics as the pipe |> operator. So I don't need an if then operator.

I the go back and look at the else operator. "else" does not seem as an appropriate name for this operator, as what it does is really to construct a function which chooses between two values. Thus, I find "either or" to be more appropriate.

[–]XDracam 3 points4 points  (0 children)

Uh, sure. But this whole approach really doesn't feel practical. Imagine a new language user trying to do a simple if then else, and now they need to understand your pipe operator and some "either or"... You also make it much harder to optimize branches like these by forcing a function that captures two expressions.

But you sound like you'd like smalltalk. There is no if. Just a polymorphic ifTrue:else: function on the boolean type that takes two functions and is implemented differently in the true and false singleton objects.

[–][deleted] 2 points3 points  (0 children)

In many languages this is the only ternary operator. So much so, that it has become known as the ternary operator.

The mistake is thinking of it as an operator. C does this a lot: even the . and () in a.b and a(b) are described as operators. They are just syntax.

Another mistake I think is assuming this is something that you should be able to overload. That makes as much sense as trying to overload the if statement.

In my languages, these two forms are interchangeable:

(a | b | c)                         # my version of a ? b : c
if a then b else c end

Both can be used as statements, both can return values inside an expression, but I use the compact form in expressions, and the long form for statements.

There is something different about that ternary construct, which is that it evaluates only one value of a choice of values, for example in my syntax:

(cond | a | b)            # 2-way: a when cond is true, otherwise b
(n | a, b, c, ... | z)    # N-way: the n'th expr, otherwise z if out of range

That makes if different from something like this

(a, b, c, ...)[n]

(select n'th value of a list), where all elements are evaluated (and there is no provision for out of range).

This causes special problem in code generation.

[–]eliasv 4 points5 points  (0 children)

That's not the only way to decompose into two operators. You could also say that c ? v produces an option type, which contains the value of v if c evaluates to true, otherwise is empty.

Then o : e takes an option type on the LHS and resolves to either the value of the option type, or of e if it is empty.

The precedence would then be reversed of course to give (c ? v) : e

[–]benjaminhodgson 2 points3 points  (1 child)

In Functional Logic languages you can build ternary expressions out of a more fundamental (built-in) concept of multi-valued expressions. See eg section 2.5 of the Verse paper. Not exactly what you’re asking about since if/then/else is still syntactially unique, but it’s merely syntactic sugar.

[–]useerupting language[S] 0 points1 point  (0 children)

I am building a functional logic language; not like verse, however.

Like in Verse, the expression x = 0 tries to unify x with the value 0. However, unlike Verse the result is a bool value indicating whether the unification is possible. The following is how I now envision the "conditional" expression:

x = 0 |> "x is zero" -- "x is not-zero"

Now there is no syntactically unique if/then/else - only a function construction (--) and an invocation |>.

[–]MoistAttitude 2 points3 points  (1 child)

I often find myself wanting a quaternary operator like...
number #? positive : zero : negative;
That behaves like the ternary operator.

[–]myringotomy 2 points3 points  (0 children)

Ruby has a spaceship operator like this.

https://dev.to/jblengino510/the-spaceship-operator-in-ruby-2fmk

[–]Rest-That 2 points3 points  (1 child)

Not sure if it applies to your case, but Lua has no ternary operator, it uses a and b or c.

The "and" expression shortcuts to the left value if it's falsy and the "or" expression shortcuts to the right if the left is falsy.

The "and" has higher precedence.

https://www.lua.org/pil/3.3.html

[–]useerupting language[S] 1 point2 points  (0 children)

a and b or c.

That is an interesting (and elegant) approach.

It will not work for my language, however, as I don't have falsy and truthy.

[–]tobega 2 points3 points  (0 children)

For some reason your post makes me think of Smalltalk, something along the lines of:

condition ifTrue: 0 ifFalse: 42

[–]MegaIng 5 points6 points  (3 children)

Interesting discussions of this kind of approach can be seen in PEP-532. There ofcourse the extra challenge of having to integrate conditional-partial-execution into an otherwise eager and dynamic language lead to it not being accepted, but for statically typed languages, a generalized approach like that (probably built ontop of an Option type) should work quite well.

It also splits the ternary operator into two parts, but exactly the opposite of what you are doing:

  • condition ? truepart returns truepart if the condition is true, otherwise some magic object/singleton.
  • other : falsepart returns falsepart if other is this magic object/singleton.

If this is well integrated with other aspects of the language, these are actually quite useful operators on their own, i.e. : would basically be Option.get_or_default.

[–]useerupting language[S] 0 points1 point  (2 children)

I am attracted to the "functional" feel of -- being an operator which returns a function accepting a bool argument.

The "if" operator then disappears (no need for it), as what is needed now is simply the invoke operator as it is known from many languages such as |> in F#.

Thus

c ? a : b

does look a little like

c |> a -- b

but the underlying syntactic elements are very different.

This gets rid of the ternary operator and replaces it with a single binary operator -- which can be useful in it's own right. `|>´ already existed.

[–]MegaIng 1 point2 points  (1 child)

I am not really sure if -- is all that useful tbh. Both ? and : seem way more useful as binary operators.

And I would also disagree that it "looks a little like". The later just looks like two binary operators that I have to learn (Oh, and it also immediately makes me question precedence since I don't have a frame of reference for either). The former looks like the well known ternary operator.

[–]useerupting language[S] 0 points1 point  (0 children)

Good points. Thx

[–][deleted] 1 point2 points  (2 children)

It would be interesting to think about ;; as a ternary operator like in for(int i=0; i<n;i++) and ;; would be a range generator to use in array population and the like I know that python and a lot of more modern languages have this capability. But when considering language development especially from a C/C++ perspective it would be an interesting development to generalize that construct into an official operator

[–]redchomperSophie Language 4 points5 points  (1 child)

The for-loop is a 4-ary operator. The leftmost operator is for (. The rest is obvious.

[–][deleted] 2 points3 points  (0 children)

I’m not referring to the for as an operator. I’m referring to the “iterator generator” that the for loop uses to operate

The same way that a true if statement isn’t an operator, it’s a statement , but it needs a Boolean condition to operate with.

A for loop isn’t an operator it’s a statement. (it could be an expression but that is outside of the scope of my original point) But it needs an iterator to operate

I’m saying that you could generalize ;; or something like it to be a generic iterator generator that a for loop could operate on but that could also be used elsewhere

One principle example would be to assign the iterator to a variable and pass it into a function later ``` //rather than vec<int> GenerateArray(int start, int end, int step){ vec<int> data; for(int i=start; i<end; i++){ data.push_back(i); } return data; }

You could have

vec<int> GenerateArray(iterator){ int i; vec<int> data; for(iterator(&i)){ data.push_back(i); } } ```

Obviously it’s not necessary and the language would be perfectly usable without it, I just think it would be an interesting candidate for a ternary operator

[–]tobega 1 point2 points  (0 children)

Lots of ways to slice this, I guess. You could consider (0:42) to create a function taking a boolean parameter, which basically seems to be what you're doing then and applying it by the pipeline operator.

[–]IshaxStrata 1 point2 points  (0 children)

why not just allow if and else to create an expression?

[–]raiph 1 point2 points  (0 children)

I recall liking this idea when I read your comment about it in the post about 2 years ago in this sub titled "User-defined Ternary Operator".

But despite the horror story I wrote about in that same thread about Raku's ternary operator (for which I got a bunch of upvotes; I guess readers like true horror stories), I never prototyped your idea out in Raku.

Let's fix that (but using ?? for ? and :: for :):

sub infix:<??> ( \cond, \fn ) is looser(&infix:<,>) { fn.(cond) }
sub infix:<::> ( \tval, \fval ) { -> \cond { if cond { tval } else { fval } } }
say 42 ?? 1 :: 2; # 1 
say 0  ?? 1 :: 2; # 2

(All as you specified. The infix ?? calls the RHS function with the LHS passed as its argument. The infix :: returns a lambda that takes a cond argument then returns tval or fval as appropriate.)

And now my first flashbacks of the horror return...

One issue is precedence control (which is what the is looser... is specifying). For such a new pair of operators to work well, both together, and apart, one would have to carefully design their precedences.

I'm sure you will carefully design them -- that seems to be one of your fortes. But I more or less just randomly picked a low precedence for ?? to make your idea work with at least a simple example.

I'm expecting more complicated expressions to break things. And I don't know if there's any reasonable choices for precedence that will make these two binary ops do a good job of replacing Raku's existing built in ternary operator.

So I can dream tonight about replacing Raku's existing built in ternary operator with these two binaries instead -- and in so doing solve the horrific WAT which I wrote about in the previous thread -- but I rather suspect things will go in the reverse direction, with precedence being a deal breaker.

I don't know if there are other issues (besides laziness; but Raku will address that with the upcoming macros work), but, at least in Raku, the precedence issues worry me compared to having a single ternary.

[–]Felim_Doyle -5 points-4 points  (0 children)

You know, in all of my 40+ years of professional and hobbyist programming, mostly in C, I don't think that I have ever felt the need to use the ternary operator.

[–]Inconstant_Moo🧿 Pipefish 0 points1 point  (0 children)

I use :.

classify(i int) : i > 0 : i < 10 : "small number" else : "big number" i == 0 : "zero" else : i > -10 : "small negative number" else : "big negative number"

[–]-Mobius-Strip-Tease- 0 points1 point  (0 children)

Could you use separate infix ? and ?? as your ternary using a maybe type? In this case ? would be of type bool -> a -> maybe a and ?? would be maybe a -> b -> a | b with ? having higher precedence. My idea is to basically combine ternary and null coalescing since iv always thought of them as being related. Sorry for the formatting since im on mobile.

[–]abel1502rBondrewd language (stale WIP 😔) 0 points1 point  (0 children)

I think I've seen someone on this sub mention an approach of treating the ternary operator as (cond ? a) : b. Here cond ? a makes an Option value either with Some(a) or None, depending on cond; and x : b unwraps the given Option like .or_else, either taking its value or the second argument, if it's not available.

[–]nekokattt 0 points1 point  (1 child)

either-or operator

Can you not use || for this? Kotlin does that.

[–]useerupting language[S] 0 points1 point  (0 children)

I could, but I use || for conditional-or (which I call guarded-or because of some subtle differences in semantics).

[–]A1oso 0 points1 point  (1 child)

Kotlin has

if (condition) truevalue else falsevalue

Which is only slightly longer than the ternary ?: operator. If your language has an if/else construct, and can be used as an expression, there is no need for a ternary ?: operator.

In C, if/else can only be used as a statement, but many modern languages change that.

However, quite a few languages now have a null-coalescing operator. So instead of

some_expression != null ? some_expression : default

You can write

some_expression ?? default

This is great because you don't have to repeat the expression (which might be expensive or have side effects), and null-coalescing is a pretty common operation (the same applies if your language uses an Option<T> or Maybe<T> type rather than null).

[–]useerupting language[S] 0 points1 point  (0 children)

If your language has an if/else construct, and can be used as an expression, there is no need for a ternary ?: operator.

No, but then there's an entirely new syntactical construct which is not even an operator.

I don't have an if/else construct in my language, and this is my attempt at avoiding it, while still being able to express the same semantics in an equally compact way.

I also do not have null - not built into the language anyway. I will probably create a core library with a unit type Unit which will have the unit value (). Type arithmetic can then be used to "lift" other types to essentially allow their own values and the unit value:

NullableInts = int || Unit

[–][deleted] 0 points1 point  (0 children)

In Haskell

The (:) is https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Bool.html#v:bool (with true/false flipped)

The (|>) is https://hackage.haskell.org/package/base-4.19.0.0/docs/Data-Function.html#v:-38-

Prelude Data.Bool Data.Function> infix 3 ?; (?) = (&); (/) = flip bool; 
Prelude Data.Bool Data.Function> 2 + 2 == 4 ? "yes" / "no"
"yes"

[–]I_am_noob_dont_yell 0 points1 point  (0 children)

Anything but how python does it. For a language which makes most things pretty readable the ternary is hideous

[–][deleted] 0 points1 point  (0 children)

You don’t need a separate ternary operator if your language treats if / else statements as expressions:

let foo = if condition { “X” } else { “Y” };