ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 02.01.2026

Просмотров: 3508

Скачиваний: 0

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Section 21.8

Chapter 21 · Implicit Conversions and Parameters

501

21.8 Debugging implicits

Implicits are an powerful feature in Scala, but one which is sometimes difficult to get right. This section contains a few tips for debugging implicits.

Sometimes you might wonder why the compiler did not find an implicit conversion that you think should apply. In that case it helps to write the conversion out explicitly. If that also gives an error message, you then know why the compiler could not apply your implicit. For instance, assume that you mistakenly took wrapString to be a conversion from Strings to Lists, instead of IndexedSeqs. You would wonder why the following code does not work:

scala> val chars: List[Char] = "xyz" <console>:19: error: type mismatch; found : java.lang.String("xyz")

required: List[Char]

val chars: List[Char] = "xyz"

ˆ

In that case it helps to write the wrapString conversion explicitly, to find out what went wrong:

scala> val chars: List[Char] = wrapString("xyz") <console>:19: error: type mismatch;

found : scala.collection.immutable.WrappedString required: List[Char]

val chars: List[Char] = wrapString("xyz")

ˆ

With this, you have found the cause of the error: wrapString has the wrong return type. On the other hand, it’s also possible that inserting the conversion explicitly will make the error go away. In that case you know that one of the other rules (such as the Scope Rule) was preventing the implicit conversion from being applied.

When you are debugging a program, it can sometimes help to see what implicit conversions the compiler is inserting. The -Xprint:typer option to the compiler is useful for this. If you run scalac with this option, then the compiler will show you what your code looks like after all implicit conversions have been added by the type checker. An example is shown in Listing 21.6 and Listing 21.7. If you look at the last statement in each of these listings, you’ll see that the second parameter list to enjoy, which was

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 21.9

Chapter 21 · Implicit Conversions and Parameters

502

left off in the code in Listing 21.6, “enjoy("reader"),” was inserted by the compiler, as shown in Listing 21.7:

Mocha.this.enjoy("reader")(Mocha.this.pref)

If you are brave, try scala -Xprint:typer to get an interactive shell that prints out the post-typing source code it uses internally. If you do so, be prepared to see an enormous amount of boilerplate surrounding the meat of your code.

21.9 Conclusion

Implicits are a powerful, code-condensing feature of Scala. This chapter has shown you Scala’s rules about implicits, and it has shown you several common programming situations where you can profit from using implicits.

As a word of warning, implicits can make code confusing if they are used too frequently. Thus, before adding a new implicit conversion, first ask whether you can achieve a similar effect through other means, such as inheritance, mixin composition, or method overloading. If all of these fail, however, and you feel like a lot of your code is still tedious and redundant, then implicits might just be able to help you out.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index



Chapter 22

Implementing Lists

Lists have been ubiquitous in this book. Class List is probably the most commonly used structured data type in Scala. Chapter 16 showed you how to use lists. This chapter “opens up the covers” and explains a bit how lists are implemented in Scala.

Knowing the internals of the List class is useful for several reasons. You gain a better idea of the relative efficiency of list operations, which will help you in writing fast and compact code using lists. You also learn a toolbox of techniques that you can apply in the design of your own libraries. Finally, the List class is a sophisticated application of Scala’s type system in general and its genericity concepts in particular. So studying class List will deepen your knowledge in these areas.

22.1 The List class in principle

Lists are not “built-in” as a language construct in Scala; they are defined by an abstract class List in the scala package, which comes with two subclasses for :: and Nil. In the following we present a quick tour through class List. This section presents a somewhat simplified account of the class, compared to its real implementation in the Scala standard library, which is covered in Section 22.3.

package scala

abstract class List[+T] {

List is an abstract class, so you cannot define elements by calling the empty List constructor. For instance the expression “new List” would be ille-

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index

Section 22.1

Chapter 22 · Implementing Lists

504

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

scala

 

 

 

 

 

 

List[+T]

 

 

 

 

 

 

«sealed abstract»

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

scala

 

scala

 

::[T]

 

Nil

 

«final case»

 

«case object»

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 22.1 · Class hierarchy for Scala lists.

gal. The class has a type parameter T. The + in front of this type parameter specifies that lists are covariant, as discussed in Chapter 19. Because of this property, you can assign a value of type List[Int], say, to a variable of type

List[Any]:

scala> val xs = List(1, 2, 3) xs: List[Int] = List(1, 2, 3)

scala> var ys: List[Any] = xs ys: List[Any] = List(1, 2, 3)

All list operations can be defined in terms of three basic methods:

def isEmpty: Boolean def head: T

def tail: List[T]

These three methods are all abstract in class List. They are defined in the subobject Nil and the subclass ::. The hierarchy for List is shown in Figure 22.1.

The Nil object

The Nil object defines an empty list. Its definition is shown in Listing 22.1. The Nil object inherits from type List[Nothing]. Because of covariance, this means that Nil is compatible with every instance of the List type.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index


Section 22.1

Chapter 22 · Implementing Lists

505

case object Nil extends List[Nothing] { override def isEmpty = true

def head: Nothing =

throw new NoSuchElementException("head of empty list") def tail: List[Nothing] =

throw new NoSuchElementException("tail of empty list")

}

Listing 22.1 · The definition of the Nil singleton object.

The three abstract methods of class List are implemented in the Nil object in a straightforward way: the isEmpty method returns true and the head and tail methods both throw an exception. Note that throwing an exception is not only reasonable, but practically the only possible thing to do for head: Because Nil is a List of Nothing, the result type of head must be Nothing. Since there is no value of this type, this means that head cannot return a normal value. It has to return abnormally by throwing an exception.1

The :: class

Class ::, pronounced “cons” for “construct,” represents non-empty lists. It’s named that way in order to support pattern matching with the infix ::. You have seen in Section 16.5 that every infix operation in a pattern is treated as a constructor application of the infix operator to its arguments. So the pattern x :: xs is treated as ::(x, xs) where :: is a case class. Here is the definition of the :: class:

final case class ::[T](hd: T, tl: List[T]) extends List[T] { def head = hd

def tail = tl

override def isEmpty: Boolean = false

}

The implementation of the :: class is straightforward. It takes two parameters hd and tl, representing the head and the tail of the list to be constructed.

1To be precise, the types would also permit for head to always go into an infinite loop instead of throwing an exception, but this is clearly not what’s wanted.

Cover · Overview · Contents · Discuss · Suggest · Glossary · Index