I often get frustrated with the lack of overridable unary operator for a parameter-less get.
I mean, cont[42] will get translated to cont.get(42) but there are no operator that translates to cont.get().
Instinctively, because I come from C++, I would use the operator * and have *cont translated to cont.get(), but in kotlin, the * is the spread operator.
So I ended up overriding the unaryPlus operator, but somehow +cont does not seem very semantic. To me, +cont has, above all, an arithmetical semantic. Thatās what I liked about *cont syntax: because it is not valid arithmetic, its semantic is more easily understood.
Furthermore, Iād like an operator that translates to cont.get(), not cont.unaryPlus().
Since cont[a, b] is valid in Kotln, maybe the solution is cont[]. I donāt really like it though, Iād prefer a unary prefixed operator.
Lots of candidates are available: %cont ^cont #cont ~cont &cont ĀÆ\_(ć)_/ĀÆcont
The use case is very simple: Every time I get a container class that contains one value to add some information (you know, composition over inheritance).
This is an example:
class Counter<T>(private val _obj: T) {
var count: Int = 0
private set
operator fun get(): T { // Operator!
++count
return _obj
}
}
Also, all Atomic... classes are good candidates. Iād rather read/write println("Current value: ${#count}") then println("Current value: ${count.get()}") (provided that count is an AtomicInteger).
@dalewking Well, this solution troubles me for not being semantic. I see a function call, not an access to an enclosed object. cont["name"] is really semantic: one can instantly understand that it means āAccessing the object that the variable cont is holding under the key "name"ā. The real type of cont, whether itās a Map or any other type of container does not come into account. cont["name"] is readable, semantic and instantly understandable. I would consider it bad practice if it would do any kind of heavy computing, because I do not consider it a function call, but a value access. Of course it translates to a function call, but it does not read as one.
Personnally, I like #cont because I can easily associate the semantic of cont.get() to this syntax (once again, coming from C++), but any kind of postfix operator would also do .
Thinking about it some more, I think the problem is that you are trying to approach this from the wrong angle, trying to think of it in terms of having an object and applying an operator to it. My solution was sticking with your āthe only tool I have is a hammer, so this problem is a nailā approach.
Lets step back and focus on what you are trying to achieve rather than the way you are trying to achieve it. As I understand it you have some value (weāll call it X) that you want to expose via a property called cont and every time you read the value via cont you want to increase a counter.
A more general description of the problem is that you have some value of some type T and you want to expose it via a property and to be able to perform an action whenever the property is read.
The focus should be on the property. Kotlin has a way to do that which Java and C++ donāt have and it is via property delegation
So a mechanism for performing an action every time a value is read via a property might look like this:
val myProperty by someValue.readObserver {
println("property $name whose value is $it was just read")
}
Then to apply that to the specific scenario you talked about we could do:
private val X = "The value you are trying to expose through cont property"
private val readCounter = AtomicInteger()
val cont by X.readObserver { readCounter.getAndIncrement() }
There I did it without using ANY operators at all.
Kotlin has a standard delegate like this for writes to a property, but not reads.
var foo by Delegates.observable(X)
{ property, old, new ->
println("property ${property.name} was changed from $old to $new") }
I donāt agree.
I was really talking about classes that are containers that add smantic to an enclosed property. AtomicInteger is an example, WeakReference is another. My Counter class is yet another example.
Those classes uses are through the get function most of the time. Because āmulti-objectā containers do have an operator to cont.get(key): cont[key].
Letās take WeakReference. For most of itās usage, youāre going to call get(). It hink calling myFunction(#ref) or even myFunction(ref[]) is more understandable than myFunction(ref.get()) for these reasons:
It reads as an access, not a function call, which is really itās underlying semantic.
It emphasizes the fact that ref is a container whose primary purpose is to contain a value, and add some semantic regarding that value.
Now regarding your solution, I donāt like it. Itās very personal so donāt take it the wrong way Itās perfectly valid, but not how I would have written it.
Here are the reasons:
It only works for properties. I must be able to have a container as a variable. I canāt pass the container as a variable. I must be able to call myFunction(ref) or myFunction(#ref) whether myFunction takes a WeakReference<T> or a T parameter.
Any āadded semanticā property is along the āmainā property. In your example, Thereās X, thereās readCounter and thereās cont. Three properties (two of them have backing fields), all three alongside probably other properties inside the object when all I really wanted was one property, on which I will use get() most of the time, and getCount() once in a while. I donāt like having readCounter alongside cont because readCounter is related to cont. Itās effectively a property of cont, not of the englobing object.
After working with it some more, you donāt even need the property delegate, you can do the same thing with custom getter:
private val X = "The value you are trying to expose through cont property"
private val readCounter = AtomicInteger()
val cont = X
get() = field.apply{readCounter.getAndIncrement()}
So I read your response mostly as āI donāt like your solution because I canāt hit it with my hammer, which is the only tool I have and know how to useā
X is just a property here just for simplifying the illustration by giving a name to the value. it is just a value and could be inlined in the example. So lets do that using my revised code that got rid of the delegate:
private val readCounter = AtomicInteger()
val cont = "The value you are trying to expose through cont property"
get() = field.apply{readCounter.getAndIncrement()}
I could actually get rid of the backing field on cont and put the value into the getter, but then I would have had to specify the type for cont in the example
You have to store the state of the count somewhere so that canāt be eliminated it will be a property somewhere. The only real question here is how someone reads the count of accesses. I was assuming that the object that declared the cont property was the only one that needed the count so I stored it there. Feel free to move it somewhere else.
To me the question is how do you get count of accesses of the property. Is it an attribute of the property or is it separate from the property. If the latter then I already showed the solution for that. If the former then you have to a way to say you want the access count for the property or you want the value of the property. In that case you need to quit thinking of the property as thing that has a value, but instead it should be viewed as a function that produces a value, Donāt think of it as an object that you call get on, it is a function you can invoke to get a result. So this would be the appropriate solution:
class CountedFunction<T>(private val producer: () -> T) : () -> T
{
constructor(value: T) : this( { value } )
val counter = AtomicInteger()
val invocationCount: Int get() = counter.get()
override fun invoke() = producer().apply { counter.getAndIncrement() }
}
val cont = CountedFunction("value to return")
fun foo()
{
val x = cont();
println("cont was invoked ${cont.invocationCount} times")
}
I realize this is not the way you normally think about it since you probably are not use to functional programming.
#ref carries no meaning and makes me think someone is trying to write Perl. I have no objections to having the operator overloading allowing a no parameter version of get to allow ref although I donāt think this is an intuitive use of .
Once again, what is a WeakReference in a functional programming mindset, it is a function that you can invoke to get the value (or null). So the solution is
inline operator fun <T> WeakReference<T>.invoke() = get()
Then I can use function call semantics on any WeakReference as in
val x = someWeakReference() // Result will be nullable
Good luck convincing anyone that #ref is a better choice than ref()
Sorry for my bad english.
I meant that most usage of a container is access and set. For example, most usage of a map will be map[key] and map[key] = value.
As for your comment "So I read your response mostly as āI donāt like your solution because I canāt hit it with my hammer, which is the only tool I have and know how to useā, please donāt put words in my mouth.
My real motive is āThereās a tool I feel is missing, Iām proposing itā. Just. That.
If I would do phrase your solution the way you phrased my request, I would say āI have a nail to hammer, but I donāt have a hammer, so letās use a brick instead, you can very well hit the nail with itā.
I donāt see an access inside a map as a function, and I donāt believe kotlin does either . If it did, then we would use respectively map(key) and map(key, value). Or even map(key)(value).
The example of #cont is just thatā¦ an example. Any operator whether prefix or postfix would do. Itās just my personnal favourite, feel free to replace it with the syntax of your choice.
So, once again: we already have operators that translates to parameterized get.
What I would like is an operator that convey the same āaccessing enclosedā semantic, just like the [key] operator that kotlin has, but without a parameter. In essence, the semantic of the unary operator * of C++.
I mean, it seems completely illogical to me to have an operator that converts to get(key), and none that converts to get(). cont() has a semantic, and it is not that one.
I have done some pure functionnal programming, and Iām not a fan. Kotlin is by no means a functionnal language, it has functionnal features (which I love), but itās far from a functionnal language (which is also why I love it).
I donāt believe cont() has the same semantics as cont.get(). Thereās a reason why cont() translates to cont.invoke(). Because it conveys the semantic of a function invocation, not an access.
To me, overriding an operator to convey a different semantic than the āregularā use of this operator is a very bad practice.
In this instance, the return of cont() should be the result of a computation, represented by cont.
Certainly. Developers spend a lot more time reading code than writing code. As a consequence, code should communicate intent as clearly as possible. To be able to do that, you need to use proper vocabulary to communicate. This is why functions, classes, variables, and objects have names. That name should be as descriptive as possible to avoid any misunderstandings (e.g. that is why a range has an endInclusive and not just b that could mean bound).
We have all been trained to have a common understanding of some abbreviations, though: == has to to with equality, [3] has to do with accessing the third element of something, and so on. This is why I strongly believe that every one of those abbreviations needs to have a strong justification to exist. The more hard to grasp meanings such operators can have, the more difficult it will become to understand code. Especially if different projects associate distinct meanings with operators.
I wouldnāt even be able to imagine what ĀÆ\_(ć)_/ĀÆ would do as an operator but disambiguateAbbreviations() would help a lot (if I knew a little about the context). I strongly believe, that this is not something Kotlin should try to achieve.
Donāt get me wrong, I do understand what you wanted to say with those examples. But even the more pragmatic examples are open for interpretation: I would rather associate #cont with cont.size() than with cont.get() for example.
This is why I also said that [] could be ok for get() because it is a simple extension of the concept that is behind the idea of ā[i] means get(i)ā rather something completely new.
I donāt see it that way either, it is more like array indexing which is why Kotlin provides the same operator for it and why they didnāt allow you to define the operator without any parameters like you were suggesting. But I am not sure how maps got in here as I have not mentioned them at all. What would be the meaning of your mythical # operator on a map?
I did, the correct way to handle it is custom getter for the property or as a function.
The get of map and list and the get of something like weak reference are logically 2 very different concepts that just coincidentally happen to be mapped to the same method name in Javaās non-functional world.
For things like map, arrays, and lists, the get is an indexing operation, which say to retrieve a particular item in the collection. Java called it get since it has no operator overloading. That is why in Kotlin, they require a parameter, since it make no sense to index a collection based on nothing
Parameterless get methods on things like weak reference are really because the object is function object (sometimes called a functor), but since Java does not have first class functions they had to resort to a parameterless get method.
In a language that supports functional programming like Kotlin the proper representation of these are with indesing operator for the map and list case, and for something like WeakReference to model it as a function. It would actually be nice if Kotlin automatically mapped parameterless methods named get to the parameterless invoke operator (and they can do the same for methods named call with any number of parameters), but until then you can do it yourself with extension functions as I already demonstrated.
I donāt want to get into a definition war over what it means to be functional. Kotlin is functional in that functions are first class citizens and we have higher order functions etc. It is less functional than Scala or Haskell, but on the other end of the spectrum Java is completely not functional and its API was designed that way, so when you have something like a WeakReference that really is a function object you map it to a parameterless get method. In a functional world you would have designed it as a function.
You may believe that, but you are wrong. You have this object and you want to tell it to return a value to you, that is pretty much the definition of a function object.
For the record if there were a need for a custom prefix operator the most likely candidate is * since Kotlin already uses it as a prefix operator for the spread operation.
For completeness sake I checked the Java 8 docs for how many places a parameterless get is used and here are the only cases:
Implementations of java.lang.ref.Reference, including Soft, Weak and Phantom references.
Iāve already made the case for function invocation semantics
java.lang.ThreadLocal
Implementations java.nio.Buffer
If anything this get() makes the most sense as a post-increment operator
java.rmi.MarshalledObject
java.util.concurrent.atomic.AtomicXXXXX classes
implementations of java.util.concurrent.Future
These DEFINITELY are function objects and are basically functions that can start running before you call them
implementations of javax.naming.directory.Attribute
I donāt see a very strong case for your operator in this list. Several are better mapped as functions or as things like a property named value (e.g. myThreadLocal.value)
I strongly believe it to Operator overuse and misuse is so easily achivedā¦
I also agree to this. However, the meaning we associate to operators changes between languages. The fact that * is the spread operator, for example, is not a language wide consensus. Nor is the ācompanion objectā. Those are specific to Kotlin and we had to learn their semantic, whatever the language we came from.
How exactly to balance āWe need an efficient syntaxā, āWe must be specificā and āWe must be easily undestandableā is a matter of taste, I think. Kotlin is very āconservativeā in that regard (which we all agree is a great thing), but it does have, now and then, its own peculiarities