Programing

Scala에서 암시 적 매개 변수의 좋은 예?

lottogame 2020. 10. 24. 09:23
반응형

Scala에서 암시 적 매개 변수의 좋은 예?


지금까지 Scala의 암시 적 매개 변수는 나에게 좋지 않습니다. 전역 변수에 너무 가깝지만 Scala가 다소 엄격한 언어처럼 보이기 때문에 내 의견으로는 의심하기 시작합니다. :-).

질문 : 암시 적 매개 변수가 실제로 작동 할 때 실제 (또는 가까운) 좋은 예를 보여줄 수 있습니까 ? IOW :.보다 더 심각한 것은 showPrompt그러한 언어 디자인을 정당화합니다.

또는 반대로-암시 적이 지 않게 만드는 신뢰할 수있는 언어 디자인 (상상적 일 수 있음)을 보여줄 수 있습니까? 코드가 더 명확하고 추측이 없기 때문에 어떤 메커니즘도 암시 적보다 낫다고 생각합니다.

암시 적 함수 (변환)가 아닌 매개 변수에 대해 질문하고 있습니다.

업데이트

전역 변수

모든 훌륭한 답변에 감사드립니다. 내 "전역 변수"이의 제기를 명확히 할 수 있습니다. 다음 기능을 고려하십시오.

max(x : Int,y : Int) : Int

당신은 그것을 부릅니다

max(5,6);

다음과 같이 (!) 할 수 있습니다.

max(x:5,y:6);

하지만 내 눈 implicits에는 다음과 같이 작동합니다.

x = 5;
y = 6;
max()

그러한 구조 (PHP 유사)와 크게 다르지 않습니다.

max() : Int
{
  global x : Int;
  global y : Int;
  ...
}

데릭의 대답

이것은 좋은 예이지만 사용하지 않고 메시지를 보내는 유연한 사용이라고 생각할 수 있다면 implicit반례를 게시하십시오. 나는 언어 디자인의 순수성에 대해 정말로 궁금합니다 ;-).


어떤 의미에서 암시는 글로벌 상태를 나타냅니다. 그러나 그것들은 가변적이지 않습니다. 이것이 전역 변수의 진정한 문제입니다. 사람들이 전역 상수에 대해 불평하는 것을 보지 않습니까? 실제로 코딩 표준은 일반적으로 코드의 모든 상수를 일반적으로 전역적인 상수 또는 열거 형으로 변환하도록 지시합니다.

암시 적은 플랫 네임 스페이스에 있지 않으며 전역의 일반적인 문제이기도합니다. 이들은 유형에 명시 적으로 연결되어 있으므로 해당 유형의 패키지 계층 구조에 연결됩니다.

따라서 전역을 가져 와서 변경 불가능하게 만들고 선언 사이트에서 초기화하고 네임 스페이스에 넣으십시오. 여전히 글로벌처럼 보입니까? 여전히 문제가있는 것처럼 보입니까?

그러나 여기서 멈추지 말자. 암시 적은 유형 묶여 있으며 유형만큼 "전역 적"입니다. 유형이 글로벌이라는 사실이 당신을 괴롭 히나요?

사용 사례는 많지만 그 역사를 바탕으로 간단한 검토를 할 수 있습니다. 원래, afaik, Scala에는 함축성이 없었습니다. 스칼라가 가지고있는 것은 뷰 타입이었는데, 다른 많은 언어들이 가지고있는 기능이었습니다. 우리는 오늘날에도 당신이 다음과 같은 것을 쓸 때마다 T <% Ordered[T]그 유형을 유형 T으로 볼 수 있다는 것을 여전히 볼 수 있습니다 Ordered[T]. 뷰 유형은 유형 매개 변수 (제네릭)에서 자동 캐스트를 사용할 수 있도록하는 방법입니다.

스칼라는 그 기능을 암시 적으로 일반화 했습니다. 자동 형변환은 더 이상 존재하지 않으며, 대신 값일 뿐이 므로 매개 변수로 전달 될 수있는 암시 적 변환Function1 이 있습니다. 그때부터는 T <% Ordered[T]암시 적 변환 값이 매개 변수로 전달됨을 의미합니다. 캐스트는 자동이므로 함수 호출자는 매개 변수를 명시 적으로 전달할 필요가 없습니다. 따라서 이러한 매개 변수는 암시 적 매개 변수가 됩니다 .

매우 유사하지만 완전히 겹치지는 않는 두 가지 개념 (암시 적 변환과 암시 적 매개 변수)이 있습니다.

