[HN Gopher] A List of Ternary Operators
       ___________________________________________________________________
        
       A List of Ternary Operators
        
       Author : azhenley
       Score  : 37 points
       Date   : 2024-11-05 18:49 UTC (4 hours ago)
        
 (HTM) web link (buttondown.com)
 (TXT) w3m dump (buttondown.com)
        
       | xn wrote:
       | What about subject-predicate-object triples?
        
       | readthenotes1 wrote:
       | Binary operators don't "go between" 2 operands, they just consume
       | two operands.
       | 
       | + 2 3
       | 
       | Is a perfectly valid binary operator.
        
         | BerislavLopac wrote:
         | This falls into the third category, "Functions/methods that
         | prefix any number of arguments". That the binary operators "go
         | between" the operands is literally the definition used in the
         | article.
        
       | mjw1007 wrote:
       | JavaScript method call notation is arguably a ternary operator,
       | or a candidate for the "Near Misses" list (because << foo.bar(x)
       | >> isn't the same as << var meth = foo.bar; meth(x) >>).
        
         | marcosdumay wrote:
         | Javascript's `this` is the gift that keeps giving...
        
         | sltkr wrote:
         | Not technically ternary because the number of arguments is
         | variable, but definitely an interesting case.
         | 
         | `a.b(c)` is actually a bit of a degenerate case because `b`
         | must be an identifier, it cannot be an arbitrary expression,
         | which seems to disqualify it as a true operator. But actually
         | `a.b` is just syntactic sugar for `a["b"]` and you can view
         | []() as an operator, so you can write nonsense like:
         | [function(){return this[1]}, 42][0]()
         | {foo(a){return this.x*a; }, bar(a){return this.y*a;}, x: 1,
         | y:2} [ Math.random() < 0.5 ? 'foo' : 'bar' ] (100 + 20 + 3)
        
       | Cieric wrote:
       | I'm not sure I'd say that it can't be done. In some languages it
       | could be implemented if the language itself supports optionals.
       | So "bool ? x" could result in an Optional<type(x)>, while
       | "Optional<type(x)> : y" could be the typical implementation of
       | value_or that is found on optionals.
       | 
       | Whether it's a good idea to implement it that way is something I
       | haven't come to an opinion on yet. My only argument is that
       | "can't be decomposed" feels like it's a little to far reaching.
       | 
       | Edit: to add on to this, while it's not really a ternary operator
       | in the sense he seems to be looking for. It it common for
       | assembly to have fma instructions. But this feels like a really
       | weird edge case since x * y + z technically can be decomposed
       | into the 2 separate operators, but the fma instruction itself
       | can't be decomposed without losing precision.
        
         | Someone wrote:
         | > I'm not sure I'd say that it can't be done. In some languages
         | it could be implemented if the language itself supports
         | optionals. So "bool ? x" could result in an Optional<type(x)>,
         | while "Optional<type(x)> : y" could be the typical
         | implementation of value_or that is found on optionals.
         | 
         | It is tricky because, in                 flag ? x : y;
         | 
         | exactly one of _x_ and _y_ gets evaluated.
         | 
         | So, _flag ? x_ would have to evaluate _flag_ first, and only
         | evaluate _x_ if that has a truthy value.
         | 
         | That can be tricky in many languages, for example if the
         | programmer writes                  (b [?] 0) ? a / b : 0;
         | 
         | You'd need lazy evaluation of arguments or something similar.
        
       | pistoleer wrote:
       | After some thinking, the ternary conditional operator can be
       | decomposed into 2 composing binary operators like such:
       | 
       | ? takes a bool, a T, and returns option<T>
       | 
       | true?b == Result b
       | 
       | false?b == None
       | 
       | : takes an Option<T> and a T and returns T
       | 
       | Result x : y == x
       | 
       | None : y == y
       | 
       | However, in most languages (looking at you php) the ?: act as a
       | type of parenthesis: in a?b:c, any expression goes into b, no
       | matter it's precedence.
        
       | taeric wrote:
       | Agreed with a sibling that this is somewhat abusing the
       | terminology. In/pre/postfix of the operators is a different thing
       | from how many terms an operator acts on. Indeed, in RPN, it was
       | not uncommon to try and execute an operator but not have enough
       | terms on the stack.
       | 
       | Seems that we typically call the ?: combination a ternary
       | operator largely stems from the distinction between operators,
       | functions, and keywords. One that we largely don't keep in a
       | consistent fashion. Add in functions versus methods, and things
       | get really fun.
       | 
       | Programming also has the odd case where functions have an easy
       | syntax to say that there are multiple inputs, but we don't have a
       | common syntax to say there are multiple outputs. Which is
       | something that is quite common in every domain.
        
         | reichstein wrote:
         | I'm personally convinced that the reason the conditional
         | operator of called "the ternary operator" it's that the ANSI C
         | Programming Language book contains the phrase "the ternary
         | operator, ?:", and a lot of readers didn't know what "ternary"
         | meant and thought it was a name.
        
           | taeric wrote:
           | This sounds reasonable to me. I used to think you could guess
           | what someone's first programming language was based on if
           | they wrote methods or procedures. :D
        
       | zarzavat wrote:
       | clamp and mix are good candidates. Ternary operator syntaxes are
       | rarer than ternary operations, because there's not much that can
       | justify being so syntactically indulgent.
       | 
       | Conditionals are common enough that they can justify the
       | indulgence, but even then I prefer an if-else expression syntax
       | over ?:, it generalises better to other constructs such as
       | pattern matching.
        
         | jcparkyn wrote:
         | > Conditionals are common enough that they can justify the
         | indulgence
         | 
         | I think there's another much more important factor that
         | distinguishes conditionals from most other ternary operations
         | (clamp, mix, FMA, etc): The "conditional evaluation" part,
         | which is what makes it hard to replicate with a regular
         | function call.
        
       | kragen wrote:
       | Maybe it was Dave Long that pointed out that lerp is a
       | generalization of the conditional ternary operator. When the
       | condition is 0, it returns the from-point, and when it's 1, it
       | returns the to-point, but if you generalize it to the whole real
       | number line, you lose nothing semantically except for short-
       | circuiting. Lerp is a pretty common _function_ in graphics--the
       | simplest way to understand Bezier splines is as recursive lerping
       | --but for whatever reason it generally isn 't written as an infix
       | operator, which seems to be what Hillel is looking for here.
       | 
       | I think that Hillel's criterion that the ternary operator "can't
       | be decomposed into two sequential binary operators" is too
       | demanding. He says, " _bool ? x_ makes no sense on its own, nor
       | does _x : y_ ". Well, that's just because C's grammar is written
       | such that they won't parse. If you extend the grammar to permit
       | them, it's not very hard to extend the semantics as well. For
       | example, you could permit every expression to either succeed,
       | yielding a value, or fail, yielding no value; languages that work
       | in something like this way include regular expressions, META-II,
       | Icon, Unicon, and Prolog, though each of them deviates from it in
       | detail. Then, to get the C/JS semantics, _bool ? x_ succeeds with
       | the value of _x_ if _bool_ is true, or fails if _bool_ is false,
       | and _x : y_ succeeds with the value of _x_ if _x_ succeeds, but
       | if _x_ fails, does _y_.
       | 
       | You could model the exception mechanism in languages with
       | exceptions as failing to return a value, in which case _x : y_
       | ends up being a general-purpose exception-handling mechanism, so
       | you can say something like _total[k] = (total[k] : 0) + 1_ to
       | increment a possibly-nonexistent hash key. Mark Lentczner 's
       | language Wheat has an operator like this, spelled _!!_ , which I
       | stole for my own language Bicicleta.
       | 
       | (That's assuming you parse _bool ? x : y_ as _(bool ? x) : y_.
       | You can also invent semantics that give you the right behavior
       | with the parsing _bool ? (x : y)_ but the ones that occur to me
       | seem less appealing.)
       | 
       | Similarly for Elixir's stepped range 1..10//2. Hillel says, "This
       | isn't decomposable into two binary ops because you can't assign
       | the range to a value and then step the value later." Well, maybe
       | not (I don't know Elixir), but it seems clear enough what _r //2_
       | should do if _r_ is the name of a range value: it should produce
       | a new range stepping either by 2 or by twice the previous step.
       | 
       | C++ does something like this for Hillel's (or munificent's)
       | example of _a[b] = c_ : the expression _a[b]_ evaluates to a
       | reference, which in rvalue context decays to an ordinary value,
       | but which can indeed be assigned to a variable. Unfortunately,
       | C++ 's reference semantics are somewhat weak, with the
       | consequence that actually overriding _operator[]_ in C++ to
       | return a reference is pretty bug-prone, because you can
       | autovivify things you didn 't want to autovivify.
       | 
       | By Hillel's apparent syntactic and decomposability criteria, I
       | think every single two-argument method in Smalltalk is a "ternary
       | operator". _1 to: anInt do: [ :i | r := ( r * self ) ]_ , for
       | example, or _arr at: i put: v_. As he sort of points out,
       | Objective-C inherited this from its Smalltalk roots, but he sort
       | of throws up his hands at it.
        
         | n_plus_1_acc wrote:
         | > Hillel says, "This isn't decomposable into two binary ops
         | because you can't assign the range to a value and then step the
         | value later." Well, maybe not (I don't know Elixir), but it
         | seems clear enough what _r //2_ should do if _r_ is the name of
         | a range value: it should produce a new range stepping either by
         | 2 or by twice the previous step.
         | 
         | This is exactly how `1 to 10 by 2` works in scala.
        
       | Etheryte wrote:
       | The whole article hinges on the fact that the author seems to be
       | unfamiliar with the term arity (?), which feels weird given
       | they're clearly familiar with a number of programming languages
       | etc. The definitions for unary, binary, etc are rather arbitrary
       | and not how they're usually used.
        
         | hwayne wrote:
         | I am amazed that I somehow didn't use the word arity _once_ in
         | the email.
         | 
         | That said, I don't believe I'm using the terms unary and binary
         | wrong. "Unary" historically meant "a math operation that takes
         | one parameter" and "binary" meant "operation that takes two".
         | An algebraic group, for example, is defined as a set and a
         | binary operation that follows certain properties.
         | 
         | arity is a way of generalizing unary/binary to arbitrary
         | parameters. It is equally correct to say that `+` is a binary
         | operation and to say that it is a 2-arity operator. It's like
         | how "nth power" generalizes "square" and "cube": "9 cubed" is
         | the same as "93".
        
           | reichstein wrote:
           | Agree, other than that I wouldn't use "2-arity". Maybe
           | "2-ary", but that's just "binary" written confusingly. It
           | works for "n-ary".
           | 
           | I'd rather say that a binary operator _has_ an arity of 2 or
           | talk about the arity _of_ an operator.
        
         | yen223 wrote:
         | "Unary" and "binary" were used exactly how they are used when
         | talking about programming languages.
        
         | NooneAtAll3 wrote:
         | > The whole article hinges on the fact that the author seems to
         | be unfamiliar with the term arity (?)
         | 
         | how does it hinge tho?
        
       | lilyball wrote:
       | When talking about J forks, the author says they don't know how
       | to parse `x (f1 f2 f3 f4 f5) y`. After reading the relevant
       | documentation, I believe this parses as                 (x f1 y)
       | f2 ((x f3 y) f4 (x f5 y))
        
       | zusammen wrote:
       | I'm surprised not to see fused multiply-add here:
       | FMA(x,y,z) ~ x += y*z
       | 
       | You see it all over the place in numerical computing and deep
       | learning.
        
         | achierius wrote:
         | It'd be syntactic sugar for two binary operations: `tmp = y*z;
         | x += tmp`
        
           | atq2119 wrote:
           | It actually isn't, that's what makes it fused.
           | 
           | If you separate the operations like that, the intermediate
           | result is rounded. The final result is then often different
           | from the result of the FMA, which doesn't do the intermediate
           | rounding.
        
           | NooneAtAll3 wrote:
           | in hardware it isn't sugar - there's (pipeline) gains to be
           | gained from literally "fusing" the two into a single circuit
        
       | bediger4000 wrote:
       | In Tarski's axiomatization of plane geometry, there's a
       | betweenness triadic relationship, Bxyz, point y is between x and
       | z.
        
       | zokier wrote:
       | (postgre)sql has lots of quirky stuff that kinda qualifies. For
       | example                    substring ( string text [ FROM start
       | integer ] [ FOR count integer ] ) - text
       | 
       | at a glance you might think that as just a normal function call,
       | but the arguments do not follow the regular function call
       | argument syntax, instead it has this special sauce ternary
       | syntax.
        
       | seanhunter wrote:
       | What about the derivative operator in maths? When you write say
       | d^nf/dx^n in Leibniz notation (or the equivalent in Lagrange
       | notation f^(n)(x)), you have 3 arguments. The "f" is the function
       | you are taking the derivative of, the n is what derivative to
       | take and then the (x) is what to name the argument in the
       | resulting function.
       | 
       | Obviously in the common case you're just writing something like
       | f'(x) or f''(x) where the ' stands for ^(1) but it's still a
       | ternary operator.
       | 
       | Another common example in maths is the "limit" operator where you
       | have lim_x->a <someexpr> and the arguments are x, a and someexpr.
        
         | taeric wrote:
         | The article touches on sigma notation and such. So, I would
         | expect that is largely how they would treat it.
         | 
         | The pitfall you will run into quickly, is that we have very
         | loose definitions between operations and functions. Is akin to
         | wanting a sharp distinction between expressions and terms or
         | statements. There are widely accepted distinctions that
         | typically work, but this is largely convention based with no
         | real intrinsic quality that defines them.
        
         | crdrost wrote:
         | On the one hand the n'th derivative operator is clearly a
         | binary operator that you are composing with a function
         | application operator.
         | 
         | On the other hand, OP seems to have not noticed that the
         | ternary conditional also has this structure. That is, `f : x`
         | could be syntactic sugar for `f(() => x)` [or perhaps `f(p =>
         | p, () => x)` if you like Church encodings] and then there are a
         | few options for meanings of `x ? y` that have `(x ? y) : z` as
         | a conditional.
        
       | not2b wrote:
       | Verilog has a sliding-window part select, which produces a slice
       | that has a fixed width. It's an lvalue expression (it can be
       | assigned to). It's ternary.
       | 
       | array[addr +: W]
       | 
       | or
       | 
       | array[addr -: W]
       | 
       | The former chooses W elements beginning at addr, the latter
       | chooses W elements ending at addr.
        
         | o11c wrote:
         | GDB has `arraylike[start]@length`. But `arraylike[start:end]`,
         | where `end=start+length` is more common across languages.
        
       | ks2048 wrote:
       | Like others here, I find the way this article discusses things a
       | bit odd. I think a more interesting question is to emphasize
       | SYNTAX (the word doesn't seem to appear in the article). Which
       | languages have special syntax that maps to an AST node as
       | OP(a,b,c)? Focus on syntax and things like range(start,end,step)
       | dont fall into this category.
        
         | plaidphantom wrote:
         | I don't think they're saying that such a range function is
         | itself a ternary, but using that as a lead-in to discussing the
         | Frink ("1 to 100 step 15") and Elixir ("1..10//2") syntaxes.
        
       | plaidphantom wrote:
       | How about SQL joins? "foo JOIN bar ON foo.id = bar.fooid"
        
         | recursive wrote:
         | If that counts, how about common table alias declarations?
         | 
         | WITH a AS b c
        
       | o11c wrote:
       | It mentions ranges, but not slices?
       | 
       | Note that the "conditional/selection operator" is normally
       | thought of as a logical operator, but variants exist that operate
       | bitwise `r = (if_false & ~condmask) | (if_true & condmask)` or on
       | a vector. Additionally, there maybe a special versions that
       | operate at compile-time instead of runtime.
       | 
       | Modular exponentiation is commonly implemented as ternary for
       | major performance improvements, even though technically it can be
       | done as composition of two binaries.
       | 
       | Modular congruence `3 === 1 (mod 2)` is common in math.
       | 
       | Many arithmetic and bitwise operations take a third argument for
       | the carry bit. Rotates are notable for actually performing a
       | different operation in that case.
       | 
       | There are various SIMD operations.
       | 
       | There's also SSA.
        
       | karmakaze wrote:
       | I rather like the Smalltalk message selector structure
       | Boolean>>ifTrue:ifFalse:                 (cond) ifTrue: [then-
       | block] ifFalse: [else-block]
       | 
       | For some reason the way Swift handles syntax I find less
       | memorable or composable.
        
       ___________________________________________________________________
       (page generated 2024-11-05 23:01 UTC)