스칼라의 람다 유형은 무엇이며 장점은 무엇입니까?


152

언젠가 나는 반 신비한 표기법으로 넘어졌다.

def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..} 

Scala 블로그 게시물에서 "우리는 그 유형 람다 트릭을 사용했습니다"핸드 웨이브를 제공합니다.

이것에 대해 약간의 직감을 가지고 있지만 (우리 A는 정의를 오염시키지 않고 익명의 유형 매개 변수 를 얻 습니까?) 유형 람다 트릭이 무엇인지, 그 이점이 무엇인지 설명하는 명확한 출처를 찾지 못했습니다. 그것은 단지 구문 설탕입니까, 아니면 새로운 차원을 열어 줍니까?


답변:


148

유형 람다는 종류가 높은 유형으로 작업 할 때 매우 중요합니다.

Either [A, B]의 올바른 투영을 위해 모나드를 정의하는 간단한 예를 고려하십시오. 모나드 타입 클래스는 다음과 같습니다 :

trait Monad[M[_]] {
  def point[A](a: A): M[A]
  def bind[A, B](m: M[A])(f: A => M[B]): M[B]
}

이제 두 인수의 형식 생성자이지만 Monad를 구현하려면 하나의 인수로 형식 생성자를 제공해야합니다. 이에 대한 해결책은 람다 유형을 사용하는 것입니다.

class EitherMonad[A] extends Monad[({type λ[α] = Either[A, α]})#λ] {
  def point[B](b: B): Either[A, B]
  def bind[B, C](m: Either[A, B])(f: B => Either[A, C]): Either[A, C]
}

이것은 유형 시스템에서 카레의 예입니다. EitherMonad의 인스턴스를 작성하려고 할 때 유형 중 하나를 지정하도록 Either의 유형을 카레했습니다. 물론 다른 지점은 호출 지점 또는 바인딩시 제공됩니다.

유형 람다 트릭은 유형 위치의 빈 블록이 익명 구조 유형을 생성한다는 사실을 이용합니다. 그런 다음 # 구문을 사용하여 형식 멤버를 가져옵니다.

경우에 따라 인라인을 작성하는 데 어려움이있는보다 복잡한 유형의 람다가 필요할 수 있습니다. 오늘 내 코드의 예는 다음과 같습니다.

// types X and E are defined in an enclosing scope
private[iteratee] class FG[F[_[_], _], G[_]] {
  type FGA[A] = F[G, A]
  type IterateeM[A] = IterateeT[X, E, FGA, A] 
}

이 클래스는 독점적으로 존재하므로 FG [F, G] #IterateeM과 같은 이름을 사용하여 일부 세 번째 모나드에 특화된 두 번째 모나드의 트랜스포머 버전에 특화된 IterateeT 모나드의 유형을 참조 할 수 있습니다. 쌓기 시작할 때 이러한 종류의 구성이 매우 필요합니다. 물론 FG를 인스턴스화하지는 않습니다. 타입 시스템에서 내가 원하는 것을 표현할 수있는 해킹이 있습니다.


3
것을주의하는 것이 재미 하스켈은 않습니다 하지 직접 타입 수준의 람다를 지원하는 일부 newtype이란 해커 (예 TypeCompose 라이브러리) 그 극복의 종류에 방법을 가지고 있지만,.
Dan Burton

1
클래스에 대한 bind메소드를 정의하는 것이 궁금합니다 EitherMonad. :-) 그 외에도, 아드리아 안을 잠시 여기로 보내면, 당신은 그 예에서 더 높은 종류의 유형을 사용하지 않습니다. 당신은 안에 FG있지만 안에는 없습니다 EitherMonad. 오히려 kind 생성자 를 사용하고 있습니다 * => *. 이 종류는 순서 1이며 "높은"것은 아닙니다.
Daniel Spiewak

