At first, I used to take Result. But, it removes m...
# announcements
m
At first, I used to take Result. But, it removes me the possibility to have an exhaustive result sealed class that I can treat in the caller with when keyword. Which is very nice at compile time.
🧵 2
n
Yeah, it's a pit of a sticking point, with
Result
, that I guess many people don't love. It's not generic on the type of the error
m
You mean, it is generic. Its of type “Exception”. Thats it.
n
That's not gneric though? It's just a fixed type
m
oh yeah! got it!
n
Personally, the way I would probably want it to work (disclaimer, I haven't fully worked this out) is to have an interface with an API similar to Result, and then have a class that inherits from it, whcih is generic on the error type
m
well im using also a different Result API : https://github.com/kittinunf/Result
n
Erasing the type of the error is also sometimes useful, for example, if you have errors coming from different sources, but you really want to have the option
m
but on top of that, if we only take kotlin in general ways : How should i propage errors through different layers of “XXXResult”
n
This one does look like it is generic on the error type
I assumed you were talking about the Result in the stdlib
m
yeah i got you there :0
n
How to propagate errors; it really depends what properties you want
Throwing is definitely the simplest way to propagate errors through many layers
m
But is throwing really “kotlin best practice”. What i read is “no”
n
however it is more implicit. You can use a result like approach, but if you are propagating Result across many layers, then IMHO it comes back to what I said before, you'll want an interface that erases the error type, so it's not such a pain
m
So i am trying to understand the boundaries of throwing, vs, using sealed class with exhaustive list AND using result with only Success or Failure possibilities
n
I think throwing is fine when it's a serious error that's propagating across many layers
I don't think there is one answer
m
yeah!
well … ok…
n
I think (maybe I'll be corrected), that the belief of the kotlin devs and many people, is that they like sealed classes when you expect the immediate caller to potentially handle errors
If you are expecting, or you know (because it's all your code) that you don't want to deal with the error nearby, but rather, many layers up the call stack, then throwing is fine
m
So if we take the example of a ControllerAdvice, would it be good to treat like we were doing in Java, all exception thrown ?
n
That's not really a domain I'm that familiar with. What exactly is the error?
m
and through other layers, using sealed class to catch “users exception”
well im struggling between, where and how should i catch IOExceptions ….
compared to “throwing my own user exception, domain exception”
n
well, what cuases the IOException?
m
i dont know, a parsing error lets say!
n
Parsing data supplied by a user
etc
Or parsing data you wrote out before
m
well, data coming from an underlying request
n
A lot depends to what degree your program can continue regular operation
m
Users ---> MyApi ----> AnotherApi ---> MyApi ----> ParseException ----> User
Typically, those king of errors are simple try catch.
Then a ControllerAdvice could catch those errors and return whatever based on that, to the end user request.
n
I'm not fully familiar but basically I'd assume that something like that, if you get a malformed request you'd probably just want to notify the user, and move on
So I'd probably have the
processRequest
function return some kind of sealed class
m
definitely! i would like to know the Kotlin style to handle that! Should I be isomorphic to what you can do in Java, or is there any other best practices Im not aware of
ahah!
n
Hmm, I'm not completely sure, opinions vary on this a lot and I'm not primarily a Kotlin dev, so I'll let other people opine at this point
m
Well, we’re “not supposed to” use exceptions for communicating non-programming errors, so a generic
Result
type won’t make sense anyway, would it? For different operations and use cases there are different errors (or more generically - potential outcomes) and thus each needs their own domain-specific sealed type. Unions & better typealiases would simplify that but not generic Result types. The current
Result
is highly optimized for low-level usages like coroutines.
e
I haven't found Result to be very useful in most code. it makes sense for infrastructure like within coroutines/flow operators /etc., but in all concrete use cases I write a sealed class. at least in the main codebase I work on, there's been no need to make the many FooResult, BarResult, etc. types particularly generic
exceptions are either at external integrations (e.g. IOException, to be handled and wrapped immediately) or for irrecoverable situations (e.g. IllegalStateException, which we'll just log and crash)
n
It really just depends what your strategy is for "bubbling up" errors. In Kotlin it probably just makes sense to start throwing once you want things up the call stack.
e
at least that's what works for us
n
If you want to keep bubbling up something that is Result-like though, for whatever reason (and there are pros and cons), then you'll want some form of it that is type-erased in the error
You see this in e.g. Rust which heavily discourages panics; thus you have the anyhow create which lets you bubble up error-type-erased Results
m
Funny, while Swift heavily encourages panics. Not sure what’s better.
e
we really want something which can represent multiple types of errors. unless you have union types, that means a sealed class, not a generic Result.
n
@Marc Knaup swift has panics separate from exceptions?
m
Yes. Force-unwrapping
nil
(null), precondition checks (similar to
require
in Kotlin) etc. all panic.
n
@ephemient you can have a Result whose error is a sealed type, that is no problem
m
e
we would map that to an appropriate public type across component boundaries anyway, might as well map the whole result type as well
m
Soo basically if i understand correctly
e
swift doesn't really have typed exceptions in the way Java does
m
For unchecked exception, if I want to output this to the user for lets say an API with an error code, the best way would be to throw and catch in some Exception handler, thats it ?!
n
@ephemient there are advantages to using a result type with the sealed class just for the errors, namely when it comes time to bubble up different kinds of errors, you can have that work for free. but, again, really depends what your strategy is
m
and for all other “exception” such as user exception or domain-specific created exception, I should use a sealed class of my own
e
functions are only declared to throw (anything), rethrow (from a lambda), or never throw (can still panic as always)
n
@Marc Knaup I'm not sure that swift really encourages panics though? The docs say you should only really use, e.g.,
try!
, when you are very certain that the error won't actually happen at runtime
it's just a last line of defense, in that example
m
@Nir the Swift team’s stance is that when there is a programming error to panic and exit. Otherwise it’s very difficult to guarantee that the system is in a consistent state.
n
Sure, I understand that
Swift itself is using panics more, e.g. unwrapping nil in Swift compared to Kotlin.
I don't think users are being encouraged to use panics. It's also a different situation from Rust, rust panics are still catchable, at the end of the day, so even the terminology here is getting unclear.
btw, wrong Nir 🙂
m
Slack’s autocompletion could be more intelligent 🙂 Well it’s not encouraged insofar to panic your program whenever possible, hehe. I was annoyed by iOS apps crashing on even the most mundane programming errors. But over time I learned to love it because it significantly shortened the bug/debug/fix cycle. In Android Apps on the other hand it’s the opposite. Many errors get swallowed or hidden in log output and in turn cause random behavior or other errors that are incredibly hard to debug.
n
another reason btw, to use a generic Result type over only use custom sealed classes, is simply because you can have things like
getOrThrow
on the Result type, which are almost always useful at some call sites. This is verbose to write out every time if you have a sealed class (or, definitely it is very verbose to write in such a way that you actually get the proper error information if there's a failure)
@Marc Knaup Yeah, I've had this discussion many times in a C++ context, whether certain things should be throwing, or aborting. It's kind of tricky. hard aborts can be very annoying in some situations.
m
Yeah in a client-side app it makes sense. Only a single user is affected. On the server-side all users would be affected.
n
In principle, people should probably not be catching NullPointerException ever. At main, you have a blanket catch, that does your business, that's probably where most NPE's get caught. In an ideal world 🙂
m
Well with union types you probably could have some form of rethrow. You just check for and erase some types from the input union and return these value immediately and unchanged.
n
Well, there's a lot more applications in the world than "client side", "server side", etc. I write trading applications, you pretty much never want to hard abort
Because you are leaving behind an open position on the market
m
I’d prefer a trading app to hard abort over making incorrect trades because of inconsistent state
n
You'd be wrong 🙂
m
Or you ¯\_(ツ)_/¯
n
Well, the difference is this decision is based on decades of experience (one of them mine, a few of them from my colleagues)
If there's a problem, you want to flatten, if at all possible
m
Also, why would a position remain open? That seems like an unrelated issue.
n
this is high frequency trading where the holding periods are pretty short, so if your application crashes you're very exposed risk wise, every moment you're not trading
m
panic -> restart -> continue
n
.... that's how markets work?
positions don't magically vanish because your application crashes
m
But you can continue after restart. Then you have a consistent state again.
n
that takes minutes
m
Then fix that
😛
n
can't be fixed
models are big
different things are a different fit for different domains
m
Then maybe do a hot swap
n
Probably better to listen to domain experts than to blanket apply the same generalizations
This is C++ code, hot swapping is very hard
and it's a huge amount of labor, which we have to balance against other things that could increase pnl
etc
m
How is that related to C++?
n
hot loading code and such things is easier on the JVM, afaik. Unless you meant something else?
m
No, I think that’s a Visual Studio thingy
I mean have a system running standby and if A crashes then B continues. No need to wait for startup.
n
We'd have to pay for extra boxes, extra cores, etc
it would be a huge cost for very limited benefit
Anyhow, you have thought about this for a couple of minutes
me and people i've worked with have thought about this for years... be open 🙂
m
I am. I just don’t like the words “never” and “always”. If the system is never supposed to hard-abort there must be significant trade-offs.
I wonder what would be a scenario where you can fail-over an inconsistency where you can be sure the inconsistency is not a problem?
n
But, you're the one saying never? "it's never ok to design a system that doesn't intentionally hard abort"
Ironically
It depends what you mean by "sure", you can never be 100% sure of anything, but in a lot of cases you can be reasonably sure. When you throw, you exit from functions, which causes state to get destroyed, and then have to be recreated later to resume whatever you were doing
The higher your throw, the more state gets destroyed; if you have a while loop in main that catches and relaunches your applications (not saying I do this), and you don't have globals, it's not clear how different this is from a restart. A restart is just the extreme end of the spectrum, in that sense.
m
Yeah that’s true but only for pure functions. And in Kotlin esp. if you catch
Error
then even otherwise pure functions are no longer pure because JVM internals change (e.g. OOM causing side-effects).
n
No, it's not about purity
m
Then how can you be sure that throwing cleans up state?
n
If you don't have globals, then all state is eventually a local of some function
and when you throw past that function, it can't be accessed directly, but (in a GC language) only via some other piece of state, that has a reference to it, but then, that subsequent piece of state will also get thrown past
m
That’s true except for programming errors. Be it buffer overflows or whatever.
n
i would have thought that Java wouldn't allow that sort of thing
(java/kotlin)
m
Java yes - more or less
C++ does
n
but, in C++, yes, that's possible, but then, in C++ stack objects just get completely destroyed, and you're very likely to see a segfault which brings you back to an abort anyway
I can tell you in practice, this notion of an inconsistent state isn't really an issue, in many years of trading. On the other hand, triggering the most informative alert, as quickly as possible, is vital
m
I wouldn’t call it likely. I could simply modify a single
int
on the stack unintentionally (e.g. due to off by one error) which could have severe effects on the domain. That kind of mistake may or may not be detected by checking parameters and state before using it.
@Nir I do believe you. Do you have any example of inconsistent state that it was able to recover from? I’m just curious.
n
it is possible, sure. In practice, it's vastly unlikely
m
Apart from that, we’re off-topic. Could be its own discussion 🙂 I’m not a fan of how Kotlin’s error handling works today. It feels like we don’t have the right tools yet. We’ve left checked exceptions (Java), we’re leaving unchecked exceptions for domain errors but have no good replacement yet.
sealed class
is the closest we can get.
n
I guess it depends what you call "inconsistent state" exactly, the line between errors and inconsistent state is tricky. Sometimes the strategy might expect, for example, the trade server to have an order not yet prepared, but it already has one.
In this case, an error is triggered and it goes to a no trading state, but it does not crash.
m
“it is possible, sure. In practice, it’s vastly unlikely” If it’s unlikely yet really random - why not hard abort manually then?
n
In these cases, usually dealing with it involves asking another team to do something with th etrade server, and then simply setting our strategy back to trade
and it's fine. Having it abort would cause much more loss of signal, loss of human time, and loss of trading time.
m
That sounds like a domain error though? Hard-aborts are mostly about programming errors.
E.g. trying to make -1 orders instead of 1
n
So if we tried to place -1 orders instead of 1, we still would not hard abort
m
or 0xFFFFFFFF if unsigned 😄
n
Just to be clear, hard aborting is never really an option
the bare minimum is to serialize the signal state of the strategy, and alert that an error has occurred
that would be the most "aggressive" error we would do
m
I see. What do you mean by signal state?
n
Skipping the flattening, in this case
well you're making predictions based on data right
m
Kind of a custom mini crashdump without crashing?
n
if you restart the strategy, how does it now what the data was for the day so far?
each day, EOD, it serializes the signal state
if you hard aborted mid day, you'd lose everything for that day. Even if you periodically wrote out the state (which would be very costly), you'd still lose everything goign back to the last time you wrote.
m
But shouldn’t programs always expect to fail and crash? Or their machines?
How’s the uptime?
n
Not sure i understand what you mean by that
it hard fails very rarely
the last times it's happened is because of a hardware failure on the box
m
That’s good. Must have been battle tested for a long time then.
n
Yeah, I mean, if you don't do crazy things there's no reason why you should be randomly crashing
m
You can’t possibly test every possible input from the market. Nor all sorts of timing issues in your code, esp. if run in parallel. So things may happen very infrequently.
n
But at any rate, you need to serialize your state. Or, you'd need to write an alternate solution where you have a server that records the data throughout the day, writing files periodically, then have a way to feed those files + last EOD file into the strategy and "replay" the signals, etc etc... all possible, but a lot of effort
Sure
So sometimes, we lose signal, it's unavoidable
it's just not something we'd ever do on purpose, it just doesn't make any sense.
Because we use C++ of course, it's also always possible to segfault, etc, and trying to use signal handlers to deal with this is terrifying enough that we haven't touched it.
m
Yeah I see what you mean
n
but if you asked me to choose between Kotlin's null behavior and Swift's, I prefer Kotlin's. Unless, Swift, at a minimum, lets you "install" your own handlers for that situation.
Keeping in mind, a lot of older java codebases have horrible practices around try/catch... in that they catch a lot.
and too broadly.
In our codebase, catching is pretty rare
m
Let’s say you’d take that C++ project to Kotlin. Apart from performance - what would be major hurdles? Esp. around error handling?
e
hardly unique to old code bases, it's very easy to do the wrong thing
n
Eh, I mean, C++ doesn't really have a great error handling story either 🙂 I think Kotlin is fine there
Other than perf, I dunno, hard to say. Kotlin's generics are very limited comparatively but if you are willing to sacrifice perf anyway then you can usually live with it
Perf is a lot of what makes the code challenging; other than that, it's software we write for ourselves, we're the end users, so that actually alleviates many challenges
relatively few third party dependencies
etc
m
Yeah that’s true. It’s often more enjoyable to set your own rules 😄
n
Indeed, and that is a pro of the code I work on 🙂
Kotlin would have definitely made some of the code I wrote quite a bit easier, namely, having reflection
on the other hand, in C++ with some elbow grease you ocan have a very nice "strong typedef" kind of thing which is actually usable, probably a bigger pain in Kotlin
m
What would you use reflection for?
@Manuel Dupont what’s your use case for using
Result
btw? Do you have an example?
n
various things. serialization. turning static structs into dynamic schemas.
m
Ah, so that’s things you can’t do with a compiler plugin or annotation processor?
n
I'm not familiar with annotations really, but why would I prefer a compiler plugin over writing a function using reflection?
m
It works multiplatform so for example you could run the program with Kotlin/Native and not just Kotlin/JVM. It’s compile-time checked instead of runtime-checked. It’s faster. It’s more powerful than runtime type information & you can easily generate new code based on it.
n
performance is irrelevant here, this isn't the critical path
if I was going to do compiler plugins then what's the advatnage over C++ lol
it's kind of funny to hear this because so many people in C++ land want reflection so badly, and consider needing a compiler plugin to handle something like serialization a hack
m
A much more enjoyable language 🤔 I hope 😄
n
although, when C++ gets reflection it will be compile time reflection, not runtime, which will render a couple of the objections moot
m
Well you could just use
kotlinx-serialization
and won’t have to write your own plugin.
n
Yeah, it's just one of the main points of suffering
Yeah that may or may not be ok; we serialize some things into json and also read/write them in python
But yeah for the applications I'm thinking of, I would definitely just do it using reflection (and enjoy it)
I wrote code like that in python, and it was quite fun
m
Just keep in mind that Reflection restricts you mostly to JVM for now. In case that matters.
n
in my case, no, we control our environment and would only be picking one that makes the most sense
but it's all not relevant since kotlin/java etc aren't fast enough
m
True. And true memory hogs
n
I did consider using kotlin for various preprocessing, config generation, etc tasks
I wanted to personally, but I felt it was hard to justify because python is also so embedded in our ecosystem already
m
I ran a market analyzer and very quickly ran OOM on a 64G machine.
n
So, if you want to avoid C++ for "niceness" reasons, you can just use python. Not as nice as kotlin, but eh, still quite nice and it means not adding a whole new ecosystem/toolchain etc etc
m
Do C++ and python share ecosystem/toolchain in any way?
n
We use the same package manager for both, conda
Mostly, not really though
But C++ and python are already fixed parts of our ecosystem
m
Interesting, I’ll check that out. I’ve used conan so far and the experience was… interesting.
n
Yeah, I've heard a lot about conan but haven't had to deal with it much. We have a team here that gives us a very good experience overall using conda.
We lock all our dependencies there, both C++ and python, and it just sets up an environment on any machine, can switch between environments, etc
m
With conan I kept having issues with different environments. Its configuration seems fragile.
Anyway, I’ll keep trying to see what Kotlin is capable of. Almost done with the first Kotlin/JS/React project 🙂 It’s liberating not having to jump between different languages and ecosystems.
m
@Marc Knaup is it open source ? i would be intetested into seeing that
j
I recommend Arrow's Either (and more)
m
@Manuel Dupont I’d love to but unfortunately I cannot open source it 😞
m
no problem. i’ll try to make the same on my side.