标量元组问题 斯卡拉,但是在C++中我尝试着在SQL Seri4Java库的顶部实现(可能是错误地)一个小的库,允许我自动从查询行中填充AbrTryLype类型的元组(其列类型与每个元组元素类型兼容)。 在C++中,通常使用Boo::元组和编译时模板递归实现这一点(使用下面的模板专门化终止)。Boost元组的实现非常类似于Haskell HLists。模式是(为了简单起见,假设查询作为字符串向量返回):

标量元组问题 斯卡拉,但是在C++中我尝试着在SQL Seri4Java库的顶部实现(可能是错误地)一个小的库,允许我自动从查询行中填充AbrTryLype类型的元组(其列类型与每个元组元素类型兼容)。 在C++中,通常使用Boo::元组和编译时模板递归实现这一点(使用下面的模板专门化终止)。Boost元组的实现非常类似于Haskell HLists。模式是(为了简单起见,假设查询作为字符串向量返回):,sql,database,sqlite,scala,tuples,Sql,Database,Sqlite,Scala,Tuples,其中rowAsTuples具有类型(String、Int、Float)。同样,请原谅任何语法错误 有人有什么想法吗?还是其他建议?提前-我对任何更高级的SQL查询库都不感兴趣。我对sqlite4java很满意,但想用一个更抽象的简单界面来包装它。试试库中的HList。我认为动态元组算术有问题,所以必须为每个元组算术实现一个方法,类似这样: def interpretListAsTuple2[A,B](s: List[String])(implicit s2a: String => A, s

其中rowAsTuples具有类型(String、Int、Float)。同样,请原谅任何语法错误


有人有什么想法吗?还是其他建议?提前-我对任何更高级的SQL查询库都不感兴趣。我对sqlite4java很满意,但想用一个更抽象的简单界面来包装它。

试试库中的HList。

我认为动态元组算术有问题,所以必须为每个元组算术实现一个方法,类似这样:

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的
Predef
String
float
的隐式转换不明确。也许有人能帮忙解决这个问题。不过,主要想法应该很清楚。您只需要为数据类型定义一次隐式转换,然后它就可以与所有元组算术一起工作

编辑:

您可以使用上面的版本来映射具有多个元组的字符串的列表<代码>列表(“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
}

当然,论点必须格式正确。

我认为你应该尝试使用模式匹配而不是解释。首先,我们需要使用
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) 具有内置的自动错误检查功能(带有选项)。

好的,您无法绕过类型参数的算术性。为了证明这一点,我们假设函数和元组对于每一个arity都有一个定义,最高可达22的任意arity


如果嵌套类型声明(这是
HList
的现有实现所做的),那么您可以做一些事情。

所以我问这个问题,因为我想围绕sqlite4java sqlite接口编写一个简单的包装器。要启用以下形式的代码,其中可以在准备好的语句中指定查询的行类型(我打算向基于类似方法传递的参数添加类型检查):

为了实现这一点,我使用了各种有用的建议,下面是我的代码。这是我在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
            }
        }
    }
}
导入java.io.File
导入com.almworks.sqlite4java_
对象SqliteWrapper
{
特征类型DCOL[T]
{
变量v:选项[T]=无
def assign(res:SQLiteStatement,index:Int)
}
密封特征列表
{
def assign(res:SQLiteStatement,index:Int)
}

最终案例类别HCons谢谢。我可能最终会。但是如果可能的话,我想知道它是否可以用标准的元组类型。比我的答案更详细,竖起大拇指。这是一个简洁的答案。可能不像我希望的那样简洁,但是也许为了得到充分的效果,我需要HLIST。两个都要考虑。谢谢。MetaScala带着大量的健康警告来。关于稳定性的问题。据你所知,HList是否相当成熟?还是我应该自己开发?@Alex我从未使用过metascala。我从一些博客上提供的实现中了解到
HList
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))
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" )
}
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
            }
        }
    }
}