[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)