2
나는 종류 *가 순서 1 이라고 생각 했지만 어쨌든 Monad는 종류가 (* => *) => *있습니다. 또한 "올바른 프로젝션"을 지정했음을 알 수 있습니다 Either[A, B]. 구현은 쉽지 않습니다 (하지만 이전에하지 않은 경우 좋은 연습입니다!)
Kris Nuttycombe

나는 Daniel이 *=>*더 높은 것을 부르지 않았다는 점 은 우리가 일반 함수 (비 함수를 비 함수, 즉 일반 값을 일반 값에 매핑하는) 고차 함수를 호출하지 않는다는 비유로 정당화된다고 생각합니다.
jhegedus

1
피어스의 TAPL 책, 페이지 442 :Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
jhegedus

52

이점은 익명 기능으로 부여 된 이점과 동일합니다.

def inc(a: Int) = a + 1; List(1, 2, 3).map(inc)

List(1, 2, 3).map(a => a + 1)

Scalaz 7의 사용 예 입니다. Functor의 두 번째 요소에 함수를 매핑 할 수 있는를 사용하려고 합니다 Tuple2.

type IntTuple[+A]=(Int, A)
Functor[IntTuple].map((1, 2))(a => a + 1)) // (1, 3)

Functor[({type l[a] = (Int, a)})#l].map((1, 2))(a => a + 1)) // (1, 3)

Scalaz는에 대한 유형 인수를 유추 할 수있는 암시 적 변환을 제공 Functor하므로 종종 이러한 인수를 작성하지 마십시오. 이전 줄은 다음과 같이 다시 쓸 수 있습니다.

(1, 2).map(a => a + 1) // (1, 3)

IntelliJ를 사용하는 경우 설정, 코드 스타일, 스칼라, 접기, 유형 람다를 활성화 할 수 있습니다. 그러면 구문의 멍청한 부분숨겨 지고 더 맛있어집니다.

Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)

스칼라의 향후 버전은 그러한 구문을 직접 지원할 수 있습니다.


마지막 스 니펫은 정말 멋져 보입니다. IntelliJ scala 플러그인은 반드시 훌륭합니다!
AndreasScheinert

1
감사! 마지막 예에서 람다가 누락되었을 수 있습니다. 튜플 펑 터는 왜 마지막 값을 바꾸려고합니까? 컨벤션 / 실제 기본값입니까?
ron

1
Nika에서 야간을 실행 중이며 설명 된 IDEA 옵션이 없습니다. 흥미롭게도,이 에 대한 검사 "응용 형 람다가 단순화 될 수있다."
랜달 슐츠

6
설정-> 편집기-> 코드 접기로 이동했습니다.
retronym

@retronym, (1, 2).map(a => a + 1)REPL을 시도 할 때 오류가 발생했습니다.` <console> : 11 : error : value map이 (Int, Int) (1, 2)의 멤버가 아닙니다 .map (a => a + 1) ^`
케빈 메러디스

41

상황에 맞게 :이 답변은 원래 다른 스레드에 게시되었습니다. 두 개의 스레드가 병합되었으므로 여기에서 볼 수 있습니다. 해당 스레드의 질문 내용은 다음과 같습니다.

