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

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

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

Добавлен: 02.01.2026

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

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

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

Section 21.5

Chapter 21 · Implicit Conversions and Parameters

490

marked implicit where they are defined, but also the last parameter list in someCall’s or someClass’s definition must be marked implicit.

Here’s a simple example. Suppose you have a class PreferredPrompt, which encapsulates a shell prompt string (such as, say "$ " or "> ") that is preferred by a user:

class PreferredPrompt(val preference: String)

Also, suppose you have a Greeter object with a greet method, which takes two parameter lists. The first parameter list takes a string user name, and the second parameter list takes a PreferredPrompt:

object Greeter {

def greet(name: String)(implicit prompt: PreferredPrompt) { println("Welcome, "+ name +". The system is ready.") println(prompt.preference)

}

}

The last parameter list is marked implicit, which means it can be supplied implicitly. But you can still provide the prompt explicitly, like this:

scala> val bobsPrompt = new PreferredPrompt("relax> ") bobsPrompt: PreferredPrompt = PreferredPrompt@74a138

scala> Greeter.greet("Bob")(bobsPrompt) Welcome, Bob. The system is ready. relax>

To let the compiler supply the parameter implicitly, you must first define a variable of the expected type, which in this case is PreferredPrompt. You could do this, for example, in a preferences object:

object JoesPrefs {

implicit val prompt = new PreferredPrompt("Yes, master> ")

}

Note that the val itself is marked implicit. If it wasn’t, the compiler would not use it to supply the missing parameter list. It will also not use it if it isn’t in scope as a single identifier. For example:

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

Section 21.5

Chapter 21 · Implicit Conversions and Parameters

491

scala> Greeter.greet("Joe")

<console>:10: error: could not find implicit value for parameter prompt: PreferredPrompt

Greeter.greet("Joe")

ˆ

Once you bring it into scope via an import, however, it will be used to supply the missing parameter list:

scala> import JoesPrefs._ import JoesPrefs._

scala> Greeter.greet("Joe") Welcome, Joe. The system is ready. Yes, master>

Note that the implicit keyword applies to an entire parameter list, not to individual parameters. Listing 21.1 shows an example in which the last parameter list of Greeter’s greet method, which is again marked implicit, has two parameters: prompt (of type PreferredPrompt) and drink (of type

PreferredDrink):

class PreferredPrompt(val preference: String) class PreferredDrink(val preference: String)

object Greeter {

def greet(name: String)(implicit prompt: PreferredPrompt, drink: PreferredDrink) {

println("Welcome, "+ name +". The system is ready.") print("But while you work, ")

println("why not enjoy a cup of "+ drink.preference +"?") println(prompt.preference)

}

}

object JoesPrefs {

implicit val prompt = new PreferredPrompt("Yes, master> ") implicit val drink = new PreferredDrink("tea")

}

Listing 21.1 · An implicit parameter list with multiple parameters.

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


Section 21.5

Chapter 21 · Implicit Conversions and Parameters

492

Singleton object JoesPrefs in Listing 21.1 declares two implicit vals, prompt of type PreferredPrompt and drink of type PreferredDrink. As before, however, so long as these are not in scope as single identifiers, they won’t be used to fill in a missing parameter list to greet:

scala> Greeter.greet("Joe")

<console>:14: error: could not find implicit value for parameter prompt: PreferredPrompt

Greeter.greet("Joe")

ˆ

You can bring both implicit vals into scope with an import:

scala> import JoesPrefs._ import JoesPrefs._

Because both prompt and drink are now in scope as single identifiers, you can use them to supply the last parameter list explicitly, like this:

scala> Greeter.greet("Joe")(prompt, drink) Welcome, Joe. The system is ready.

But while you work, why not enjoy a cup of tea? Yes, master>

And because all the rules for implicit parameters are now met, you can alternatively let the Scala compiler supply prompt and drink for you by leaving off the last parameter list:

