tl;dr:
fibs
is defined as a Stream[BigInt]
, so when you prepend an Int
to it (1 #:: ...
), the compiler looks for an implicit conversion from Int
to BigInt
and finds it in BigInt.int2bigInt
There are a couple of things going on here.
1) The BigInt
companion object defines an implicit conversion from Int
to BigInt
:
implicit def int2bigInt(i: Int): BigInt = apply(i)
This means that wherever you need a BigInt
you can supply an Int
and the implicit conversion will convert the value. You can also say that Int
s can be viewed as BigInt
s.
2) methods that end with a colon are right-associative. This means that 1 #:: 2 #:: Stream.empty[BigInt]
can be effectively rewritten as Stream.empty[BigInt].#::(2).#::(1)
Now, if you look at the signature of Stream.#::
(def #::(hd: A): Stream[A]
) you'll see that Stream[BigInt].#::(x)
can only compile if x
is a BigInt
.
When you call 1 #:: 2 #:: Stream.empty[BigInt]
you are calling Stream[BigInt].#::
passing an Int
value instead of a BigInt
, but, as I mentioned earlier, Int
s can be viewed as BigInt
s, so they are automatically converted to BigInts and everything compiles.
When you do this: val s1:Stream[BigInt] = 1 #:: 2 #:: Stream.empty
you are doing a different thing instead: you are creating on the right hand side a Stream[Int]
(Stream.empty
type is inferred to be Int from the 1,2 values you pass) and then you are assigning this value to a Stream[BigInt]
val.
Unfortunately, there is no implicit conversion from Stream[A]
to Stream[B]
, even if A
can be viewed as B
, thus compilation fails.
You can define your own implicit conversion though:
implicit def asBigIntStream(xs: Stream[Int]): Stream[BigInt] = xs.map(BigInt.int2bigInt)
val s1:Stream[BigInt] = 1 #:: 2 #:: Stream.empty //This now works
There's something else going on with List
s: differently from Stream
, the List
cons is defined as:
def ::[B >: A] (x: B): List[B]
With Stream.#::(x)
you needed x to be the exact same type as the Stream you were prepending x
to. With List.::(x)
, instead, x
(that has type B
) can be an instance of a supertype of the list's type. The resulting list will be a List[B]
, i.e. prepending to a list can widen its type.
So, when you do 2 :: List.empty[BigInt]
you're effectively Calling List[A].::(x: B)
where A
is BigInt
and B
is inferred to be Any
because Any
is the most strict supertype of BigInt
that is also a supertype of Int
.
Since this makes the compiler happy, no implicit conversions are looked for, and the resulting list is a List[Any] that you can't use anymore as a list of integers.
You can basically call whatever :: List[X]
to get a List[Y]
where Y
is the most strict supertype of both X
and the type of whatever
So, why doesn't val l1:List[BigInt] = 1 :: 2 :: List.empty[BigInt]
work while val l2 : List[BigInt] = 1 :: BigInt(2) :: List.empty[BigInt]
does?
It's because of type inference. Let's rewrite the two expressions removing the right-associativity:
val l1: List[BigInt] = (List.empty[BigInt].::(2)).::(1) // incorrect, found Any required BigInt
val l2: List[BigInt] = (List.empty[BigInt].::(BigInt(2))).::(1) // correct
I'm not 100% sure of this (anyone please correct me if I'm wrong):
The compiler can help the type inference only on the last application of ::
(List.empty[BigInt].::(2))
is a List[Any]
well before applying .::(1)
so there's nothing we can do
(List.empty[BigInt].::(BigInt(2)))
is already a List[BigInt]
and the compiler can try to make .::(1)
a BigInt
(thus looking for implicit conversions)