This is probably a dumb question. Working with `kt...
# getting-started
c
This is probably a dumb question. Working with
ktor
this weekend reading from a HOCON file. I ended up with this code,
Copy code
// Truncated for simplicity
fun Application.getDatabaseConfigurationFromEnvironment(): DatabaseConfiguration {
    val host = environment.config.propertyOrNull("postgresl.host")?.getString() ?: "localhost"
    return DatabaseConfiguration(host = host)
}
Why is this not
suspend
? I would have guessed reading from the environment is an effect? This question is more general than the example. Why are some effects marked suspend and other not? Coming from Haskell and it seems like it would be beneficial to look at this function, see
suspend
, and immediately realize it is doing an effect (versus being pure). 🧵
I really like the idea of
suspend
and getting a little confused at the best way to use it. It could be that this is an "interop with Java" thing and therefore suspend is not available? How best to think about pure versus impure in this language?
s
I think the answer is that Kotlin isn't as of a functional language as you assume!? (I'm admittedly out of my depth here. I don't even know what an effect is and don't ask me about monads.) So my ignorant answer would be that
suspend
isn't about being an effect or about being pure , but only about being concurrent. A few days ago I saw a talk from the KotlinConf23 about where Kotlin is on the spectrum between imparativ and functional (apparantly in the middle being "expression oriented"), unfortunately the all the Kotlinconf-videos have been made invisible again for now 🤷‍♂️ But there is a compiler plugin that makes Kotlin more functional: Arrow (it adds type classes, whatever that is, and stuff) On the other hand, you're invited to stick around with the less-than-functional Kotlin (pun intended). I sure do have a good time with it and you might too 🙂
c
This is useful. I am likely just thinking of
suspend
incorrectly. I would really love to look at a function and know if it is pure or not.
s
that isn't possible in Java/Kotlin. not even for lambdas (not even for subcases like extension functions or inline lambdas). Us Kotlin-users (at least those coming from collections-are-mutable by default and adding-final-to-variables-just-increases-the-visual-noise Java-land) are already happy about collection(interface)s being immutable by default (though technically most implementation could be casted back to mutable, but that would surely stick out in a code review) and
val
being equally long to
var
(and is therefore actually being used wherever possible). Though technically not even
val
means that you'll always get the same value. Technically it only means that the property has no setter, but its value can still change:
Copy code
interface HasX {
    val x:Long  // seems like it would always return the same value but doesn't have to
}

data class Permanent(
    override val x: Long  // once initialized will always return the same value
): HasX

class Impermanent: HasX {
    override val x: Long get() = System.currentTimeMillis()  // will basically never return the same value
}
This is why smartcasting only works when you make a local copy of the value 😉 But like I said, still better than Java where everything is mostly mutable.
c
Thanks @Stephan Schröder! Appreciate the viewpoints.
Sounds like I have a language to design 😛
s
go for it 😆 maybe this helps Crafting Interpreters But maybe look at Arrow first.
c
It's for historical reasons. There currently is no official Kotlin-first library to deal with files, so the example you showed above is based on Java's standard library (which doesn't have
suspend
). If there was a Kotlin-first library to deal with files, I would bet on it using
suspend
.
(also, I definitely recommend you read about #arrow, their library has an error strategy that just feels right in functional code)
@Stephan Schröder, your vision of Arrow is a bit outdated 😅 They gave up on type classes a few years ago, instead basing everything on
suspend
, and lately, context receivers. Arrow is also not a compiler plugin, it's just a regular library (though they also have compiler plugins and annotation processing for some other projects, like Arrow Optics or Arrow Meta).
I would really love to look at a function and know if it is pure or not.
Same, but sadly, adding that is a massive language design decision that essentially leads to Haskell. One of the reasons is that the platforms Kotlin runs on (JVM, JS, Native) don't have a concept of immutability. Another reason is Kotlin's goal to be "the language of the industry", meaning it should be easy to understand and easy to introduce into new codebases. The pure/impure explicit coloring doesn't have enough benefits for its complexity. For a while, the Arrow team recommended to mark impure functions as
suspend
, but I think that has fallen out of use nowadays.
suspend
is implemented with something that looks a lot like the Continuation monad (I'm not an expert there, but there are some limitations), so in that sense, it is the closest thing we have to effects, yes.
👍 1
😅 1
I recommend to pay attention to the wording in the documentation:
val
and
List
are read-only, not immutable. Of course, it is possible to create truly immutable data structures (as long as you trust your users not to do weird memory accesses). There even is an alpha library for immutable collections: https://github.com/Kotlin/kotlinx.collections.immutable
Also: the Arrow documentation has a great article on the relation of context receivers,
suspend
and effects: https://arrow-kt.io/learn/design/effects-contexts/
If you're starting with Arrow right now though, be aware that we're in a transition period. A lot of things have been simplified in 1.2.0-RC (the documentation is up-to-date with the new patterns), but the official 1.2.0 release is not here yet, to gather feedback from users. It's probably a few weeks or months away, I think.
👍 1
c
👌 I have done a bit of optics via Arrow. Going to check out the effects context bit.
For a while, the Arrow team recommended to mark impure functions as suspend, but I think that has fallen out of use nowadays.
And this is mostly because you have to interact with a lot of existing code, not because it wouldn't be ideal?
Also, do we expect https://arrow-kt.io/learn/design/effects-contexts/#looking-at-the-future the
with
function to do this in the future?
s
I pondered some years ago about using truly immutable collections but concluded that the immutable interface was enough for practical code. It already communicates that the collection shouldn't be mutated and everyday devs seem to respect that 🤷‍♂️
m
@chiroptical I agree with both approaches. In most simple cases regular function would work. My 2c: For bigger architectures, I would personally consider suspend function for configuration. As soon as you deal with microservices & config servers, you will benefit for declaring those as suspend. We had a situation in production where some of the config were read from env, while some more critical ones were read from config server. These configs have fallbacks e.g. to s3, dynamo caches, etc.. All of those calls were nonblocking suspend calls. We were lucky that we thought ahead and modelled our config with suspend. Other teams weren’t as lucky as those that gets their configuration without suspend functions had to refactor their code to use runBlocking everywhere and raised a lot of techdebts for config synchronizations..
@Stephan Schröder By the way, to add to @CLOVIS. Arrow has evolved a lot. In KotlinConf’23 you’d learn that the library has been simplified. I did a survey recently and it’s actually getting more mainstream in the developer community in my circle (Australia/New Zealand). Many of them were either already using arrow or consider using it. Most adopted because of readability improvement for typed error handling with
either { }
. I did an interview with developers re context-receivers and
context(Raise<E>)
introduced in KotlinConf ’23 and Arrow 2.x, most were able to naturally relate to it and are really excited to use it. I had the opportunity to note SonarQube scans on various codebases. Though I can’t publish it due to it not being in public domain.. The findings were interesting: Teams using arrow tend to have on average better score in various maintainability metrics, e.g. cognitive complexity, cyclomatic complexity, test coverage, and readability. It took me a couple of months to finish all the interviews, experiments and documentation.. I’ve documented the interesting takeaway from the experiment in this article if you’re interested to read up. 🙇‍♂️ https://betterprogramming.pub/typed-error-handling-in-kotlin-11ff25882880
c
Nice! Great post by the way. I read it once but I should try the approach in the small app I am trying.
🙌 1
c
@mitch Note that context receivers are at least a year away a this point. Probably two. (not before Kotlin 2.1). We'll be on Arrow 1.2.0 for a long time.
m
Yeah that's right... Which is also fine. From the looks of it the builders will allow migration to that style pretty seamlessly.