Jannis
11/17/2019, 7:44 AMsam
11/20/2019, 2:34 AMTry { "a".toInt() }
Jannis
11/20/2019, 11:21 AM"a".toInt()
has the type fun String.toInt(): Int
which would mean it would have to be defined for all strings possible. But it isn't as not all strings can be converted to ints. Thats a partial function and partial functions throw exceptions on unhandled input and are thus not pure. Throwing an exception is an action not indicated by the return type => definition of a side effect. fun Sring.toInt(): Int
is much better if it has either Option<Int>
or Either<SomeDescriptiveError, Int>
(or similar) as a return type. Coming back to Try
since String.toString(): Int
is impure and Try
does not suspend that side effect, the invocation of Try
is by definition also impure.Jannis
11/20/2019, 11:41 AMTry
is the main purpose it was added for (as a simple wrapper around try catch), so using it that way is also not too bad when no other side-effects are involved. It's just that kotlin cannot prove that with it's type system, making this a bad abstraction because it's too easy to use it wrong. I usually follow two paths when working with exception throwing functions that I have no control over:
Wrap it in suspend because it might be doing side-effects or use try catch directly and convert to option/either for code that I know is otherwise pure. It's quite useful to define an extension function for that. The only reason arrow does not provide this is because it's again, like Try
, an abstraction that promotes misuse and we try to avoid that in arrow.sam
11/20/2019, 6:00 PMTry
value is such a function. I donât believe thereâs any requirement for the function to be total.
The reason I asked about this is because when people question why Try
was deprecated, I think the better answer is not âbecause itâs not pureâ but because âwe donât want to encourage people to catch IO exceptionsâ or âwe donât want to encourage strict evaluation of effectsâ.Jannis
11/20/2019, 8:19 PMfun a(str: String) = Try { str.toInt() }
is total and deterministic and thus anything returning Try
is pure (if and only if there are no other side-effects apart from exceptions). That is not the problem with Try
though. The problem is anything within it has to be impure to be useful, and that is not what arrow wants to promote. As I said in my second message, wrapping impure code that has no other side effects and that you have no control over, is exactly what Try
is/was useful for and that's still how I use it (or an equivalent ext func to option or either). But as the deprecation message states: Try promotes use of impure functions (or something like that) and that is just bad in all cases.
My original message is indeed wrong. Try
is pure in all cases that only involve non-fatal exceptions (fatal exceptions are rethrown afaik).
So a good answer to "why is Try
deprecated?" is: "Because it promotes using and writing impure functions". The other part about IO or effects is also true, but not the only reason.Jannis
11/20/2019, 8:22 PMIO
or Try
(or equivalent). If you know it has no side-effects other than exceptions (which is quite hard to figure out about code you do not own) then Try
is perfectly fine, for all other cases use IO
or equivalently suspendsam
11/20/2019, 8:23 PMJannis
11/20/2019, 8:23 PMJannis
11/20/2019, 8:23 PMsam
11/20/2019, 8:24 PMJannis
11/20/2019, 8:24 PMsam
11/20/2019, 8:24 PMJannis
11/20/2019, 8:24 PMsam
11/20/2019, 8:25 PMa.toInt()
fulfils the rules of purity - output is always the same for the input and it has no side effects.Jannis
11/20/2019, 8:26 PMfun String.toInt(): Int
has no indication that it might throw, similar to fun Int.div(i: Int): Int
Jannis
11/20/2019, 8:26 PMJannis
11/20/2019, 8:26 PMsam
11/20/2019, 8:26 PMpublic Int toInt(String input) throws NumberFormatException
is that better ?Jannis
11/20/2019, 8:27 PMInt | NumberFormatException
. It has lots of other problems but it is something I'd consider puresam
11/20/2019, 8:28 PMsam
11/20/2019, 8:28 PMJannis
11/20/2019, 8:29 PMsam
11/20/2019, 8:29 PMfun String.toInt(): Int
<-- itâs the same just the exception isnât included in the return type, but itâs the same as the Java one.Jannis
11/20/2019, 8:30 PMsam
11/20/2019, 8:30 PMJannis
11/20/2019, 8:31 PMsam
11/20/2019, 8:31 PMJannis
11/20/2019, 8:31 PMJannis
11/20/2019, 8:32 PMsam
11/20/2019, 8:32 PMJannis
11/20/2019, 8:33 PMthrows NumberFormatException
. It needs to be in the signature/return type to be considered puresam
11/20/2019, 8:33 PMsam
11/20/2019, 8:33 PMsam
11/20/2019, 8:34 PMconst foo = (a) => a + 1
sam
11/20/2019, 8:34 PMJannis
11/20/2019, 8:34 PMsam
11/20/2019, 8:35 PMsam
11/20/2019, 8:35 PMJannis
11/20/2019, 8:36 PMfun String.toInt() = toInt()
it's just as impuresam
11/20/2019, 8:36 PMJannis
11/20/2019, 8:36 PMsam
11/20/2019, 8:37 PMsam
11/20/2019, 8:37 PMsam
11/20/2019, 8:37 PMsam
11/20/2019, 8:38 PMJannis
11/20/2019, 8:38 PMJannis
11/20/2019, 8:38 PMsam
11/20/2019, 8:39 PMJannis
11/20/2019, 8:39 PMsam
11/20/2019, 8:40 PMJannis
11/20/2019, 8:40 PMsam
11/20/2019, 8:40 PMsam
11/20/2019, 8:40 PMJannis
11/20/2019, 8:40 PMfun a(arg: A): B
I do not want this to throw eversam
11/20/2019, 8:40 PMJannis
11/20/2019, 8:41 PMsam
11/20/2019, 8:41 PMJannis
11/20/2019, 8:41 PMsam
11/20/2019, 8:41 PMTry
and things like that is because they tend to hide effectsJannis
11/20/2019, 8:42 PMJannis
11/20/2019, 8:42 PMsam
11/20/2019, 8:42 PMsam
11/20/2019, 8:43 PMsam
11/20/2019, 8:43 PMJannis
11/20/2019, 8:44 PMTry
is pure, code that is useful to use inside Try
and has no other side effects is not. Btw thanks for pointing out that Try
is pure, I had that wrong first. đJannis
11/20/2019, 8:45 PMJannis
11/20/2019, 8:45 PMsam
11/20/2019, 8:46 PMJannis
11/20/2019, 8:46 PMsam
11/20/2019, 8:46 PMJannis
11/20/2019, 8:46 PMJannis
11/20/2019, 8:46 PMJannis
11/20/2019, 8:46 PMJannis
11/20/2019, 8:47 PMsam
11/20/2019, 8:47 PMsam
11/20/2019, 8:47 PMJannis
11/20/2019, 8:47 PMJannis
11/20/2019, 8:48 PMsam
11/20/2019, 8:48 PMsam
11/20/2019, 8:48 PMJannis
11/20/2019, 8:48 PMJannis
11/20/2019, 8:48 PMJannis
11/20/2019, 8:48 PMJannis
11/20/2019, 8:49 PMsam
11/20/2019, 8:51 PMJannis
11/20/2019, 8:51 PMval genA = Gen.fx {
val length = int(0..100).bind()
string().list(length)
}
this is a perfectly fine generator for creating lists strings, however when shrinking it will first shrink the length and then the elements inside the list. That means once it shrunk the content list it cannot go back to shrinking the lengthJannis
11/20/2019, 8:52 PMsam
11/20/2019, 8:53 PMsam
11/20/2019, 8:53 PMGen.ints().map { ⌠}
Jannis
11/20/2019, 8:53 PMJannis
11/20/2019, 8:54 PMJannis
11/20/2019, 8:54 PMsam
11/20/2019, 8:55 PMJannis
11/20/2019, 8:55 PMflatMap
the result is a function, so to shrink it it has to first evaluate it, but you cannot go back after doing soJannis
11/20/2019, 8:57 PMclass Rose<A>(val el: A, val branches: Sequence<Rose<A>>)
Getting them to work nicely with a decent api is quite a bit of work tho.sam
11/20/2019, 8:59 PMJannis
11/20/2019, 8:59 PMsam
11/20/2019, 9:00 PMsam
11/20/2019, 9:00 PMJannis
11/20/2019, 9:01 PMJannis
11/20/2019, 9:01 PMsam
11/20/2019, 9:02 PMsam
11/20/2019, 9:02 PMsam
11/20/2019, 9:03 PMJannis
11/20/2019, 9:03 PMflatMap
require quite a bit of work to overcome. The solution is two fold: Don't ever use it unless you need it, and if you need it either ignore that you have worse shrinking or add you own shrinking back đJannis
11/20/2019, 9:04 PMJannis
11/20/2019, 9:04 PMsam
11/20/2019, 9:06 PMsam
11/20/2019, 9:06 PMJannis
11/20/2019, 9:08 PMsam
11/20/2019, 9:11 PM