어쨌든 뷰 유형은 암시 적으로 전달되는 암시 적 변환에 대한 구문 설탕이되었습니다. 다음과 같이 다시 작성됩니다.

def max[T <% Ordered[T]](a: T, b: T): T = if (a < b) b else a
def max[T](a: T, b: T)(implicit $ev1: Function1[T, Ordered[T]]): T = if ($ev1(a) < b) b else a

암시 적 매개 변수는 단순히 해당 패턴의 일반화이므로 . 대신 모든 종류의 암시 적 매개 변수 를 전달할 있습니다 Function1. 그런 다음 실제 사용이 뒤 따랐고 사용에 대한 구문 설탕이 나중에 나왔습니다.

그중 하나는 유형 클래스 패턴 을 구현하는 데 사용되는 Context Bounds 입니다 (패턴은 기본 제공 기능이 아니기 때문에 Haskell의 유형 클래스와 유사한 기능을 제공하는 언어를 사용하는 방법 일뿐입니다). 컨텍스트 바인딩은 클래스에 고유하지만 선언되지 않은 기능을 구현하는 어댑터를 제공하는 데 사용됩니다. 단점없이 상속 및 인터페이스의 이점을 제공합니다. 예를 들면 :

def max[T](a: T, b: T)(implicit $ev1: Ordering[T]): T = if ($ev1.lt(a, b)) b else a
// latter followed by the syntactic sugar
def max[T: Ordering](a: T, b: T): T = if (implicitly[Ordering[T]].lt(a, b)) b else a

이미 사용 하셨을 것입니다. 사람들이 일반적으로 알아 차리지 못하는 일반적인 사용 사례가 하나 있습니다. 이것은 다음과 같습니다.

new Array[Int](size)

클래스 매니페스트의 컨텍스트 바인딩을 사용하여 이러한 배열 초기화를 활성화합니다. 이 예를 통해 확인할 수 있습니다.

def f[T](size: Int) = new Array[T](size) // won't compile!

다음과 같이 작성할 수 있습니다.

def f[T: ClassManifest](size: Int) = new Array[T](size)

표준 라이브러리에서 가장 많이 사용되는 컨텍스트 경계는 다음과 같습니다.

Manifest      // Provides reflection on a type
ClassManifest // Provides reflection on a type after erasure
Ordering      // Total ordering of elements
Numeric       // Basic arithmetic of elements
CanBuildFrom  // Collection creation

후자의 세 가지는 대부분 컬렉션과 함께 사용되며 max, summap. 컨텍스트 경계를 광범위하게 사용하는 라이브러리 중 하나는 Scalaz입니다.

또 다른 일반적인 용도는 공통 매개 변수를 공유해야하는 작업에서 상용구를 줄이는 것입니다. 예를 들어, 거래 :

def withTransaction(f: Transaction => Unit) = {
  val txn = new Transaction

  try { f(txn); txn.commit() }
  catch { case ex => txn.rollback(); throw ex }
}

withTransaction { txn =>
  op1(data)(txn)
  op2(data)(txn)
  op3(data)(txn)
}

그러면 다음과 같이 단순화됩니다.

withTransaction { implicit txn =>
  op1(data)
  op2(data)
  op3(data)
}

이 패턴은 트랜잭션 메모리와 함께 사용되며 Scala I / O 라이브러리도이를 사용한다고 생각합니다 (잘 모르겠습니다).

제가 생각할 수있는 세 번째 일반적인 사용법은 전달되는 유형에 대한 증명을 만드는 것입니다. 이렇게하면 컴파일 타임에 감지 할 수 있습니다. 그렇지 않으면 런타임 예외가 발생합니다. 예를 들어 다음에서이 정의를 참조하십시오 Option.

def flatten[B](implicit ev: A <:< Option[B]): Option[B]

이것이 가능합니다.

scala> Option(Option(2)).flatten // compiles
res0: Option[Int] = Some(2)

scala> Option(2).flatten // does not compile!
<console>:8: error: Cannot prove that Int <:< Option[B].
              Option(2).flatten // does not compile!
                        ^

이 기능을 광범위하게 사용하는 라이브러리 중 하나는 Shapeless입니다.

Akka 라이브러리의 예가이 네 가지 범주 중 어느 하나에도 적합하지 않다고 생각하지만, 이것이 일반 기능의 요점입니다. 사람들은 언어 디자이너가 규정 한 방식 대신 모든 종류의 방식으로 사용할 수 있습니다.

