2011-03-18 102 views
3

Scala的新手,但在C++中經驗我試圖在sqlite4java庫的頂部實現(可能是誤導性的)一個小庫,以允許我自動填充查詢行中的abritrary類型的元組列類型每個都與各自的元組元素類型兼容)。斯卡拉元組的問題

在C++中,我通常使用boost :: tuples和編譯時模板遞歸來實現此功能(使用模板專用化終止,如下所示)。 Boost元組的實現與Haskell HList非常相似。該模式將是(假設爲簡單起見,該查詢返回的字符串的向量):

template<typename T1, typename T2> 
void populateTuple(boost::tuples::cons<T1, T2>& tupleRec, int index, const std::vector<std::string>& vals) 
{ 
    tupleRec.head = boost::lexical_cast<T1>(vals[index]); 
    populateTuple(tupleRec.tail, index+1, vals); 
} 

template<typename T> 
void populateTuple(boost::tuples::cons<T, boost::tuples::null_type>& tupleRec, int index, const std::vector<std::string>& vals) 
{ 
    tupleRec.head = boost::lexical_cast<T>(vals[index]); 
} 

(道歉 - 我還沒有通過編譯運行上面的,但我希望它表明我的意思)

我很想能夠用Scala做類似的事情。我可以通過產品特徵重新生成一個通用的Tuple類型 - 並在運行時使用模式匹配(針對我支持的少量列類型)獲取每個元素的類型。但是我沒有找到通過產品特性分配元組元素的方法。說實話,我不相信這是一種特別好的或慣用的方式來做我想要的東西。

但這樣的:

val returnedFromQuery = List[String]("Hello", "4", "6.0") 
val rowAsTuples = interpretListAsTuple[(String, Int, Float)](returnedFromQuery) 

凡rowAsTuples一直類型(字符串,整型,浮點)。再次請原諒任何語法錯誤。

任何人有任何想法?還是有其他建議?事先 - 我對任何更高級別的SQL查詢庫都不感興趣。我對sqlite4java感到滿意,但想用簡單的更抽象的接口來包裝它。

回答

5

我想你應該嘗試使用模式匹配而不是解釋。首先,我們使用unapply需要的東西,將字符串拔出我們的類型:

object Stringy { 
    def unapply(s: String) = Some(s) 
} 
object Inty { 
    def unapply(s: String) = { 
    try { Some(s.toInt) } 
    catch { case nfe: NumberFormatException => None } 
    } 
} 
object Floaty { 
    def unapply(s: String) = { 
    try { Some(s.toFloat) } 
    catch { case nfe: NumberFormatException => None } 
    } 
} 

現在我們可以在模式中使用它們匹配:

scala> List("Hello","4","6.0") match { 
    case Stringy(s) :: Inty(i) :: Floaty(f) :: Nil => Some((s,i,f)) 
    case _ => None 
} 
res3: Option[(String, Int, Float)] = Some((Hello,4,6.0)) 

需要注意的是,如果你做這種方式,你( 1)如果你不想要返回元組, (2)可以立即訪問所有解析出來的變量; (3)內置自動錯誤檢查(帶有選項)。

+0

比我的答案更詳細,豎起大拇指! – 2011-03-18 23:43:32

+0

這是一個很好的答案。可能不像我期望的那麼簡潔,但也許爲了獲得完整的效果,我希望我需要HList。將考慮兩個。謝謝! – 2011-03-19 08:26:01

1

嘗試使用MetaScala庫中的HList。

+0

謝謝。我最終可能會。但是如果可能的話,我想知道它是否適用於標準Tuple類型。 – 2011-03-18 22:40:22

1

我覺得你有與動態元組元數的問題,所以你必須落實每個元組元數的方法,這樣的事情:

def interpretListAsTuple2[A,B](s: List[String])(implicit s2a: String => A, s2b: String => B) = { 
    s.grouped(2).map { case x :: y => (x: A, y.head: B) } 
} 

def interpretListAsTuple3[A,B,C](s: List[String])(implicit s2a: String => A, s2b: String => B, s2c: String => C) = { 
    s.grouped(3).map { case x :: y :: z => (x: A, y: B, z.head: C) } 
} 

implicit def string2String(s: String) = s 
implicit def string2Int (s: String) = s.toInt 
implicit def string2Float (s: String) = s.toFloat 

val returnedFromQuery = List("Hello", "4", "6.0") 

interpretListAsTuple3[String,Int,Float](returnedFromQuery) 

