https://kotlinlang.org logo
Title
i

Iliyan Germanov

04/07/2023, 11:17 PM
[FEATURE REQUEST] Constrained Primitive Types I don't know if that was already requested but wanted to hear your thoughts on having
PositiveInt
,
NotBlankString
and other constrained primitives type in Arrow. • `PositiveDouble`: a value class that guarantees its value is positive and finite. • `NonNegativeDouble`: >= 0 and finite. • The same for
Int
,
Long
• `NotBlankString`: guarantees that a string is not blank •
NotBlankTrimmedString
like the above but also applies trimming •
Percent
: a float in [0f, 1f] • ... What are your thoughts? Apologies if that's already built and I haven't seen it. Sample implementation (incomplete): https://github.com/Ivy-Apps/ivy-wallet/tree/multiplatform/core/src/commonMain/kotlin/ivy/core/data/primitives
p

Pavel

04/08/2023, 8:34 AM
I like the idea, that is what value classes are for and how we got unsigned integers. I would like to add a StrictInt or ExactInt using Math.addExact for additions to throw on overflow. But it sounds like a general purpose separate library, I don't see how it relates to arrow. Although I personally would totally don't mind if it is part of arrow 🙂 It is hard to believe that there is no such a library already somewhere.
i

Iliyan Germanov

04/08/2023, 8:43 AM
Yeah I agree with you. I haven't searched for such library but it would be pity if we don't have a well maintained one. Also being explicit and strict is a FP thing so IMO having it in Arrow won't hurt - like a separate package "arrow.datatypes" or whatever. The idea is that we already trust Arrow, it's well maintained and have an established community. Also a dozen of value classes + extensions and operator overloads won't make it more heavy
s

simon.vergauwen

04/08/2023, 8:53 AM
It's a concept that often comes back in FP, and I would be more than happy to create a new repository in the Arrow org and invite you all so we can collaborate and contribute there. I don't think it belongs in core, now that we've finally been able to cut down on binary size and API surface 😄 It can be considered "a general purpose separate library, but the goal of Arrow is not solely around "functional programming" but safer software in general. So it's more general purpose, we would be more than happy to facilitate and contribute to this ourselves. Are either of you interested in working / collaborating on this? I can create an new repository, and invite everyone that is interested on contributing there.
i

Iliyan Germanov

04/08/2023, 9:09 AM
Yeah that sounds awesome! It'd be a honor for me to contribute to the Arrow project. Feel free to invite me to the repository. I'm having a full-time job and also maintain the Ivy Wallet project but would be more than happy to contribute to this in my free time :arrow:
c

CLOVIS

04/08/2023, 9:14 AM
@simon.vergauwen isn't this exactly the type refinement demo showcased by Arrow Meta a year or so ago?
s

simon.vergauwen

04/08/2023, 9:50 AM
@Iliyan Germanov, I'll create a repo where we can start building this this weekend. @CLOVIS Yes, kind-of but it's not yet in a state where it can be used in production 😞 It currently stands still due to lack of contributors, and maintainers and I guess instability with K2 not yet being released.
c

CLOVIS

04/08/2023, 10:16 AM
There's a talk next week at KotlinConf on K2 compiler plugins :)
s

simon.vergauwen

04/08/2023, 10:17 AM
Super excited, also getting super nervous for my talk 😱
p

Pavel

04/08/2023, 10:17 AM
On second thought, it might/should be arrow specific. The point of defining wrapper value classes for primitives is mostly to redefine operations like in case of unsigned integers. So it might be better to design them with Raise context receivers and maybe some nice DSL instead of just throwing like in my example with overflow.
s

simon.vergauwen

04/08/2023, 10:18 AM
I've been thinking of creating a nice validation library as well, with some specialized DSLs, etc. Perhaps this could fit nicely together in a single library. @Pavel Something similar to Konform, but built with
Raise
. https://github.com/konform-kt/konform
c

CLOVIS

04/08/2023, 10:19 AM
Maybe have the lib be vanilla Kotlin with all functions named
plusOrThrow
and create a companion Arrow compatibility layer that declares the real operators with using Raise? This way you still get the choice of using Arrow or not
@simon.vergauwen I'd be interested on that, and how well it can fit with Compose. Arrow feels like the missing piece of the puzzle
s

simon.vergauwen

04/08/2023, 10:20 AM
The 2.0 binary will be truly small, so including Arrow will be much less impactful.
c

CLOVIS

04/08/2023, 10:21 AM
It's not really about the binary size, it's about the mental model. Raise & co are great, but it's still quite a bit shift in thinking process (very similar to understanding suspend)
s

simon.vergauwen

04/08/2023, 10:21 AM
After context receivers, we can potentially even push it down into an even smaller binary as well.
c

CLOVIS

04/08/2023, 10:22 AM
I'm personally using them everywhere in personal projects, but at my day job where we're not even using coroutines yet, and most devs have no idea what FP is (and therefore are afraid of it 😔), Arrow is a lot to ask
p

Pavel

04/08/2023, 10:22 AM
@CLOVIS I am mainly thinking about primitive wrappers, so it will be operators, not functiones. Like
a + b
which throws/fails on integer overflow instead of giving incorrect result.
c

