2016-01-16 58 views
2

我想在一組「條件動作」規則中使用Scala模式匹配的強大功能。這些規則並不是事先知道的,而是根據一些複雜的標準在運行時生成的。 algorithmic generation mechanism可以被認爲是完全獨立的,而不是這個問題的一部分,它涉及如何通過Scala反射/ quasiquotes來表達這個問題。Scala:動態生成案例類的匹配子句

具體而言,我期望在運行時生成案例定義(通用形式case [email protected](v1,_,v2): X => f(v1,v2))。

大概可以通過toolBox.parse(str)爲運行時生成的某個字符串執行此操作。然而,如果可能的話,似乎需要包含比這更高的類型安全度:

更具體地說,我希望案例defs匹配一個密封案例類層次結構的術語(Term,Var(name: Char),Lit(value:Int),Group(a: Term,b: Term,c: Term))。

例如,產生的情況下,高清會在一般情況下,返回沒有的一些功能,部分或全部V0,V1,V2的:

t match { 
    case [email protected]([email protected]_,[email protected]('a')) => Group(v2,v0,Group(v1,Var('z'),Lit(17))) // etc 
    } 

我試圖在quasiquotes的描述跟進對於案例defs給出here,但語法相當精神彎曲(和Scala 2.11拒絕向我展示類型),所以下面是我所知道的。我的具體問題嵌入代碼:

def dynamicMatch(condition: SomeType, action: SomeType, tb: ToolBox) 
(t: Term): Option[Term] = { 

    // Q1. What type should condition and action be for maximum 
    // typesafety in the calling code? Symbols? Quasiquotes? 
    // Would they best be combined into a single actual CaseDef? 

    // This is obviously a hardcoded placeholder expression, in general: 
    // Q2. How to bind in t, condition and action? 
    val q"$expr match { case ..$cases }" = 
    q"foo match { case _ : Term => Some(expr) case _ => None }" 

    val cq"$pat1 => $body1" :: cq"$pat2 => $body2" :: Nil = cases 

    // Q3. how should this be invoked to return the desired result? 
    ??? 
} 
+0

你想用這個做什麼?這一切都發生在編譯時,而不是運行時。 –

+0

你能解釋一下爲什麼你需要這個?我想知道對象的運行時反射可能比使用宏來生成匹配語句更好嗎?如果你希望在這裏找到答案,我認爲你需要使這個問題更容易獲得。 – Rich

+0

@ m-z在運行時可以生成CaseDefs,但是對嗎?據推測,人們可以簡單地使用toolBox.parse(「case X => Y」)作爲可以在運行時生成的一些字符串,我所要求的部分是可以用來代替String的最強類型。 – NietzscheanAI

回答

1

有一個shapeless example構建函數調用,目的是讓工具箱根據運行時類型選擇一個類型類。

您也想動態構建邏輯,但是您在quasiquoting時遇到困難。

此做一些事情:

// Some classes 
sealed trait Term 
case class Lit(value: Int) extends Term 
case class Group(a: Term, b: Term) extends Term 

// Build a function that hooks up patterns to other expressions 
    def stagedTermFunction(t: Term): Option[Term] = { 
    // add lits 
    val tt = tq"_root_.shapeless.examples.Term" 
    val lit = q"_root_.shapeless.examples.Lit" 
    val grp = q"_root_.shapeless.examples.Group" 
    val pat = pq"$grp($lit(x), $lit(y))" 
    val add = cq"$pat => Some($lit(x + y))" 
    val default = cq"_ => None" 
    val cases = add :: default :: Nil 
    val res = q"{ case ..$cases } : (($tt) => Option[$tt])" 
    val f = evalTree[Term => Option[Term]](res) 
    f(t) 
    } 

那麼這不炸掉:

val t3: Term = Group(Lit(17), Lit(42)) 
    val Some(Lit(59)) = stagedTermFunction(t3) 
    assert(stagedTermFunction(Lit(0)) == None) 

如果你想操縱你可能要sym.fullName轉換爲Select樹通過q"name"建成;我認爲在某處有一種實用方法。

0

這真的不覺得應該用宏來完成。正如其他答案所指出的,宏應該用於編譯時安全。這樣做沒有明顯的好處,下面寫的一般定義沒有提供。

case class GenericClass(`type`: String, args: List[Any]) 

val _actions = Map("1" ->() => println("hello"), "2" ->() => println("goodbye")) 

def dynamic(gen: GenericClass) match { 
    case GenericClass(n, _) => _actions.get(n).map(_.apply()) 
} 

您可在運行過程中的情況下創建類,但是這是不一樣,創建CaseDef這是一個簡單的AST樹。您基本上必須通過在代碼之外提供的類/方法的步驟。

  1. 創建AST樹
  2. 獲取Scala編譯器使用自己的類加載器或某種反射來加載字節碼編譯成Java的
  3. 裝載Java類。

不僅如此,但無處不在,你使用這些新生成的類將不得不使用反射來實例化,除非你生成的在同一時間產生的類型調用的方法

正如你可能告訴這很難。 Java和scala都是編譯語言,不像python或javascript被解釋。將類加載到運行時不是標準或推薦的。

編輯 - 澄清

值得澄清的問題是,更多的是與如何創建一個局部功能安全,而不是動態生成的代碼作爲第一似乎後。

首先讓我們的場景中,你基本上是希望爲n個不同的類,你不知道在運行時(大概是因爲一些算法的輸出的一部分),在下面的語句中的行爲,

case [email protected](v1,_,v2): X => f(v1,v2))

在我們繼續之前,值得討論它實際編譯的內容。用於代碼塊,

val f: PartialFunction[String, Int] = { 
    case "foo" => 1 
} 
特別

,階基本上這種形式的case語句轉換成PartialFunction,即對於用於輸入的某些值定義值的函數。 如果未定義點,它將返回返回類型Option。這種類型的關鍵方法是isDefined

這加寬型時Any和類匹配確實有效,

val f: PartialFunction[Any, Int] = { 
    case _: String => 1 
    case _: Int => 2 
} 

那麼這與你的問題?那麼,PartialFunction的另一個有趣的方法是orElse方法。它所做的是檢查是否爲特定點定義了部分函數,​​如果不是,將會嘗試評估第二個PartialFunction

case class Foo(s: String) 
case class Bar(i: Int) 

val f1: PartialFunction[Any, String] = { 
    case Foo(s) => s 
} 

val f2: PartialFunction[Any, String] = { 
    case Bar(i) => i.toString 
} 

//our composite! 
val g = f1 orElse f2 

在上述g的例子中,如果輸入是任一的FooBar將僅評估。如果它不是,它將安全地返回None,並且該函數的行爲在運行時發生改變。

+0

感謝你的回答,但你的回答表明我應該進一步澄清問題。雖然case * class *定義在編譯時是固定的:它是使用它們的*表達式*(例如Group(Var('z'),...)和我想要生成的相應匹配語句)運行。 AFAICS,這絕對可以使用Scala反射(並且不需要重新加載字節碼):問題實際上是如何以最安全的方式實現這一點。 – NietzscheanAI