Is there a way to add your own "prelude" that's au...
# announcements
n
Is there a way to add your own "prelude" that's automatically present in every file? For example, suppose you want to replace the standard library
map
with your own that returns an ImmutableList instead of a List. How would you do it?
a
List is immutable. And yeah you could just declare an extension function in your file
j
There are several ways, but in general they are all considered hacks to be avoided. Are you changing code you don't own? They probably have assumptions about how the code works. But since you asked I can think of a couple ways: 1. Use your IDE to generate a header for the file 2. Add an import statement to take in the function/class that you want 3. Override the CLASSPATH to take in a jar file that provides the implementations you want (please don't do this) 4. Declare an extension 5. Use AOP 6. There are advanced methods where you can do late binding of containers (ad-hoc polymorphism) but the library should be written with this purpose in mind
What you are asking for basically violates the O is SOLID - Open for extension, Closed for modification
c
List
is not immutable, it’s read-only. By casting it to
MutableList
you might be able to modify it (definitely a sign of bad code, but still possible). Or the danger is in passing a Kotlin
List
to Java, where it is not read-only. But like others have said, the best route is definitely to make an extension function with this functionality (such as
mapImmutable
). This makes it more clear the intent behind your code, and also doesn’t require any nasty hacks to make it work
j
If you own the code, and it's self-contained (no one else is depending on it) then a relatively easy hack would be to import using an alias. But you are better off just having the IDE refactor the code for you to use the containers you want.
n
@allan.conda List is not immutable
Please.... stop repeating this
the amount of misinformation surrounding immutability in Kotlin (in /r/kotlin and in this slack) is prob the worst I've ever seen
@Jakub Pi how on earth does what I'm asking for violate SOLID?
It's at project scope, yes, all owned
and it's not refactoring existing code
say from day 1, I know I want to use my own
map
, because the standard
map
is a mistake
it's basically just wanting to have the equivalent of
import prelude.*
in every file
it would be nice if there was a better way to do this than actually forcing every file to have this, in terms of enforcement, etc
And @Casey Brooks making an extension function is verbose. If you plan to use immutable data structures extensively, it makes much more sense to just have
map
do the right thing
especially since
map
should probably have returned an ImmutableList to begin with
b
Guys, the question here is simply if there's a way to make a custom code (e.g. function
myStuff
) present in all the files (same as
println
is) so that this "file" would compile
Copy code
// note no imports

fun main() {
  myStuff()
}
instead of having to do
Copy code
import my.package.myStuff

fun main() {
  myStuff()
}
2
j
Ok I get it now, you want to enforce a particular standard on future code. AOP would do this but it's not particularly transparent. I'd suggest just enforcing it as a rule inside your code linter and integrating this into your build process. That way no one coming onto the project is confused by the non-standard use of map. Better to make things explicit and use persistentMapOf/immutableHashMapOf/etc... during creation. If a new person forgets, the linter will fail and they will get a message telling them why they should use one of the alternatives instead.
b
Ignore extension functions and immutablity here. If understand OP right he's just asking for a way to make custom code be "globally" visible in the same way stdlib is
c
The simple answer is no, you can’t make the import always used by default to my knowledge. With the scripting engine, I think you can, but scripting is kind of a special edge-case of Kotlin, in general. AOP and other mechanisms that would make this happen are likely only going to be available in JVM/Android targets because they work by rewriting Java bytecode (which happens after the Kotlin compiler finishes its job). Having the import in each file is the intended mechanism for this use-case. But then consider that
.map
in your project means something different than every other Kotlin project out there, and so is difficult to enforce the proper usage and is not good having to train team members on this fact. By using extension functions with a different name, it becomes clear what the expectation is (use
.mapImmutable
instead of
.map
), easy to detect in code reviews and lint, and follows Kotlin conventions and practices.
👍 1
So while there are ways to make this work, it might be a signal of something else in your project that needs to be addressed instead.
List
should suffice to keep a list from being modified, and if you’re having issues with that list being modified, then you need to train your team members to not cast lists to
MutableList
but use
.toMutableList()
instead, or to always send a copy of the list into Java code
n
@Big Chungus yes, thanks. It's tricky sometimes about questions, if you don't give an example, everyone jumps on "why do you want this", if you do, everyone jumps on the example 🙂
@Casey Brooks list is not enough to keep things from being modified
You don't need to do casts in order to end up modifying a
List
in general. for the
List
that you specifically get from
map
, yes, that's true, but you don't get the guarantee in the type system, so it's not good enough. Hence why I said,
map
is mis-designed
The problem with your
.mapImmutable
approach, is that people coming onto the team will almost certain use
.map
a lot, and if that' snot what you want, then now you have to catch
map
usages everywhere which is much more work
c
Right, but the deeper question is why is it not sufficient? The Kotlin
List
interface has no methods that allow it to be modified, so you must be doing something dangerous to that object which allows it to be modified (reflection, casting to
MutableList
, sending it to unsafe Java code, etc).
List
by itself doesn’t need to be fully immutable to be safe, assuming you are using it safely
n
uh, no
you don't need to do anything whacky to mutate a List
b
Best example of modifying a List I've seen is this:
Copy code
data class ImmutablePretender(val list:List<Any>);

