4

私のアプリでは、ユーザーが持っているクレジット数を追跡​​しています。型チェックを追加するために、次のCreditsようなクラスを使用しています。

case class Credits(val numCredits: Int) extends Ordered[Credits] {
   ...
}

def accept(creds: Credits): Unit呼び出したい関数があるとします。私がそれを呼び出す方法はありますか

process(Credits(100))
process(0)

しかし、これではありませんか?

process(10)

つまり、リテラルからのみ暗黙的な変換を提供し、0他には何も提供したくないのです。今のところ、私val Zero = Credits(0)はコンパニオンオブジェクトを持っているだけで、それはかなり良い習慣だと思いますが、とにかく、次のような他のコメントを含む答えに興味があります:

  • これは、2.10 のマクロの暗黙的な変換で行うことができますか?
  • CreditsAnyVal を拡張し、2.10 のケース クラスにすべきではありませんか?
4

3 に答える 3

10

この種のコンパイル時チェックは、2.10 で利用可能になるマクロを使用するのに適しています。

Jason Zauggという名前の非常に頭の良い人が、必要なものに似たものを既に実装していますが、これは正規表現に適用されます: Regex コンパイル時間チェック。

Macrocosm を調べて、それがどのように行われるか、また同じ目的で独自のマクロをどのようにコーディングできるかを確認することをお勧めします。

https://github.com/retronym/macrocosm

本当にマクロについてもっと知りたいのであれば、まず勇気が必要です。ドキュメントは今のところ不足しており、API は変更される可能性があるからです。Jason Zaugg は 2.10-M3 で問題なく動作しますが、新しいバージョンで動作するかどうかはわかりません。

いくつかの読み物から始めたい場合:

さて、トピックに取り掛かると、Scala マクロはCATです:「コンパイル時の AST 変換」。抽象構文ツリーは、コンパイラがソース コードを表現する方法です。コンパイラは結果として生じる変換を AST に適用し、最後のステップで実際に Java バイトコードを生成します。

それでは、Jason Zaugg のコードを見てみましょう。

 def regex(s: String): scala.util.matching.Regex = macro regexImpl

  def regexImpl(c: Context)(s: c.Expr[String]): c.Expr[scala.util.matching.Regex] = {
    import c.universe._

    s.tree match {
      case Literal(Constant(string: String)) =>
        string.r // just to check
        c.reify(s.splice.r)
    }
  }

ご覧のとおり、regex は、マクロ regexImpl を呼び出すことによって、文字列を受け取り、正規表現を返す特別な関数です。

マクロ関数は、最初のパラメーター リストでコンテキストを受け取り、2 番目の引数リストでマクロのパラメーターを c.Expr[A] の形式で受け取り、c.Expr[B] を返します。c.Expr はパスに依存する型であることに注意してください。つまり、Context 内で定義されたクラスであるため、2 つのコンテキストがある場合、次は無効になります。

val c1: context1.Expr[String] = ...
val c2: context2.Expr[String] = ...
val c3: context1.Expr[String] = context2.Expr[String] // illegal , compile error

コードで何が起こっているかを見ると、次のようになります。

  • s.tree に一致する一致ブロックがあります
  • s.tree が定数 String を含むリテラルの場合、 string.r が呼び出されます

ここで何が起こっているかというと、文字列から Predef.scala で定義された StringOps への暗黙的な変換があり、これはすべての scala ソースのコンパイルで自動的にインポートされます。

implicit def augmentString(x: String): StringOps = new StringOps(x)

StringOps は scala.collection.immutable.StringLike を拡張します。これには以下が含まれます。

def r: Regex = new Regex(toString)

マクロはコンパイル時に実行されるため、これはコンパイル時に実行され、例外がスローされるとコンパイルは失敗します (これは、無効な正規表現文字列から正規表現を作成する動作です)。


注: 残念ながら、API は非常に不安定です。http://scamacros.org/documentation/reference.html を見ると Context.scala への壊れたリンクが表示されます。正しいリンクは https://github.com/scala/scala/blob/2.10.x/src/reflect/scala/reflect/makro/Context.scalaです

于 2012-07-20T09:32:10.300 に答える
4

基本的に、依存型が必要です。Scala がパス依存型の限定された形式の依存型をサポートする理由は、あなたが求めることを実行できないからです。

Edmondoはマクロを提案する素晴らしいアイデアを持っていましたが、いくつかの制限があります。とても簡単だったので、私はそれを実装しました:

case class Credits(numCredits: Int)        
object Credits {
  implicit def toCredits(n: Int): Credits = macro toCreditsImpl

  import scala.reflect.makro.Context
  def toCreditsImpl(c: Context)(n: c.Expr[Int]): c.Expr[Credits] = {
    import c.universe._                                                                          

    n.tree match {                                                                               
      case arg @ Literal(Constant(0)) =>                                                         
        c.Expr(Apply(Select(Ident("Credits"), newTermName("apply")),           
          List(arg)))
      case _ => c.abort(c.enclosingPosition, "Expected Credits or 0")                            
    }                                                                                            
  }                                                                                              
}  

次に、REPL を起動し、 を定義acceptして、基本的なデモンストレーションを行いました。

scala> def accept(creds: Credits) { println(creds) }
accept: (creds: Credits)Unit

scala> accept(Credits(100))
Credits(100)

scala> accept(0)
Credits(0)

scala> accept(1)
<console>:9: error: Expected Credits or 0
              accept(1)
                     ^

さて、問題に:

scala> val x = 0
x: Int = 0

scala> accept(x)
<console>:10: error: Expected Credits or 0
              accept(x)
                     ^

つまり、識別子に割り当てられた値のプロパティを追跡することはできません。これは、従属型によって可能になることです。

しかし、全体が無駄に思えます。0だけを変換したいのはなぜですか?デフォルト値が必要なようです。その場合、最も簡単な解決策はデフォルト値を使用することです。

scala> def accept(creds: Credits = Credits(0)) { println(creds) }
accept: (creds: Credits)Unit

scala> accept(Credits(100))
Credits(100)

scala> accept()
Credits(0)
于 2012-07-20T14:09:35.683 に答える
1

Use は暗黙の部分関数を使用できます。

scala> case class Credits(val numCredits: Int)
defined class Credits

scala> def process(c: Credits) = {}
process: (c: Credits)Unit

scala> implicit def i2c:PartialFunction[Int, Credits] = { case 0 => Credits(0) }

i2c: PartialFunction[Int,Credits]

許可します

scala> process(Credits(12))

scala> process(0)

しかし:

scala> process(12)
scala.MatchError: 12 (of class java.lang.Integer)
        at $anonfun$i2c$1.apply(<console>:9)
        at $anonfun$i2c$1.apply(<console>:9)
        at .<init>(<console>:12)
        at .<clinit>(<console>)
        at .<init>(<console>:11)
        at .<clinit>(<console>)
        at $print(<console>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)

        at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.sca
la:920)
        at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:4
3)
        at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
        at java.lang.Thread.run(Unknown Source)

編集:しかし、はい、コンパイラはprocess(12)実行時に一致エラーが発生することを許可します。

于 2012-07-20T09:15:25.987 に答える