In the new website, the page <https://arrow-kt.io/...
# arrow
c
In the new website, the page https://arrow-kt.io/learn/typed-errors/working-with-typed-errors/#accumulating-different-computations recommends the pattern of using
operator invoke
to create a constructor-like function. In my opinion, this is an anti-pattern, because it makes code harder to understand and the IDEA integration more confusing for beginners (you have to click on the opening parentheses to go to the source code, not on the function name!). To my knowledge, it's also never used in the standard library or any other official library, which all use top-level functions with the same name as the class to do this (
Array
,
List
, ...).
a
I’ve used it quite often, so maybe I was biased when writing that part
I think it’s something we can easily fix, the use of such a constructor is not required for the section itself
thanks for your input 🙂
c
On a more positive note, the article is great
The part on creating custom DSLs, in particular, is eye-opening
m
I’m also used to doing this. As far as I know this is actually used in stdlib and variation of this is very widely used
c
I'm curious to see if you have an example of it, I never found any. I also never found it mentioned in the official docs. It seems to me that it's an old pattern that a lot of people still use.
s
There is actually two common styles, I've gone back and forth and both are used in Kotlin Std / KotlinX. •
operator fun invoke
fun ClassName(...): ClassName
The only real reason for using the former is that it gives you access to
private constructor
. Otherwise it requires you to make the constructor
internal
. Or you need to define
fun ClassName
on the
Companion
which is also a bit strange 🤔 I do prefer the latter though, after a lot of back-and-forth but I do miss file private support from Scala to overcome the
private constructor
issue.
Actually, since this is a
data class
it doesn't even matter since
copy
can still break the contract. I was going to say the
private constructor
there completely eliminates the ability to constructor incorrect instances... We'll have to wait on multi-field
value class
for that.
c
I wish
private
on the primary constructor meant private-in-file and not private-in-class, but well
s
Yes, I agree. I agree with your overall assessment though, but I am unsure what the best solution is because now I also feel we need to mention the
copy
caveat 😅 and/or suggest a solution. IIRC there was a compiler-plugin for that, and now I guess two with DataClassGenerate.
j
I click over parenthesis for normal constructor, and to see instances of that class too 🤔
w
Re: the
copy
caveat, one alternative would be to run the checks on
init
, but that would lead to duplication and to throwing, which is probably not desirable
a
The main issue I have with using
invoke
on a companion object is that it has to be manually imported as intellij doesn’t give auto completion for operator functions (unless there’s a setting I don’t know that changes that)
s
I don't think I've every manually imported
invoke
😮
j
It works for me automatically
c
It's been a few years since I last needed to do it manually, but there was definitely a time when it didn't do it automatically
a
Really? :O
I have got to figure out how to make that work because it would save so much time lol
s
I think the factory function is the more common approach in Kotlin... e.g.
MutableStateFlow
is a function. from what I remember, the kotlin team does NOT recommend the invoke on companion object because the companion object creates an object in bytecode. for this specific example (the user example of the docs) we can get around the issue (data classes having copy) by using:
Copy code
@JvmInline value class Age private constructor(val value: Int)
+ factory function (which returns
Either
) 🙂 then
data class User (val name: String, val age: Age)
s
How do you define the factor method there for
Age
without
companion object
and without removing
private
from constructor? 🧌
s
yup, you do need the companion with
private constructor
😄 ....
s
The best I've found, and is used by Kotlin(X) in multiple places is.
Copy code
@JvmInline value class Age private constructor(val value: Int) {
  companion object {
    fun Age(...): Either<Error, Age> = ...
  }
}
It's used by Duration, some KotlinX libraries (datetime?), .. 🤔 Albeit it's also a bit strange. Should we update towards this? It combines the benefits of all I think.
When typing
Age(..
it'll resolve, and automatically add the import
my.pack.Age.Companion.Age
s
my point with the
Age
example is, that unless properties depend on each other (like openDate should be before closeDate of a bank account) you can typically push the opaque type up, towards the individual properties.... Then copy doesn't break your invariants
s
"soon", I am really hoping we get some clarity on timelines @ KotlinConf. I know K2 is highest priority, and I am happy it's the case because besides languages improvements it'll also bring more stability and IDEA improvements but the timeline after K2 is still unclear to me.
m
There’s a lot of good things coming. I’m personally really excited about context receivers hopefully that’s going to be finalized sooner rather than later!