fun main() {
  val trickster = mutableListOf<Any>();
  val pretender = ImmutablePretender(trickster);
  println(pretender.list.size) // 0
  trickster.add(1);
  println(pretender.list.size) // 1
}
n
yup
again, no offense Casey but this is just misinformation. List is not really safe as a member of a class
c
To reiterate,
List
implied a list that cannot be changed or mutated. But it is just an interface, any real guarantees of a truly immutable List must be an implementation detail. What more could you add to a the List interface to make sure the implementation is safe?
💯 2
n
Err, you can have an ImmutableList interface? And part of the implicit contract is that anything implementing the interface really doesn't allow mutation?
b
How do you enforce that? Nothing's stopping implementing classes from adding
set
methods
Since interfaces specify only a bare minimum a class needs to have, not the boundaries of what it can have
n
nothing is stopping you from implementing the
List
interface incorrectly, as well, so that things like
get
actually perform mutations
interfaces aren't a guarantee of correctness. They specify things up to a certain degree, that's all.
c
By the same logic, there’s nothing that an
ImmutableList
interface can do that would prevent the
ImmutablePretender
problem above. The only thing that could is if
ImmutableList
is a final class (that is, an implementation)
b
Exactly. All I'm saying is that the interface will never enforce immutability, it's just not possible
n
There's a difference between Machiavelli, and Murphy, right?
List is implemented by mutable objects, right in the standard library
it all boils down to the chance of misuse in real world software
c
Not necessarily.
listOf()
with a single element on JVM returns
Collections.singletonList
, which is immutable
n
the odds that someone has a MutableList and uses it to initialize ImmutablePretender are high
b
Ah, but that's not the interfaces fault. The fault is at the builders that return interface implementations backed by mutable objects
n
@Big Chungus it is the interface's fault in the sense that it makes sense to have two separate interfaces
they mean two different things
they both mean reasonable, useful things.
b
Don't we have MutableList for exactly that?
👆 2
n
No, I mean List and ImmutableList mean two differnt things
b
Just different choice of words... List & MutableList -> ImmutableList & List
c
How so? If you can create an
ImmutableList
implementation that misuses that interface, how does it help any more than the normal
List
interface?
n
Uh, no
List, ImmutableList, and MutableList
three different interfaces. Three different meanings.
@Casey Brooks if you deliberately misuse it, then sure.
You guys are commiting the Nirvana fallacy here.
b
How is List different from the Theoretical ImmutableList in kotlin?
n
Because it's not perfect, it's not worth having?
MutableList
does not implement
ImmutableList
MutableList
does implement
List
b
But doesn't kotlin's List define all the structure an ImmutableList would define? Looks like a matter of different naming here
n
... but I just explained how it's different?
The fact that MutableList implements one but not the other makes all the difference?
b
Ok, what would ImmutableList have that current List doesn't?
n
I'm not sure 🙂 https://github.com/Kotlin/kotlinx.collections.immutable/tree/master/core/commonMain/src could read that. My point is, it doesn't have to have anything new to be worthwhile.
👍 1
b
I understand where you're going (I think), but wouldn't ImmutableList be just a marker interface then?
n
I'm not sure what exactly you mean by "just" a marker, but yes, it could be an interface that inherits from List, and does nothing else, and still be useful
b
Got it. Yeah, I see your point now
n
You guys tend to focus on the idea that someone could implement the interface incorrectly, but that's true of all interfaces, to varying degrees
the real point is that people very rarely implement these data structures
but they do create them a lot, and pass them into classes a lot
ImmutablList prevents the situation you outlined with ImmutablePretender
👍 1
the reason why I picked on
map
, is because
map
is a waste in that sense.
map
creates a new list, with only one reference, which is read-only.
b
Yep, just drawing a strict line between (im)mutability
n
So
map
really is immutable shy of casting.
j
There are already well-established Immutable collections that enforce the correct semantics. I agree with what Nir is saying about the dangers of List (that's why some libraries and other languages create defensive copies when returning a list). However, the proposed solution is actually going to add more overhead to the project as a whole.
n
Performance overhead?
j
In general immutables are going to take up more memory, the persistent versions will share internal structure, at the cost of overhead
n
There are different ways to do them. But yeah, that's one common approach.
Probably for most projects the performance difference is basically irrelevant.
j
oh I see, no I mean maintenance overhead, training, addressing the wrong issue, rampup/onboarding
n
I think it's a pretty microscopic amount of onboarding. Learning a new company's codebase is already typically a huge amount of work.
We're talkihng about a prelude that could be fully understood in a handful of hours.
j
I think you will get the best of all worlds with the linter approach.. Just have a rule that says anyone importing the standard map gets a warning.. And they can override it if they really need to with an annotation
n
you don't need to important the standard map 🙂 its' there for free
so you have to lint all code, because at any time it could be used
that's exactly why I'd want the prelude in by default. because, the standard library puts its things in by default, right?
If you don't like the things the standard library puts in by default, it makes much more sense to replace them, than do anything else
j
Ah good point, didn't realise the import is implicit. Not sure how well the linter would deal with that. The problem with replacing anything in the standard library is the element of surprise. You might even yourself forget after a few years of not touching the code.
n
I kind of view it though as part of an overall strategy. If you're committed to "immutable by default" I think it makes sense
I think you can try to approach Kotlin in a way that defaults to immutability for almost no cost, and benefit by eliminating certain categories of errors, surprises etc
This stuff with
map
is peanuts compared to dealing with immutable data classes; need to use arrow lenses for that 🙂 Still very easy to learn, but more foreign
j
From my experience, it's really hard to get buy-in from other developers. I had a whole project based around java immutables (it's a library).. And I ended up being the only maintainer on that section of the code, because no one else could be bothered to learn it. It really depends on the maturity and experience of the team. If you want to embrace immutability and it's applicable to your use case, I'd go full-in on functional programming.
n
That's the thing, I don't find many other things in FP necessarily that compelling.
I don't even come from FP, I come from C++. Most objects there hold their members by value. So it's much more rare that somebody can change your state at a distance.
Immutability solves a very specific, real problem, and with the right tools the cost is actually pretty low. Flipping through arrow, I didn't feel that way about most of the other stuff.
j
Central idea of FP is composition (not object/data composition, functional composition). It really forces you to design your programs differently, but then you can layer stuff without worrying about internal interactions.
n
Sure, my point is though that you can use this immutability in kotlin, and do pretty much nothing differently.
and there's pretty much no downside. Getting buy-in from devs who don't understand this stuff well, sure, is hard. But that's not a technical downside.
I don't think paradigms have to come wholesale. After all, kotlin has cherry picked many popular things from FP, without the rest (e.g. the emphasis on lambdas, chaining operations on data, pattern matching light)
j
I agree with your sentiment, but having tried to do similar things in the past, I know the problem is not really on the technical side... And you will saddle yourself with hidden technical debt which may not actually bring much benefit.. You'd have to do your own analysis for your use-case, etc...
n
I'm not sure what kind of technical debt you're referring to. I definitely wouldn't be implementing piles of stuff myself. Really, it's just a matter of taking kotlinx.immutable and arrow.lenses as givens in the project. Somewhere, there must be other people doing that... right? 🙂
But I agree, the non-technical problems.
j
Yes, I do it myself. It's a great approach.
n
I haven't ever argued for immutability, but I work at a C++ shop that used to do tons of really regressive things, and yes, the inertia of how things are already done can be devastating.
I think it would make for a good blog post.
maybe someone could use it as ammo 🙂
j
It's really dependent on the class of problems you are trying to address. Immutability can really hurt you in certain low-latency contexts. But from a multithreading/safety perspective, I don't see why it shouldn't be the default option.
I tried once to convince my C++ quant dev that he should strive to write pure functions. Just had to give up after a while. Guy was brilliant but he had his way of doing things and not really interested in software development. My time was better spent writing a regression test suite that would automatically reject his merges if they caught a multi-threading problem (which they often did)
n
Yeah....
Were his functions impure because they modified globals, or because they had arguments that were mutable references or similar
j
yes.. all of the above
const correctness was not a thing
n
ugh. I'm so sorry.