scala> Greeter.greet("Joe") Welcome, Joe. The system is ready.

But while you work, why not enjoy a cup of tea? Yes, master>

One thing to note about the previous examples is that we didn’t use String as the type of prompt or drink, even though ultimately it was a String that each of them provided through their preference fields. Because the compiler selects implicit parameters by matching types of parameters against types of values in scope, implicit parameters usually have “rare” or “special” enough types that accidental matches are unlikely. For example, the types PreferredPrompt and PreferredDrink in Listing 21.1 were defined solely to serve as implicit parameter types. As a result, it is unlikely

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


Section 21.5

Chapter 21 · Implicit Conversions and Parameters

493

that implicit variables of these types will be in scope if they aren’t intended to be used as implicit parameters to Greeter.greet.

Another thing to know about implicit parameters is that they are perhaps most often used to provide information about a type mentioned explicitly in an earlier parameter list, similar to the type classes of Haskell. As an example, consider the maxListUpBound function shown in Listing 21.2, which returns the maximum element of the passed list:

def maxListUpBound[T <: Ordered[T]](elements: List[T]): T = elements match {

case List() =>

throw new IllegalArgumentException("empty list!") case List(x) => x

case x :: rest =>

val maxRest = maxListUpBound(rest) if (x > maxRest) x

else maxRest

}

Listing 21.2 · A function with an upper bound.

The signature of maxListUpBound is similar to that of orderedMergeSort, shown in Listing 19.12 on page 444: it takes a List[T] as its argument, and specifies via an upper bound that T must be a subtype of Ordered[T]. As mentioned at the end of Section 19.8, one weakness with this approach is that you can’t use the function with lists whose element type isn’t already a subtype of Ordered. For example, you couldn’t use the maxListUpBound function to find the maximum of a list of integers, because class Int is not a subtype of Ordered[Int].

Another, more general way to organize maxListUpBound would be to require a separate, second argument, in addition to the List[T] argument: a function that converts a T to an Ordered[T]. This approach is shown in Listing 21.3. In this example, the second argument, orderer, is placed in a separate argument list and marked implicit.

The orderer parameter in this example is used to describe the ordering of Ts. In the body of maxListImpParm, this ordering is used in two places: a recursive call to maxListImpParm, and an if expression that checks whether the head of the list is larger than the maximum element of the rest of the list.

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


Section 21.5

Chapter 21 · Implicit Conversions and Parameters

494

def maxListImpParm[T](elements: List[T]) (implicit orderer: T => Ordered[T]): T =

elements match { case List() =>

throw new IllegalArgumentException("empty list!") case List(x) => x

case x :: rest =>

val maxRest = maxListImpParm(rest)(orderer) if (orderer(x) > maxRest) x

else maxRest

}

Listing 21.3 · A function with an implicit parameter.

The maxListImpParm function, shown in Listing 21.3, is an example of an implicit parameter used to provide more information about a type mentioned explicitly in an earlier parameter list. To be specific, the implicit parameter orderer, of type T => Ordered[T], provides more information about type T—in this case, how to order Ts. Type T is mentioned in List[T], the type of parameter elements, which appears in the earlier parameter list. Because elements must always be provided explicitly in any invocation of maxListImpParm, the compiler will know T at compile time, and can therefore determine whether an implicit definition of type T => Ordered[T] is in scope. If so, it can pass in the second parameter list, orderer, implicitly.

This pattern is so common that the standard Scala library provides implicit “orderer” methods for many common types. You could therefore use this maxListImpParm method with a variety of types:

scala> maxListImpParm(List(1,5,10,3)) res9: Int = 10

scala> maxListImpParm(List(1.5, 5.2, 10.7, 3.14159)) res10: Double = 10.7

scala> maxListImpParm(List("one", "two", "three")) res11: java.lang.String = two

In the first case, the compiler inserted an orderer function for Ints; in the second case, for Doubles; in the third case, for Strings.

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