당신이 처방받는 것을 좋아한다면 (예를 들어, 파이썬이 그렇듯이), Scala는 당신을위한 것이 아닙니다.


확실한. Akka는 액터와 관련하여 좋은 예를 가지고 있습니다. 액터의 receive메서드 안에있을 때 다른 액터에게 메시지를 보낼 수 있습니다. 이렇게하면 Akka는 (기본적으로) 현재 액터를 sender메시지와 같이 번들로 묶습니다 .

trait ScalaActorRef { this: ActorRef =>
  ...

  def !(message: Any)(implicit sender: ActorRef = null): Unit

  ...
}

sender암시이다. 액터에는 다음과 같은 정의가 있습니다.

trait Actor {
  ...

  implicit val self = context.self

  ...
}

이렇게하면 코드 범위 내에서 암시 적 값이 생성되고 다음과 같이 쉽게 할 수 있습니다.

someOtherActor ! SomeMessage

이제 원하는 경우이 작업도 수행 할 수 있습니다.

someOtherActor.!(SomeMessage)(self)

또는

someOtherActor.!(SomeMessage)(null)

또는

someOtherActor.!(SomeMessage)(anotherActorAltogether)

하지만 보통은 그렇지 않습니다. 액터 트레이 트의 암시 적 값 정의에 의해 가능해진 자연스러운 사용을 유지합니다. 약 백만 가지의 다른 예가 있습니다. 컬렉션 클래스는 엄청난 것입니다. 사소하지 않은 Scala 라이브러리를 둘러 보면 트럭을 찾을 수 있습니다.


한 가지 예는에 대한 비교 작업입니다 Traversable[A]. max또는 sort:

def max[B >: A](implicit cmp: Ordering[B]) : A

작업이있을 때이 단지 현명하게 정의 할 수 있습니다 <A. 따라서 암시 적없이이 Ordering[B]함수를 사용할 때마다 컨텍스트를 제공해야 합니다. (또는 내부 유형 정적 검사를 포기 max하고 런타임 캐스트 오류가 발생할 위험이 있습니다.)

그러나 암시 적 비교 유형 클래스 가 범위에있는 경우 (예 : some Ordering[Int]) 즉시 사용하거나 암시 적 매개 변수에 다른 값을 제공하여 비교 방법을 간단히 변경할 수 있습니다.

물론, 암시적인 내용이 숨겨 질 수 있으므로 범위 내에있는 실제 암시적인 내용이 충분히 명확하지 않은 상황이있을 수 있습니다. 간단한 용도를 위해 max또는 sort참으로 고정 된 순서가하기에 충분 수도 trait에를 Int하고이 특성을 사용할 수 있는지 여부를 확인하려면 몇 가지 구문을 사용합니다. 그러나 이것은 추가 특성이 없을 수 있고 모든 코드가 원래 정의 된 특성을 사용해야 함을 의미합니다.

추가 : 글로벌 변수 비교
에 대한 응답 .

나는 당신이 옳다고 생각합니다.

implicit val num = 2
implicit val item = "Orange"
def shopping(implicit num: Int, item: String) = {
  "I’m buying "+num+" "+item+(if(num==1) "." else "s.")
}

scala> shopping
res: java.lang.String = I’m buying 2 Oranges.

썩고 사악한 전역 변수 냄새가 날 수 있습니다. 그러나 중요한 점 은 범위의 유형 당 암시 적 변수가 하나만있을 수 있다는 것 입니다. 두 개의 Ints 를 사용한 예제 는 작동하지 않습니다.

Also, this means that practically, implicit variables are employed only when there is a not necessarily unique yet distinct primary instance for a type. The self reference of an actor is a good example for such a thing. The type class example is another example. There may be dozens of algebraic comparisons for any type but there is one which is special. (On another level, the actual line number in the code itself might also make for a good implicit variable as long as it uses a very distinctive type.)

You normally don’t use implicits for everyday types. And with specialised types (like Ordering[Int]) there is not too much risk in shadowing them.


Based on my experience there is no real good example for use of implicits parameters or implicits conversion.

The small benefit of using implicits (not needing to explicitly write a parameter or a type) is redundant in compare to the problems they create.

I am a developer for 15 years, and have been working with scala for the last 1.5 years.

I have seen many times bugs that were caused by the developer not aware of the fact that implicits are used, and that a specific function actually return a different type that the one specified. Due to implicit conversion.

