1

私はいくつかのアプリケーション用の小さな登録モジュールを書いており、これを型クラスで行っています。主なロジックはスプレー ルーティングに基づいています。

val api = post {
    path("register" / Rest) { rest =>
      rest match {
        case "user" => register[User]
        // ... other cases
      }
    }
  }
}

簡単にするために、ユーザーには次のデータ型があります。

case class User(id: String, email: String)

POST リクエストからエンティティを抽出するには、typeclass を使用しています:

trait EntityExtractor[T] {
  def extractFromRequest: Directive[T :: String :: HNil]
}

ユーザーの実装:

object User {
  implicit val extractor = new EntityExtractor[User] {
    def extractFromRequest: Directive[User :: String :: HNil] =
      formFields('email, 'pass).hmap {
        case email :: pass :: HNil =>
          val id = UID.genId
          User(id, email) :: pass :: HNil
      }
  }
}

typeclass ディレクティブを使用する register メソッドで問題が発生します。

def register[T] =
  extractEntity[T].apply { entity => // fails here
    validateEntity(entity) {
      completeRegistration(entity)
    }
  }
}

def extractEntity[T: EntityExtractor]: Directive[Entity[T] :: HNil] = {
  implicitly[EntityExtractor[T]].extractFromRequest.hmap {
    case entity :: pass :: HNil => Entity(entity, pass) :: HNil
  }
}

そして、例外で失敗します: could not find implicit value for evidence parameter of type EntityExtractor[T].

パターン マッチングなしでリフレクション (TypeTags または Manifest) でこれを修正する方法はありますか?

4

1 に答える 1

0

次のやや不自然な例が全体の問題を少し明確にすることを願っています.

次のコードを検討してください (現在のコードに対応しています):

object Main {
  def main(args: Array[String]) {
    implicit val intIntable = new Intable[Int] {
      def toInt(obj: Int) = obj
    }

    println(squareAndDouble(10: Int))
  }

  // Corresponds to your register[T] function
  def squareAndDouble[T](obj: T) = {
    val d = double[T](obj)
    d*d
  }

  // Corresponds to your extractEntity[T] function
  def double[T: Intable](obj: T) = {
    implicitly[Intable[T]].toInt(obj)*2
  }
}

trait Intable[T] {
  def toInt(obj: T): Int
}

このコードは、次のエラーでコンパイルされません。

test.scala:11: error: could not find implicit value for evidence parameter of type Intable[T]
    val d = double[T](obj)

それでは、暗黙のパラメーターを使用してそれを書き直しましょう (これは、コンテキストがバインドされていることを確認したときにコンパイラーが行うことです)。

object Main {
  def main(args: Array[String]) {
    implicit val intIntable = new Intable[Int] {
      def toInt(obj: Int) = obj
    }

    println(squareAndDouble(10: Int))
  }

  def squareAndDouble[T](obj: T) = {  
    val d = double[T](obj)
    d*d
  }


  def double[T](obj: T)(implicit intable: Intable[T]) = {
    intable.toInt(obj)*2
  }
}

trait Intable[T] {
  def toInt(obj: T): Int
}

このプログラムも同様にコンパイルされません:

test.scala:11: error: could not find implicit value for parameter intable: Intable[T]
    val d = double[T](obj)

これで、コンパイルできない理由がより明確になるはずです。関数に必要な関数T内の型パラメーターの暗黙的な値 (「証拠」) がないため、単純に失敗します。関数はその型パラメーター (何でもかまいません) の証拠を必要とするため、この証拠がスコープ内に表示される唯一の方法は、暗黙のパラメーターからのものですが、現在は関数内にあります。squareAndDoublesquaredoublesquareAndDoublesquareAndDouble

基本的に、これは、型クラスを使用するために、ジェネリック パラメーターの証拠を「渡す」必要があることを意味します。

修正しましょう:

object Main {
  def main(args: Array[String]) {
    implicit val intIntable = new Intable[Int] {
      def toInt(obj: Int) = obj
    }

    println(squareAndDouble(10: Int))
  }

  // Added context bound here
  def squareAndDouble[T: Intable](obj: T) = {
    val d = double[T](obj)
    d*d
  }

  def double[T: Intable](obj: T) = {
    implicitly[Intable[T]].toInt(obj)*2
  }
}

trait Intable[T] {
  def toInt(obj: T): Int
}

これで、正常にコンパイルおよび実行されます。

% scalac test.scala
% scala Main
400

問題を理解する別の (より単純な) 方法は次のとおりです。私たちのsquareAndDouble[T]関数は無制限の型パラメータを受け入れますが、型パラメータが制限Tされている別の関数を呼び出そうとします。double[T: Intable]これは許可されていません: 正当な場合、型クラスのインスタンスsquareAndDouble[T]を持たない型であっても、任意の型で呼び出すことができ、結果として関数が壊れます。したがって、関数をコンパイルするには、関数にも境界を追加する必要があります: すると、完全に機能します。 これは実際、暗黙的な値の代わりにクラス継承で機能する上限/下限と非常によく似ています。IntabledoubleIntablesquareAndDoublesquareAndDouble[T: Intable]

于 2013-08-02T19:45:13.393 に答える