Scala的新手,但在C ++方面经验我试图在sqlite4java库之上实现(可能被误导)一个小型库,以允许我从查询行(列类型为的行)自动填充abritrary类型的元组每个都与相应的元组元素类型兼容。)
在C ++中,我通常使用boost :: tuples和编译时模板递归来实现它(使用模板特化终止,如下所示)。 Boost元组的实现与Haskell HLists非常相似。模式将是(假设为简单起见,查询作为字符串向量返回):
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做类似的事情。我可以通过Product trait递归一般的Tuple类型 - 并使用模式匹配(对于我支持的少量列类型)在运行时获取每个元素的类型。但是我还没有找到通过产品特性分配元组元素的方法。说实话,我不相信这是一种特别好或惯用的方式来做我需要的。
但是像:
val returnedFromQuery = List[String]( "Hello", "4", "6.0" )
val rowAsTuples = interpretListAsTuple[(String, Int, Float)]( returnedFromQuery )
其中rowAsTuples具有类型(String,Int,Float)。再次请原谅任何语法错误。
有人有什么想法吗?还是另类建议?事先 - 我对任何更高级别的SQL查询库都不感兴趣。我对sqlite4java很满意,但想用一个简单的更抽象的界面来包装它。
答案 0 :(得分: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)内置自动错误检查(带选项)。
答案 1 :(得分:1)
尝试MetaScala库中的HList。
答案 2 :(得分:1)
我认为你遇到了动态元组arity的问题,所以你必须为每个元组实现一个方法,类似的东西:
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)
很抱歉,此代码不起作用,因为scala String
float
中Predef
到LowPriorityImplicits
的隐式转化含糊不清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
}
如果您只想处理一个元组,请使用:
{{1}}
当然,论证必须很好地形成。
答案 3 :(得分:0)
AFAIK,你无法绕过类型参数arity。为了证明这一点,假设函数和元组对每个arity都有一个定义,直到22的任意arity。
如果您嵌套类型声明,这是HList
的现有实现,那么您可以做一些事情。
答案 4 :(得分: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" )
}
为了实现这一点,使用各种有用的SO建议我已经提出了下面的代码。这是我在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
}
}
}
}