Lieu Zheng Hong
Lieu Zheng Hong

Reputation: 696

Scala Map's get vs apply operation: "type mismatch"

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

Answers (2)

lambda.xy.x
lambda.xy.x

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

Andrey Tyukin
Andrey Tyukin

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

Related Questions