CLOVIS

04/08/2023, 10:25 AM
@Pavel I mean:
// your-lib/src/...
value class Foo(...) {
  fun plusOrThrow(other: Foo): Foo
  fun plusOrNull(other: Foo): Foo?
}

// your-lib-arrow/src/...
context(Raise<...>)
operator fun Foo.plus(other: Foo): Foo
This way, users who don't want to use Arrow can (but have to be explicit about how they want failures), and users who like Arrow get the nicer codebase (= safer code is simpler to write)
p

Pavel

04/08/2023, 10:30 AM
@CLOVIS I see. Well, this is already offtopic and implementation detail 😉
i

Iliyan Germanov

04/08/2023, 11:52 AM
My use-case for this is having types that provide certain guarantees. For example,
NotBlankTrimmedString
will always hold a string value that isn't blank and has no leading/trailing whitespaces. When I see:
data class Person(val firstName: NotBlankTrimmedString, val lastName: NotBlankTrimmedString)
I know that I'll have valid firsName without doing validation if my criteria is not-blank AND trimmed. I want to be able to create those primitives in two ways: •
PositiveDouble(3.14)
(unsafe) - crashes on invalid •
PositiveDouble.fromDouble(a+b)
- returns Option<PositiveDouble> / Raise / whatever • Also: • Operator overloads for +, -, ... that are type safe •
.map
to map its value • PositiveDouble.toNonNegative() Example implementation (limited) https://github.com/Ivy-Apps/ivy-wallet/blob/multiplatform/core/src/commonMain/kotlin/ivy/core/data/primitives/PositiveDouble.kt Example for properties for different types: https://github.com/Ivy-Apps/ivy-wallet/tree/multiplatform/core/src/commonTest/kotlin/ivy/core/data/primitives Goals: • Guarantee that type the type invariant holds • Easy to create and use • No overhead
c

CLOVIS

04/08/2023, 11:54 AM
I agree with all you said except the syntax. The safe variant should be shorter to type and easier to find with auto-complete.
Bonus point if the unsafe variant makes it clear in its name
(PositiveDoubleOrThrow()
)
i

Iliyan Germanov

04/08/2023, 11:55 AM
PositiveDouble.unsafe
seems more straightforward to me When @simon.vergauwen creates a GitHub we can have a more formal discussion there. I'm new to libraries so I'm happy with whatever process/ideas you have
c

CLOVIS

04/08/2023, 12:15 PM
Not a fan of
unsafe
because it doesn't communicate how it fails, and is connoted with memory safety. Probably bike shedding though.
s

simon.vergauwen

04/08/2023, 5:24 PM
Any preference on name? ChatGPT sugessted: • (Arrow) Refined? • Arrow Shield • Arrow Precision • Arrow Tighten
i

Iliyan Germanov

04/08/2023, 5:29 PM
Arrow Shield and Arrow Precision are interesting but IMO something more self-explanatory would work better. It's hard to come up though. A few more suggestions: • Arrow Precise • Arrow Constrain • Arrow Primitives • Arrow Define • Arrow Strict Data Idk, names are hard I vote for Precision
• Arrow Guard • ArrowValidatedPrimitives • ArrowRefinedData • ArrowCheckedTypes • ArrowAssuredPrimitives
s

simon.vergauwen

04/08/2023, 5:32 PM
Guard, Precise, Exact are probably my favorites.
i

Iliyan Germanov

04/08/2023, 5:33 PM
+1 for those, I like Exact
implementation("io.arrow-kt:arrow-exact:1.2.0-RC")
Looks nice! Let's see what others think, my favorite of the proposed ones is definitely Exact.
s

simon.vergauwen

04/08/2023, 5:37 PM
I created https://github.com/arrow-kt/arrow-exact, so we can start discussing & drafting some code there. I invited all three of you into the repo. If anyone wants to collaborate, feel free to fork or ask here so I can give you write access ☺️
i

Iliyan Germanov

04/08/2023, 5:38 PM
Awesome! I won't be available tonight but will definitely check it out tomorrow evening. Excited to see it as a new dependency for Ivy Wallet :arrow:
s

simon.vergauwen

04/08/2023, 5:39 PM
No worries ☺️ Whenever you have time, no rush. Enjoy your weekend
i

Iliyan Germanov

04/08/2023, 5:56 PM
I created this issue for discussion purposes. Apologies for the lack of formatting! Did it on my Fold. IMO, our best course of action is for everyone to share their ideas and decide on a scope/purpose of the library. Have a great weekend all! https://github.com/arrow-kt/arrow-exact/issues/4
y

Youssef Shoaib [MOD]

04/08/2023, 9:28 PM
Just dropping a link here to this YouTrack issue by Roman Elizarov about restricted types. Might be relevant to the design considerations of the library
s

simon.vergauwen

04/09/2023, 8:15 AM
Hey @Youssef Shoaib [MOD], Thanks for sharing! I saw this link, and it looks really interesting but I am curious how long it will be until such a feature makes it into the languages. FIR might also offer some solutions, but development and maintenance cost are currently still very high. I already upvote it a long time ago 😜 👍