I also heard statements saying that if you don't like implicits, don't use them. This is not practical in the real world since many times external libraries are used, and a lot of them are using implicits, so your code using implicits, and you might not be aware of that. You can write a code that has either:

import org.some.common.library.{TypeA, TypeB}

or:

import org.some.common.library._

Both codes will compile and run. But they will not always produce the same results since the second version imports implicits conversion that will make the code behave differently.

The 'bug' that is caused by this can occur a very long time after the code was written, in case some values that are affected by this conversion were not used originally.

Once you encounter the bug, its not an easy task finding the cause. You have to do some deep investigation.

Even though you feel like an expert in scala once you have found the bug, and fixed it by changing an import statement, you actually wasted a lot of precious time.

Additional reasons why I generally against implicits are:

  • They make the code hard to understand (there is less code, but you don't know what he is doing)
  • Compilation time. scala code compiles much slower when implicits are used.
  • In practice, it changes the language from statically typed, to dynamically typed. Its true that once following very strict coding guidelines you can avoid such situations, but in real world, its not always the case. Even using the IDE 'remove unused imports', can cause your code to still compile and run, but not the same as before you removed 'unused' imports.

There is no option to compile scala without implicits (if there is please correct me), and if there was an option, none of the common community scala libraries would have compile.

For all the above reasons, I think that implicits are one of the worst practices that scala language is using.

Scala has many great features, and many not so great.

When choosing a language for a new project, implicits are one of the reasons against scala, not in favour of it. In my opinion.


Another good general usage of implicit parameters is to make the return type of a method depend on the type of some of the parameters passed to it. A good example, mentioned by Jens, is the collections framework, and methods like map, whose full signature usually is:

def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[GenSeq[A], B, That]): That

Note that the return type That is determined by the best fitting CanBuildFrom that the compiler can find.

For another example of this, see that answer. There, the return type of the method Arithmetic.apply is determined according to a certain implicit parameter type (BiConverter).


It's easy, just remember:

  • to declare the variable to be passed in as implicit too
  • to declare all the implicit params after the non-implicit params in a separate ()

e.g.

def myFunction(): Int = {
  implicit val y: Int = 33
  implicit val z: Double = 3.3

  functionWithImplicit("foo") // calls functionWithImplicit("foo")(y, z)
}

def functionWithImplicit(foo: String)(implicit x: Int, d: Double) = // blar blar

Implicit parameters are heavily used in the collection API. Many functions get an implicit CanBuildFrom, which ensures that you get the 'best' result collection implementation.

Without implicits you would either pass such a thing all the time, which would make normal usage cumbersome. Or use less specialized collections which would be annoying because it would mean you loose performance/power.


I am commenting on this post a bit late, but I have started learning scala lately. Daniel and others have given nice background about implicit keyword. I would provide me two cents on implicit variable from practical usage perspective.

Scala is best suited if used for writing Apache Spark codes. In Spark, we do have spark context and most likely the configuration class that may fetch the configuration keys/values from a configuration file.

Now, If I have an abstract class and if I declare an object of configuration and spark context as follows :-

abstract class myImplicitClass {

implicit val config = new myConfigClass()

val conf = new SparkConf().setMaster().setAppName()
implicit val sc = new SparkContext(conf)

def overrideThisMethod(implicit sc: SparkContext, config: Config) : Unit
}

class MyClass extends myImplicitClass {

override def overrideThisMethod(implicit sc: SparkContext, config: Config){

/*I can provide here n number of methods where I can pass the sc and config 
objects, what are implicit*/
def firstFn(firstParam: Int) (implicit sc: SparkContext, config: Config){ 
    /*I can use "sc" and "config" as I wish: making rdd or getting data from cassandra, for e.g.*/
    val myRdd = sc.parallelize(List("abc","123"))
}
def secondFn(firstParam: Int) (implicit sc: SparkContext, config: Config){
 /*following are the ways we can use "sc" and "config" */

        val keyspace = config.getString("keyspace")
        val tableName = config.getString("table")
        val hostName = config.getString("host")
        val userName = config.getString("username")
        val pswd = config.getString("password")

    implicit val cassandraConnectorObj = CassandraConnector(....)
    val cassandraRdd = sc.cassandraTable(keyspace, tableName)
}

}
}

As we can see the code above, I have two implicit objects in my abstract class, and I have passed those two implicit variables as function/method/definition implicit parameters. I think this is the best use case that we can depict in terms of usage of implicit variables.

참고URL : https://stackoverflow.com/questions/9530893/good-example-of-implicit-parameter-in-scala

반응형