Reputation: 696
I am learning Scala and found the following:
List(('a', 1)).toMap get 'a' // Option[Int] = Some(1)
(List(('a', 1)).toMap) apply 'a' // Int = 1
(List(('a', 1)).toMap)('a') // Error: type mismatch;
found : Char('a')
required: <:<[(Char, Int),(?, ?)
(List(('a', 1)).toMap)('a')
But then assigning it to a variable works again.
val b = (List(('a', 1)).toMap)
b('a') // Int = 1
Why is this so?
The standard docs gives:
ms get k
The value associated with key k in map ms as an option, None if not found.
ms(k)
(or, written out,ms apply k
)The value associated with key k in map ms, or exception if not found.
Why doesn't the third line work?
Upvotes: 2
Views: 894
Reputation: 4998
The signature is slightly different:
abstract def get(key: K): Option[V]
def apply(key: K): V
The issue is error handling: get
will return None
when an element is not found and apply
will throw an exception:
scala> Map(1 -> 2).get(3)
res0: Option[Int] = None
scala> Map(1 -> 2).apply(3)
java.util.NoSuchElementException: key not found: 3
at scala.collection.immutable.Map$Map1.apply(Map.scala:111)
... 36 elided
Regarding the failing line: toMap has an implicit argument ev: A <:< (K,V)
expressing a type constraint. When you call r.toMap('a')
you are passing an explicit value for the implicit but it has the wrong type. Scala 2.13.0 has a companion object <:<
that provides a reflexivity method (using the given type itself instead of a proper sub-type). Now the following works:
scala> List(('a', 1)).toMap(<:<.refl)('a')
res3: Int = 1
Remark: i could not invoke <:<.refl
in Scala 2.12.7, the addition seems to be quite recent.
Upvotes: 1
Reputation: 44957
It's essentially just an idiosyncratic collision of implicit arguments with apply
-syntactic sugar and strange parentheses-elimination behavior.
As explained here, the parentheses in
(List(('a', 1)).toMap)('a')
are discarded a bit too early, so that you end up with
List(('a', 1)).toMap('a')
so that the compiler attempts to interpret 'a'
as an implicit evidence of (Char, Int) <:< (?, ?)
for some unknown types ?
, ?
.
This here works (it's not useful, it's just to demonstrate what the compiler would usually expect at this position):
(List(('a', 1)).toMap(implicitly[(Char, Int) <:< (Char, Int)]))('a')
Assigning List(...).toMap
to a variable also works:
({val l = List((1, 2)).toMap; l})(1)
Alternatively, you could force toMap
to stop accepting arguments by feeding it to identity
function that does nothing:
identity(List((1, 2)).toMap)(1)
But the easiest and clearest way to disambiguate implicit arguments and apply
-syntactic sugar is to just write out .apply
explicitly:
List((1, 2)).toMap.apply(1)
I think at this point it should be obvious why .get
behaves differently, so I won't elaborate on that.
Upvotes: 5