對不起,此代碼doesn't工作,因爲模糊Stringfloat的隱式轉換分別在scala的PredefLowPriorityImplicits中。也許有人可以幫助和解決這個問題。但主要的想法應該清楚,但。您只需爲數據類型定義一次隱式轉換,然後使用所有元組。

編輯:

可以使用上述版本的地圖數元組的字符串列表。 List("Hello", "4", "6.0","Hey","1", "2.3")如果你想只處理一個元組,然後用這個:

def interpretListAsTuple3[A,B,C](s: List[String])(implicit s2a: String => A, s2b: String => B, s2c: String => C): (A,B,C) = { 
    s.grouped(3).map { case x :: y :: z => (x: A, y: B, z.head: C) }.next 
} 

當然,參數必須很好地形成。

0

AFAIK,你不能繞過類型參數arity。對於這方面的證據,以事實功能和元組有一個定義每個參數數量,高達22

任意元數如果類型聲明,這是什麼HList現有實現做,那麼你可以做點事情。

+0

MetaScala帶有一些關於穩定性的健康警告。據你所知,HList是否合理成熟?或者我應該推出自己的? – 2011-03-19 08:24:28

+0

@Alex我從未使用過metascala。我從某些博客上的實現中知道「HList」。 – 2011-03-20 00:13:57

0

所以我問這個問題,因爲我想寫一個簡單的包裝sqlite4java sqlite接口。爲了使下面的形式,其中從查詢行類型可以在事先準備好的聲明中指定的代碼(我打算基於類似的方法類型檢查添加到傳遞的參數):

test("SQLite wrapper test") 
{ 
    val db = new SQLiteWrapper() 
    db.exec("BEGIN") 
    db.exec("CREATE TABLE test(number INTEGER, value FLOAT, name TEXT)") 

    val insStatement = db.prepare("INSERT INTO test VALUES(?, ?, ?)", HNil) 
    insStatement.exec(1, 5.0, "Hello1") 
    insStatement.exec(2, 6.0, "Hello2") 
    insStatement.exec(3, 7.0, "Hello3") 
    insStatement.exec(4, 8.0, "Hello4") 

    val getStatement = db.prepare("SELECT * from test", Col[Int]::Col[Double]::Col[String]::HNil) 
    assert(getStatement.step() === true) 
    assert(_1(getStatement.row) === Some(1)) 
    assert(_2(getStatement.row) === Some(5.0)) 
    assert(_3(getStatement.row) === Some("Hello1")) 

    getStatement.reset() 

    db.exec("ROLLBACK") 
} 

,並啓用該,使用各種有用的建議,我已經拿出了下面的代碼。這是我在Scala的任何形式的通用編程方面的第一次嘗試 - 我只用了一兩個星期的時間。所以這個代碼不太可能被有經驗的Scala社區認爲是好的/好的。任何建議/反饋歡迎....

import java.io.File 
import com.almworks.sqlite4java._ 

object SqliteWrapper 
{ 
    trait TypedCol[T] 
    { 
     var v : Option[T] = None 
     def assign(res : SQLiteStatement, index : Int) 
    } 

    sealed trait HList 
    { 
     def assign(res : SQLiteStatement, index : Int) 
    } 

    final case class HCons[H <: TypedCol[_], T <: HList](var head : H, tail : T) extends HList 
    { 
     def ::[T <: TypedCol[_]](v : T) = HCons(v, this) 
     def assign(res : SQLiteStatement, index : Int) 
     { 
      head.assign(res, index) 
      tail.assign(res, index+1) 
     } 
    } 

    final class HNil extends HList 
    { 
     def ::[T <: TypedCol[_]](v : T) = HCons(v, this) 
     def assign(res : SQLiteStatement, index : Int) 
     { 
     } 
    } 

    type ::[H <: TypedCol[_], T <: HList] = HCons[H, T] 

    val HNil = new HNil() 



    final class IntCol extends TypedCol[Int] 
    { 
     def assign(res : SQLiteStatement, index : Int) { v = Some(res.columnInt(index)) } 
    } 

    final class DoubleCol extends TypedCol[Double] 
    { 
     def assign(res : SQLiteStatement, index : Int) { v = Some(res.columnDouble(index)) } 
    } 

    final class StringCol extends TypedCol[String] 
    { 
     def assign(res : SQLiteStatement, index : Int) { v = Some(res.columnString(index)) } 
    } 

    trait TypedColMaker[T] 
    { 
     def build() : TypedCol[T] 
    } 

    object TypedColMaker 
    { 
     implicit object IntColMaker extends TypedColMaker[Int] 
     { 
      def build() : TypedCol[Int] = new IntCol() 
     } 
     implicit object DoubleColMaker extends TypedColMaker[Double] 
     { 
      def build() : TypedCol[Double] = new DoubleCol() 
     } 
     implicit object StringColMaker extends TypedColMaker[String] 
     { 
      def build() : TypedCol[String] = new StringCol() 
     } 
    } 