이 유형 정의를 해결하는 방법 : Pure [({type? [a] = (R, a)}) #?]?

이러한 구성을 사용하는 이유는 무엇입니까?

Snack은 scalaz 라이브러리에서 제공됩니다.

trait Pure[P[_]] {
  def pure[A](a: => A): P[A]
}

object Pure {
  import Scalaz._
//...
  implicit def Tuple2Pure[R: Zero]: Pure[({type ?[a]=(R, a)})#?] = new Pure[({type ?[a]=(R, a)})#?] {
  def pure[A](a: => A) = (Ø, a)
  }

//...
}

대답:

trait Pure[P[_]] {
  def pure[A](a: => A): P[A]
}

상자의 밑줄은 P형식 생성자가 한 형식을 취하고 다른 형식을 반환한다는 것을 나타냅니다. 이 종류의 생성자의 예 : List, Option.

부여 ListInt, 구체적인 유형을, 그리고 당신을 제공 List[Int], 다른 구체적인 유형입니다. 부여 ListString그리고 당신을 제공합니다 List[String]. 기타.

따라서 List, Option1. 공식적으로 우리가 말할 인수에 대응의 타입 수준의 기능으로 간주 될 수있다, 그들은 종류가 * -> *. 별표는 유형을 나타냅니다.

이제 Tuple2[_, _]종류가있는 형식 생성자입니다. (*, *) -> *즉, 새 형식을 얻으려면 두 가지 형식을 지정해야합니다.

자신의 서명이 일치하지 않기 때문에, 당신은 대체 할 수 있습니다 Tuple2에 대한 P. 당신이해야 할 일은 인수 중 하나에 부분적으로 적용 Tuple2 하는 것입니다. 이는 우리에게 kind를 가진 타입 생성자를 줄 것이고 * -> *, 우리는 그것을 대신 할 수 있습니다 P.

불행히도 스칼라는 형식 생성자의 부분적 적용을위한 특별한 구문을 가지고 있지 않으므로 람다 형식이라는 괴물에 의존해야합니다. (당신의 예에있는 것) 그것들은 그것들이 가치 수준에 존재하는 람다 식과 유사하기 때문에 호출됩니다.

다음 예제가 도움이 될 수 있습니다.

// VALUE LEVEL

// foo has signature: (String, String) => String
scala> def foo(x: String, y: String): String = x + " " + y
foo: (x: String, y: String)String

// world wants a parameter of type String => String    
scala> def world(f: String => String): String = f("world")
world: (f: String => String)String

// So we use a lambda expression that partially applies foo on one parameter
// to yield a value of type String => String
scala> world(x => foo("hello", x))
res0: String = hello world


// TYPE LEVEL

// Foo has a kind (*, *) -> *
scala> type Foo[A, B] = Map[A, B]
defined type alias Foo

// World wants a parameter of kind * -> *
scala> type World[M[_]] = M[Int]
defined type alias World

// So we use a lambda lambda that partially applies Foo on one parameter
// to yield a type of kind * -> *
scala> type X[A] = World[({ type M[A] = Foo[String, A] })#M]
defined type alias X

// Test the equality of two types. (If this compiles, it means they're equal.)
scala> implicitly[X[Int] =:= Foo[String, Int]]
res2: =:=[X[Int],Foo[String,Int]] = <function1>

편집하다:

더 많은 가치 수준 및 유형 수준 병렬.

// VALUE LEVEL

// Instead of a lambda, you can define a named function beforehand...
scala> val g: String => String = x => foo("hello", x)
g: String => String = <function1>

// ...and use it.
scala> world(g)
res3: String = hello world

// TYPE LEVEL

// Same applies at type level too.
scala> type G[A] = Foo[String, A]
defined type alias G

scala> implicitly[X =:= Foo[String, Int]]
res5: =:=[X,Foo[String,Int]] = <function1>

scala> type T = World[G]
defined type alias T

scala> implicitly[T =:= Foo[String, Int]]
res6: =:=[T,Foo[String,Int]] = <function1>

제시 한 경우, type 매개 변수 R는 작동하기에 로컬 Tuple2Pure이므로 type PartialTuple2[A] = Tuple2[R, A]해당 동의어를 넣을 장소가 없기 때문에 간단히 정의 할 수 없습니다 .

이러한 경우를 처리하기 위해 형식 멤버를 사용하는 다음 트릭을 사용합니다. (이 예제는 자명하다.)

scala> type Partial2[F[_, _], A] = {
     |   type Get[B] = F[A, B]
     | }
defined type alias Partial2

scala> implicit def Tuple2Pure[R]: Pure[Partial2[Tuple2, R]#Get] = sys.error("")
Tuple2Pure: [R]=> Pure[[B](R, B)]

0

type World[M[_]] = M[Int]우리에 넣어 무엇이든 그 원인 AX[A](가) implicitly[X[A] =:= Foo[String,Int]]항상 true I 추측이다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.