What would you like instead?
1. Errors quickly lose their usefulness if you simply pass them up the stack. Even just on the surface, if you don't even know where the error came from, good luck making sense of it. Other languages have tried to avoid that problem by having things like "sidecar" handlers that can handle the error elsewhere, but in the end that's just moving code around. You haven't actually solved the overhead of needing to do something with the error.
2. Errors simply passed up the stack are, more often than not, going to leak implementation details. Consider a function that fetches data from a SQL database, where the underlying operations produce a "SQLNoResults" error. If you let that flow through, callers are going to start to rely on it. Now, imagine new requirements dictate that you need to fetch the data from an HTTP service instead. If you continue to simply pass the error along, now callers are going to get a "HTTPNotFound" error instead, breaking their usage. Not a good situation.
2. There are places where you want to abstract your errors, but those places are not every function call or even most function calls.
Do you mean often you want multiple segments of code to all do the same thing on error? If there is only one segment then you well and truly have just moved things around.
Multiple segments all doing the same thing on error would give more justification to centralizing functionality, but at the same time if you have multiple segments of code all doing the same thing you've probably not thought your design through. Papering over design mistakes with language features is commonly done, but I'm not sure it is something to strive for.
> but those places are not every function call or even most function calls.
If the original error is your own you don't need to abstract it, but if you are passing your own errors through multiple levels of indirection you've, again, probably not thought your design through very well. Papering over design mistakes with language features is commonly done, but I'm not sure it is something to strive for.
If a segment has 6 function calls and you want the same error handling for each one, you can't get rid of the boilerplate with the current language.
> If the original error is your own
Assume the original error is not my own then.
My deepest sympathies for the person who has to respond to the resultant error once you've collected your paycheque and have moved on to the next project. Which of the six functions produced the error? Nobody knows. That may be all well and good for a contrived internet comment example, but if you write real code like that someone's life is soon going to become a living hell.
Every other language has recognized that you can't have the same error handling for each of the function calls. Even where they have special error handling semantics have special ways to ensure that the handling is different in each case. Why do you think it would work in Go?
> Assume the original error is not my own then.
Then you've forever hitched your horse to their code. A better or more performant replacement comes along in the future and you want to use it instead? Too bad. You can't without breaking your own API – and for what reason?
But, okay, we accept that you like to live life on the edge (or come from the Javascript world and thus don't know any better) and if the people using your functions start having breakage, too bad so sad. However, if you're just passing values through from another package, why are you really offering your callers in the first place? Why don't they just use the other package directly?
> Then you've forever hitched your horse to their code. A better or more performant replacement comes along in the future and you want to use it instead? Too bad. You can't without breaking your own API – and for what reason?
No, I did not say that. What I said is that the place to prevent that is not every function call. If my code goes 4 functions deep, I need at least one of them to handle errors I didn't cause, or convert them into my own errors for the sake of a stable API. But many of the other functions can pass errors through.
If it only calls one function and isn't part of the public API it is likely that you can get away with it. There is a time and place for that, but if that time and place is most of the time like your earlier comment indicated and something that can be counted as many in this comment... I'd like to see this codebase because I am highly skeptical that it is something anyone would ever want to work on[1].
If it calls two or more functions, then you're back to the "which function was it?" problem.
[1] And, as it happens, Google actually commissioned a study on how frequently that kind of code is actually written based on open source projects and other code they had access to when evaluating an error handling proposal. They found it to be an unusual case. It being "most" or "many" is definitely limited to within your works, not something applicable in general. There just might be a reason why your ways haven't caught on, but thrill me!
Nope, not in languages where you have a stacktrace attached.
> Errors simply passed up the stack are, more often than not, going to leak implementation details.
That's why in a good language you can just wrap them at the right level of abstraction.
Like you say, the stack trace needs to be attached. For that you, at very least, need a "sidecar" handler if not done so in the same execution path, as we discussed earlier. Did you, uh, forget to read the thread?
> That's why in a good language you can just wrap them at the right level of abstraction.
You can move the logic around, but you can't avoid it, as we discussed earlier. Did you, uh, forget to read the thread?
I just read it again, but I'm not sure what you mean. And sorry, I'm not familiar with that terminology (sidecar) but from the perspective of the user/developer, does it matter? As system or library developer, I don't need to do anything - I'll have the stacktrace available when I need it. There is extra code necessary. (one has to be mindful of the performance, but that's it)
> You can move the logic around, but you can't avoid it, as we discussed earlier. Did you, uh, forget to read the thread?
Doesn't have to do anything with moving logic around.
Let's say you are function foo and you call other functions and one of them is bar and it will fail with barError. Then, to avoid breaking your (= foo's) consumers if bar changes its internals, you simple wrap bar's error with your own. That can be as simple as doing `bar.mapError(barError -> fooError(cause = barError))` and that's it.
That is all I wanted to say.
Then, depending on the language, you stil don't have to repeatedly do "`if err != nil {}`" or so. There are enough alternatives, e.g. monadic error handling like in Haskell or macros like in Rust.
You were familiar with it earlier – you couldn't have sensibly replied otherwise. How did you manage to lose it in the meantime?
> That can be as simple as doing `bar.mapError(barError -> fooError(cause = barError))` and that's it.
At the end of the day is that really any different than: `err = errors.Join(MyError{}, err)`?
But you've still just moved logic around (e.g. into mapError/Join). You've not changed what needs to be done.
In the end, everything is machine code. You tell me if that is any different or not.
> But you've still just moved logic around (e.g. into mapError/Join)
To improve backwards compatibility, yeah. Somehow the error needs to be changed.
But: with a stacktrace and a good language, this is a single line of code. No if/else etc. needed, even in the case of multiple different errors in different places in foo.
And my impression was that this is what we were discussing here - ergonomics of error handling.
Code is ultimately written for humans, not machines. If we only cared about the machine you could flip toggle switches and not worry about all these pesky human problems found in understanding code.
> You tell me if that is any different or not.
I don't think there is. But I may have missed your intent. The question was posed to ensure that we are on the same page. If you leave it up to me, we are on the same page, which means your earlier comment really doesn't work. There is no `if err != nil` to be found.
> But: with a stacktrace and a good language, this is a single line of code.
Why can't it be a single line of code in Go? In fact, at one point Go even did include the stack trace in that single line of code in some pre-release work, but real-world usage determined that nobody ever used it (all the information you need is already there without a stack trace!), so it was stricken before final delivery. You can still do it yourself if you want, though. Errors are not magic.
Because Golang has (to my knowledge) no support of any syntax that supports that.
You are a bit hard to discuss with, but I want to show good will, so I'll try to explain and hope you can appreciate that! :-)
Golang (just like most, but not all!) languages has one default way of doing things. Which is: execute each line (or statement / expression) sequentially.
That's why you can write `loadMissiles(); fireMissles()` and it works.
But it could be different. Imagine a language where each of those is, by default, executed in parallel. There are academic languages that actually work like that.
How would you then do something sequentually? By rewriting your code: `var result = loadMissiles(); fireMissles(result)`. This is a semantical enforcement of sequential execution.
Now let's change this a little bit and add a `.then()` method onto every value (even `null` if the language has that). Then we rewrite the code:
`loadMissiles().then(result -> fireMissles(result))`.
Looks familiar? If we add builtin error-handling then we just have re-invented javascript promises and this is not a coincidence.
Now, there is a duality to that - executing code independent of each other, so non-sequential. (whether it is actually run in parallel or not does not matter, as long as the outcome is the same, minus performance implications of course).
How would one do that? By adding a new method, let's call it `all()` that accepts a list of expressions. Unlike methods like .fold or .reduce, there is no way for the elements inside the list of expressions to interact with each other. That means even in a language that is "sequential by default" these expressions can (or could) be executed in parallel without a problem. This is basically Promise.all() in javascript.
Two more final steps.
First step: we have now invented promises (including sequential and non-sequential execution) which describe asynchronous computations. But how about other things? Let's think of results. They are similar - sometimes we need a successful result to continue (sequential) sometimes we can execute logic non-sequential. How about optionality? Well, it's basically like a result where the error has no information, so same thing. How about parsers? Sometimes we can need to parse something and then we decide how to keep parsing based on the result (sequential) - sometimes we can parse multiple things non-sequential. What about resources? Sometimes we need a database connection to open a network connection (sequential). Sometimes we can do both non-sequential.
And so on. See the pattern? Let's call those things "contexts" and then allow developers to define those contexts themselves, because we certainly can't foresee all contexts that exist in the world. Certain things are necessary to allow to do that, including some kind of parametrism (like generics).
Second step:
Now that we have those contexts, we can use them. But it would be nice to write code in the same way as "normal context" code (whatever that means for our language). So we should have some syntax to help with context switches - optimally for both sequentual and non-sequential logic. And optimally generalized and not specialized to single contexts.
Different languages have different strategies for the second step. Golang doesn't have anything like that (well, to my knowledge, I'm not a golang dev). It certainly doesn't have a generalized version though, that is for sure.
Therefore to come back to:
> Why can't it be a single line of code in Go?
The answer is, because it lacks the syntax in the second step and - to my knowledge - the way to define contexts (at least typesafe ones, my unsafe ones are possible) and in particular the syntax to deal with them (without having to call .then() or - worse - if/else).
The single line was already demonstrated...
> That's why you can write `loadMissiles(); fireMissles()` and it works.
Maybe.
func loadMissles() {
go func() {
// Do the things.
}()
}
Maybe not.Get back to us when you gain at least a surface understanding of how computers work.
> See the pattern?
All that just to convert one type/value to another? That is complete and utter insanity.
Did you write this piece before reading the thread and decide to arbitrarily dump it upon us, totally oblivious to what is happening around you, to satisfy your sunk cost fallacy pangs?
value, err := function()
> if there is an error return it
if err != nil { return err }
> otherwise give me the value
// rest of the code goes here
Not really.
If I want to do foo().bar().baz() it expands to six lines.
Rust's `?` operator on Result<T,E> types is flipping fantastic, puts all of the following to shame.
// can forget to check err
thing, err := getThing()
if err != nil {
panic(err)
}
// More verbose, now you could possible forget to assign thing
var thing Thing
if t, err := getThing(); err != nil {
panic(err)
} else {
thing = t
}
// What I end up doing half the time when I've got a string of many
// calls that may return err as a result of this
var whatIActuallyWant string
if first, err := getFirst(); err != nil {
return err
} else if second, err := doWith(first); err != nil {
return err
} else if final, err := doFinally(second); err != nil {
return err
} else {
whatIActuallyWant = final
}
It's actually to the point that in quite a few projects I've worked on I've added this: func [T] must(value T, err error) T {
if err != nil {
panic(err)
} else {
return value
}
}Isn't that what your tests are for? Linters aren't normally intended to stop you from creating undefined behaviour.
It is not like Rust negates the need for those tests. Remembering to handle an error is not sufficient. You also need to ensure that you handle it correctly and define a contract to ensure that the intent is documented for human consumption and remains handled correctly as changes are made. Rust is very much a language designed around testing like every other popular language.
What you do need to do is document how the function is intended to behave. If, for example, your function opens a file, you need to describe to other developers what is expected to happen when the file cannot be open.
"The compiler won't let me forget to handle the error" is not sufficient to answer that. That you need to handle the error is a reasonable assumption, but upon error... Should it return a subsequent error? Should it try to open a file on another device? Should it fall back to using a network resource? That is what you need to answer.
And tests are the way to answer it. It is quite straightforward to do so: You write a test that sees the file open failure occur and check that the expected result happened (it returned the right error, it returned the right result from the network resource, etc.). Other programmers can then read your example to understand what is expected of the function. This is as necessary in Rust as it is in Go as it is in any other language you are conceivably going to be using. Otherwise, once you are gone, how will anyone ever know what it is supposed to do? As changes occur through the ongoing development cycle, how will they ever ensure that they haven't broken away from your original intent?
So, once you've written the necessary tests – those that are equally necessary in Rust as in any other language – how, exactly, are you going to forget to handle the error? You can't! It's impossible.
I don't know why this silly thought persists. It is so painfully contrived. If one is a complete dummy who doesn't understand the software development process perhaps they can go out of their way to make it a problem, but if one is that much of dummy they won't be able to grasp the complexities of Rust anyway, so...
type errHandler struct {
err error
}
func (eh *errHandler) getFirst() string {
// stuff
if err { eh.err = err }
return result
}
func (eh *errHandler) doWith(input string) string {
if eh.err != nil {
return ""
}
//stuff
if err { eh.err = err }
return result
}
func (eh *errHandler) doFinally(input string) string {
if eh.err != nil {
return ""
}
//stuff
if err { eh.err = err }
return result
}
func (eh *errHandler) Err() error {
return eh.err
}
func main() {
eh := &errHandler{}
first := eh.getFirst()
second := eh.doWith(first)
final := eh.doFinally(second)
if err := eh.Err(); err != nil {
panic(err)
}
} func foo() (final int, err error) {
defer func() {
if e, ok := recover().(failure); ok {
err = e
} else {
panic(e)
}
}()
first := getFirst()
doWith(first)
final = doFinally()
return
}
encoding/json does it. It's okay if you understand the tradeoffs.But look at what you could have wrote:
func foo() (int, error) {
first, err := getFirst()
if err != nil {
return 0, ErrFirst
}
err = doWith(first)
if err != nil {
return 0, ErrDo
}
final, err := doFinally()
if err != nil {
return 0, ErrFinally
}
return final, nil
}
This one is actually quite nice to read, unlike the others, and provides a better experience for the caller too – which is arguably more important than all other attributes. func foo() (int, error) {
first := getFirst()?
doWith(first)?
return doFinally()
}
or this: func foo() (int, error) {
first := getFirst() % ErrFirst
doWith(first) % ErrDo
return doFinally() % ErrFinally
}
The first one is a significant upgrade over the exception version. It cuts out half the code and makes the early return points explicit.I think something similar to the second one is also nice to read, and it gives the same improved experience to the caller as your suggestion.
Albeit a contrived suggestion for the sake of brevity. In the real world you are going to need to write something more like:
first, err := getFirst()
var err1 *fooError
var err2 *barError
switch {
case errors.As(err, &err1):
return nil, FirstError1{err1.Blah()}
case errors.As(err, &err2):
return nil, FirstError2{err2.Meh()}
case errors.Is(err, io.EOF):
return nil, EOF{}
// ...
case err != nil:
return nil, FirstError{err}
}
And that is where eyes start to gloss over. The trouble with errors is that they quickly explode exponentially. Programmers long to distill all possible errors into one logical operation to not have to actually think about all the cases, since that is hard and programmers are lazy, but that is not sufficient for a lot of programming problems.The cutesy shortcuts like ? and % operators are fine for some classes of programming problems, to be sure, but there are numerous languages that are already designed for those classes of problems. Does Go even need to consider travelling into those spaces? In the original Go announcement it was made explicitly clear that it was designed for a very particular need and was never intended to be a general purpose programming language.
I'm certainly not the gatekeeper. If Go wants to move away from its roots and become the must-have language for the classes of problems where something like ? is a wonderful fit, so be it. But, from my point of view, putting energy into tackling the big problems is more interesting. There should be plenty of room for improvement in the above code without losing what it stands for. But that is going to require a lot more deep thought than I've seen put in and programmers are lazy, so...
> Does Go even need to consider travelling into those spaces?
Oh come on. Changing how one common piece of boilerplate is written is not travelling into new spaces or moving away from Go's roots.
I didn't know about this trick, thanks for sharing.
Do people actually do this? Is it included in the standard library? If not, should it be?
PHP also ignored errors out of the box (even has @ to suppress error output), but error_reporting(-1); is basically how every sane framework starts, and PHP 8 set the default level to E_ALL [1](in 5.3 accessing undefined variable was also ignored (notices - as far as I remember)[2])
Python and NodeJS by default aborts on error. (Python had exceptions before user-defined classes in the last millennia. [3])
...
Rob Pike said in 2015 "don't just check errors, handle them gracefully." [4]
I think Scala/ZIO has the most powerful and comprehensive and compact [5] error handling machinery that I have experience with, that's where it's the easiest to live up to Mr Pike's admonition.
...
All are fine. I hope/predict Go will pull a Bash or PHP and will introduce flags to turn some/most unchecked errors into aborts. (Or perhaps people will build tooling and alternative stdlib eventually.)
Result types are nice because it allows library developers to encode a shitton of really useful semantic information into them, and it's really easy to handle them however the downstream user wishes. (Rust offers .unwrap(), TypeScript ! (assume non-null), and JS itself has the ? optional chaining operator.)
[1] https://php.watch/versions/8.0/error-display-E_ALL
[2] https://stackoverflow.com/a/69291454/44166
[3] https://python-history.blogspot.com/2009/03/how-exceptions-c...
First, to understand how to handle errors differently, you have to understand how errors are different.
Like, is a person's age an error? Your gut reaction is almost certainly "What? No. A person's age isn't an error." Yet soon enough you're writing age verification checks like: if age < 18 { /* not an adult */ }. if age < 21 { /* not old enough to drink in the USA */ } – with all the exact same problems if err != nil {} has. Clearly it is an error in certain contexts.
Keep going and you start to wonder what branching situation isn't error handling. So, really, it seems to me what we really want is a better way to express branching operations. "if" is one of the earliest additions to programming languages, so it stands to reason that it is getting a little long in the tooth.
The effort to improve error handling is clearly there. Core team member Ian Lance Taylor submitted a new proposal and built a reference implementation just within the last few months. There have been ~200 error handling proposals! It is a super hard problem, though. A "tiny bit more" thinking is not sufficient.
They could've at least looked at what other languages were doing at the time, and that would have been much better than what they ended up with. However, as with many other aspects of Go, the creators ignored existing work and went with what seemed like improvements to problems from decades ago.
Other languages at the time were doing the exact same thing, except, maybe, wrapping the error in a monad such that you have to check the error before doing anything with value, but still with "if" or an if-like construct.
But that is unnecessary in Go as it believes that values should always be useful – which means you don't need to even consider the error to use the value.
That's how you get garbage (but valid) values fed as input to your code, eventually resulting in garbage output produced on the other end of the pipe. Always fun to debug.
If you face a function author who doesn't know what the hell they are doing and, as a result, foolishly feeds you garbage, then yes, it is possible for that garbage to propagate unknowingly. But when faced with a function author that doesn't know what the hell they are doing, that's going to be a problem no matter what. No language construct ever conceived can save you from someone who doesn't know what the hell they are doing. They will do foolish things in every language ever created. -- That is not a reasonable position.
If a function is written sensibly, the value can never be garbage. How could it be?
First things first, how can you read from a file that doesn't exist? Even you questioned that later, so this is a strange question. – Do you mean if you try to open a file that doesn't exist? nil would be a reasonable value. It is how Go signifies the absence of something. nil is always useful and with `if file == nil` will tell you that the file isn't there. No need to observe the error.
If you need to know why the file isn't there then, sure, you're also going to have to look at the error. Sometimes that is important. In the case of opening a file you realistically will need to know why it failed to open so that you can resolve the condition, but in other cases you don't need to worry about why it failed. Checking the error would be unnecessary in those cases, assuming the function author has some understanding of how to make even half-decent APIs.
Let's assume here, for the sake of discussion, that you have no need to know why the file was unable to opened. Again, how do you envision garbage propagating into the rest of your program?
But also, let's say the read function in question is named ReadInt32. Then what does it return on error?
Is this true? Are values always useful? You shouldn't even need to consider the error?
For example, if GPS errored while calling an Uber, is it useful to ignore the error and instead display the Uber driver as being in the middle of the ocean? https://www.reddit.com/r/uberdrivers/comments/zjpv69/well_ho...
There is a fairly wide-adopted and battle-proven pattern. It's called monads. Basically, you put a "context" over parts of your code and then some type of branching becomes implicit.
Good languages generalize it and allow their developers to create and use their own contexts. Other languages at least have special cases for very common cases. async/await is such an example.
If by fairly wide-adopted you mean it is adopted in a handful of language nobody uses, sure.
> Other languages at least have special cases for very common cases.
Where they usually screw it up. Look at the canonical example of a "monad" in Rust:
let result = divide(3.0, 2.0);
match result {
Just(x) => println!("Answer: ", x),
Nothing => println!("division failed; we'll get 'em next time."),
}
That is just "if" by another name!I ask because it's very much not "canonical," Just and Nothing are Haskell terms, not Rust terms. You'd expect at least Some and None.
Regardless, I do agree that in this specific circumstance, you're emulating an if. There's even a form in this case that emulates it even more closely:
if Some(x) = divide(3.0, 2.0) {
println!("Answer: ", x);
} else {
println!("division failed; we'll get 'em next time.");
}
But these simple cases don't show off where Option/Result truly shine.Sent one in: https://en.wikipedia.org/w/index.php?title=Monad_(functional... we'll see if it gets reverted. Honestly, I find this whole section kind of awkward. I fully agree with you that it doesn't really show off why this stuff works, and works well. Real-world code doesn't get written like this.
Please precisely define your criteria so that I can end this discussion by giving you a concrete counter example.
> That is just "if" by another name!
It appears you haven't understand the difference. Here's a counter example:
let first_result = divide(10, ???);
let second_result = divide(20, ???);
let final_result = first_result.or(second_result).unwrap_or(42);
println!("Result: {}", final_result);
Logic being, do two divisions and print the result of the first one, or if it failed, of the second one, or fall back to 42.We can go one with using lists of results, results of results and doing conditional calculations and so on and so on - and all of that without writing a single if.
Now do that in go without using if. Won't look as nice for sure.
You introduced the term. It is on you to define it. My stab in the dark to try and get us to an understanding may have failed, but that's where you would logically come in and explain yourself in better detail.
> so that I can end this discussion by giving you a concrete counter example.
A single counter example? I definitely would have never guessed that by wide-adoption you were thinking something singular. No wonder you've been so afraid to share your definition.
> Logic being, do two divisions and print the result of the first one, or if it failed, of the second one, or fall back to 42.
That too is just "if" with different syntax, and worse syntax – treading into Perl one-liner territory, but I concede that this particular example may not be a good basis on which to start from.
And my example is not "if". There is no if. Maybe there is "if" being used in the underlying functions? Again, eventually everything is machine code, but then every discussion about PLs is meaningless...
It doesn't literally include the keyword "if", but you've only rewritten an "if" statement in different syntax – and with worse syntax at that. It doesn't take things to a higher level.
But, again, I understand that this is probably not a good example on which to start from. We only ended up with it because it is a common snippet found elsewhere. How about you pick something that is a better demonstration?
> Again, eventually everything is machine code
Right, but we have higher level languages that enable expressing ideas beyond what the machine is directly capable of. The "if" statement itself is one such abstraction, albeit a fairly low-level one.
I don't understand what you mean. There is, to my knowledge, no different syntax for if-statements. In my code example there simply is no if being used.
Care to elaborate?
Can we agree that the ternary operator in C provides a way to write if-statements with different syntax?
> In my code example there simply is no if being used.
Assuming we do agree, there is no literal "if" being used when using the ternary operator. However, it doesn't take the concept to a higher level. It is still leans on the exact same conceptual if-statement expression, albeit written in a slightly different way.
The same is true of your code example.
I can neither agree or disagree because I don't know, but we are not talking about C and my code example didn't use a ternary operator either, so from my POV it doesn't matter.
> async/await was a better example. That moves something to a higher level of abstraction. However, it is not clear how to make that generalize
It is clear. Just not to you. Check Haskels do-notation or Scala for-comprehension or F#'s do!-notation and how they work. They all generalize over what async/await does (all with slightly different syntax though)
It matters because it is an attempt to explain what I am trying to convey. Technical people in particular tend to not share a common vernacular, so it is likely that I am using words in ways that do not match your understanding of them.
Communication is hard. I may have fallen short in my attempt, but surely we can try again? What do you hope to gain from this standoffishness?
> It is clear. Just not to you.
Fair enough, but that's the whole reason we're here, isn't it? To change that. If it were already clear, what purpose would the discussion serve?
So, let's go back to the opening comment with the age check problem. That is the explicit case where a higher level abstraction is being sought, and I think it serves as a good place to think about generalization without getting caught in the weeds.
I looked at Haskell's do notation and don't see how it helps. I prompted several LLMs in hopes that maybe they could resolve, but in every case they reversed back to using the exact same if-statements we started with. We're going to have to rely on your wisdom here.
> Fair enough, but that's the whole reason we're here, isn't it? To change that.
I'm afraid to tell you that I can't change that with my explanation. You will have to actually apply this in practice for a while. So long, that you have gotten used to it. And then compare it to how you work in golang. Because otherwise the code will just look unfamiliar to you and you'll potentially suffer from the blub paradox.
Of course this takes tons of time, so I can't expect you (or anyone) to do it. I can only tell how it looks from my POV since I've done it both ways.
I did. But you said it doesn't matter. Now it does matter?
> I'm afraid to tell you that I can't change that with my explanation.
No need for an explanation. Code will suffice. Perhaps I cannot write it myself, but I will eventually be able to understand it.
> Because otherwise the code will just look unfamiliar to you
That may be true in the first instant, but time marches forward and familiarity grows. This isn't an issue.
Is the real problem here that the code is so long and arduous that you don't have the time to write it? I don't think that satisfies the intent, if so. The idea is that the abstraction should be better than an if-statement, not the same or worse.
> Assuming we do agree, there is no literal "if" being used when using the ternary operator. However, it doesn't take the concept to a higher level.
So does it mean that you take back your original claim of "if in a different syntax" then and move the goalpost to "higher level"?
If so, I think "higher level" is too subjective and I'm tired of discussing this topic.
> No need for an explanation. Code will suffice. Perhaps I cannot write it myself, but I will eventually be able to understand it.
Then please do me a favor and explain the requirements again, because I'm not sure what exactly you have in mind for the age constraint(s).
> That may be true in the first instant, but time marches forward and familiarity grows. This isn't an issue.
I don't think so. You have to actually use it yourself, over time. At least that's true for everyone I know. Otherwise the abstraction will not and can not feel better to you, because you assess it in the wrong context.
Its like showing someone prolog code who is used to C. That just doesn't work. I can write you the code, but I doubt it will help you. You already called the other example of the divide function to not look great, so from there it only gets worse for you if that style is unfamiliar to you.
Take back what now? I said that your code example demonstrated an "if" statement written in different syntax; that it did not approach things from a higher level. The same would be true if you had written it with a ternary operator, if the language in question had such a feature. How does restating the same thing in a slightly different way lead to some kind of contradiction or whatever it is you are seeing here?
> If so, I think "higher level" is too subjective and I'm tired of discussing this topic.
I suspect once again you're getting too caught up in what words mean to you and not what they mean to me. As much as I'd like to use words as you know them, I haven't quite figured out how to drill into your brain to extract that information. Best I can do is use what I have and work with you to clarify intent as needed.
But we've already discussed that to death. There was no need to say it again – especially if it has tired you. Why did you bring it up again, exactly...?
> You have to actually use it yourself, over time.
Time I have. All we lack is your code.
> I can write you the code, but I doubt it will help you.
Humorously, you've put way more effort into telling me this than the supporting if-statement approach would have required. Does this one again suggest that what you envision is so long and arduous that you are avoiding it because of the effort required?
Otherwise you may as well try me. If it doesn't help we're still in the same place with much less energy expenditure on your part as compared to whatever this is.
> It's like showing someone prolog code who is used to C. That just doesn't work.
No, it works just fine. Prolog is perfectly understandable to read. It is harder to write without first understanding a bunch of technical nuance, but we don't need the uninitiated to write code here.
Why not admit that you can't write the code in question? This act isn't fooling anyone.
Sorry, it was late and you indeed mentioned that it wasn't making it "high level". My bad. Unfortunately, "high level" is quite subjective and I'm not willing to put the time to discuss if something falls under this definition or not.
> Why not admit that you can't write the code in question? This act isn't fooling anyone.
I asked you to clarify what exactly you want to see (which you didn't quote, even though you quoted basically everything else in my reply). In this particular thread here, there was no mention of age, that was in one of the other threads. So before I write code for something that you don't want to you
Again, please explain the requirements for the code again. What exactly do you want to see written in this style?
Uh... https://news.ycombinator.com/item?id=43487167
With respect, are you okay? Between this and your other comment [https://news.ycombinator.com/item?id=43502693] that was completely off the rails, I'm worried for you. You seem to have lost your sense of what is going on.
If it is simply that you are posting too much and can't keep things straight, making up stories like that you could code something but won't because I wouldn't be able to understand your brilliance to cover your tracks, maybe it's time to stop using the internet for a while?