    def Col[T : TypedColMaker]() = implicitly[TypedColMaker[T]].build() 

    // Hideousness. Improve as Scala metaprogramming ability improves 
    def _1[H <: TypedCol[_], T <: HList](t : HCons[H, T]) = t.head.v 
    def _2[H1 <: TypedCol[_], H2 <: TypedCol[_], T <: HList](t : HCons[H1, HCons[H2, T]]) = t.tail.head.v 
    def _3[H1 <: TypedCol[_], H2 <: TypedCol[_], H3 <: TypedCol[_], T <: HList](t : HCons[H1, HCons[H2, HCons[H3, T]]]) = t.tail.tail.head.v 
    def _4[H1 <: TypedCol[_], H2 <: TypedCol[_], H3 <: TypedCol[_], H4 <: TypedCol[_], T <: HList](t : HCons[H1, HCons[H2, HCons[H3, HCons[H4, T]]]]) = t.tail.tail.tail.head.v 
    def _5[H1 <: TypedCol[_], H2 <: TypedCol[_], H3 <: TypedCol[_], H4 <: TypedCol[_], H5 <: TypedCol[_], T <: HList](t : HCons[H1, HCons[H2, HCons[H3, HCons[H4, HCons[H5, T]]]]]) = t.tail.tail.tail.tail.head.v 
    def _6[H1 <: TypedCol[_], H2 <: TypedCol[_], H3 <: TypedCol[_], H4 <: TypedCol[_], H5 <: TypedCol[_], H6 <: TypedCol[_], T <: HList](t : HCons[H1, HCons[H2, HCons[H3, HCons[H4, HCons[H5, HCons[H6, T]]]]]]) = t.tail.tail.tail.tail.tail.head.v 
    def _7[H1 <: TypedCol[_], H2 <: TypedCol[_], H3 <: TypedCol[_], H4 <: TypedCol[_], H5 <: TypedCol[_], H6 <: TypedCol[_], H7 <: TypedCol[_], T <: HList](t : HCons[H1, HCons[H2, HCons[H3, HCons[H4, HCons[H5, HCons[H6, HCons[H7, T]]]]]]]) = t.tail.tail.tail.tail.tail.tail.head.v 
    def _8[H1 <: TypedCol[_], H2 <: TypedCol[_], H3 <: TypedCol[_], H4 <: TypedCol[_], H5 <: TypedCol[_], H6 <: TypedCol[_], H7 <: TypedCol[_], H8 <: TypedCol[_], T <: HList](t : HCons[H1, HCons[H2, HCons[H3, HCons[H4, HCons[H5, HCons[H6, HCons[H7, HCons[H8, T]]]]]]]]) = t.tail.tail.tail.tail.tail.tail.tail.head.v 

    final class DataWrapper[T <: HList](var row : T) 
    { 
     def assign(res : SQLiteStatement) { row.assign(res, 0) } 
    } 

    final class SQLiteWrapper(dbFile : File) 
    { 
     val conn = new SQLiteConnection(dbFile) 
     conn.open() 

     def exec(statement : String) 
     { 
      conn.exec(statement) 
     } 

     def prepare[T <: HList](query : String, row : T) = 
     { 
      new PreparedStatement(query, row) 
     } 

     // TODO: Parameterise with tuple type 
     // make applicable to for comprehensions (implement filter, map, flatMap) 
     final class PreparedStatement[T <: HList](query : String, var row : T) 
     { 
      val statement = conn.prepare(query) 

      private def bindRec(index : Int, params : List[Any]) 
      { 
       println("Value " + params.head) 
       // TODO: Does this need a pattern match? 
       params.head match 
       { 
        case v : Int => statement.bind(index, v) 
        case v : String => statement.bind(index, v) 
        case v : Double => statement.bind(index, v) 
        case _ => throw new ClassCastException("Unsupported type in bind.") 
       } 

       if (params.tail != Nil) 
       { 
        bindRec(index+1, params.tail) 
       } 
      } 

      def bind(args : Any*) 
      { 
       bindRec(1, args.toList) 
      } 

      def exec(args : Any*) 
      { 
       bindRec(1, args.toList) 
       step() 
       reset() 
      } 

      def reset() 
      { 
       statement.reset() 
      } 

      def step() : Boolean = 
      { 
       val success = statement.step() 
       row.assign(statement, 0) 
       return success 
      } 
     } 
    } 
}