I have been discussing with a friend and I wonder ...
# advent-of-code
d
I have been discussing with a friend and I wonder what your views are: He uses extension-functions more often than me. E.g. on Day03 (Slopes/Trees), he used an extension function on 'String' to check a coordinate (including the 'wrapping') if it is a tree or not. I used a regular function. On Day01 we both used extension-functions e.g. on Pair to to define a .sum(). So when is it a good idea to use extension functions and when not?
My 'gutfeeling' tells me not to add extension functions to classes where it does not fit the classes logic in some way. A String is typically not a map of snow and trees, so adding a .'hasTreeAt()' function to String kind of feels awkward. Compared to Pair<Int, Int>.sum() which feels more like a 'logical' extension of functionality to said class. But maybe it is alright to a add extensions in such small scopes… that is why I would love to hear other views on this. 🙂
a
I would agree with you. For logic unrelated to the class’ core purpose, I just define a normal function taking in that one parameter
e
I mostly agree, but I think the rules can be loosened for private extension functions (that don't leak into other namespaces) or when constructing DSLs
j
Yeah, I think scope is an important part of the equation, the bigger the scope the more it should fit within the original domain of the receiver. But for very local scopes where the function's purpose is clear from context I wouldn't mind something like hasTreeAt
n
If it's in a local scope I tend to be pretty liberal with extension functions
It gives you better auto completion and left to right flow of data
And no real downside except rather subjective things
I've used extension functions for many of my main transforming functions in AOC
j
I'm usually using extension functions for collections, like
Copy code
fun Sequence<String>.groups(): Sequence<List<String>> = sequence {

    var currentGroup = mutableListOf<String>()

    forEach {
        if (it.isEmpty()) {
            yield(currentGroup.toList())
            currentGroup = mutableListOf()
        } else {
            currentGroup.add(it)
        }
    }

    if (currentGroup.isNotEmpty()) {
        yield(currentGroup.toList())
    }
}

fun String.groupSequence() = this.trim().lineSequence().groups()
But for own classes - rarely. Only if I want to keep it private in single file.
n
You mean, for your own classes you prefer member functions over extensions?
j
usually, yes
n
Kinda think thats backwards. Should prefer extension functions by default, because they don't have access to private data, so they can't be broken by changing implementation details of your class
Use a member function if you need to access private data, or need polymorphism. Extension functions are also better for code reuse (not always relevant). The standard library follows this as well, usually standard library classes have far more extension functions than member functions.
j
true, good point. on the other hand - it's different when you're creating an API class (like in the library you want to publish) and not so strict when it's specific to your project and you have total control on the interface it's presenting to the world.