Discussion about structural types(index-based)

I agree with you, IIUC there are no such abstraction in scala :frowning:

I hope it will never happen in the word of databases. I do not want to make manually such big volume of work which can be done automatically by declarative languages.
I understand desire to convert all tasks to orm. But an orm is not a silver bullet. Oracle database solves such amount of works which is difficult to imagine outside. It is nightmare to try solving such tasks manually.

I think in Anorm, you can write something like:

SQL(“SELECT * FROM MyTable”)().foreach { row =>

println(rowInt)

println(rowInt)

}

It is often repeated that structural types are slow, but do they have to be? I don’t fully grok invokeDynamic on the JVM, but it seems to me that it could potentially be used to give structural types nearly the same performance of regular access.

I have always thought it was simply that no one has had the time and interest to make scalac emit the correct code to do this.

If this is correct, it could be a big change to scala: the ability to use structural types with (nearly) no perf hit would allow us to write libraries that are much less tightly coupled yet still type-safe and fast.

We use anorm, it is very good library.
I do not understand your example.
What type does row have?
If it is “scala.Dynamic” I will not be able to see any difficulties. But It will be just slower than tuples. Tuples will be hard to read and do refactor when there are large amount of columns.

scalac already uses invokedynamic to implement structural calls: scala/src/compiler/scala/tools/nsc/transform/CleanUp.scala at 3bb7dbd8f047267f66e33ae89d18f51667a686e0 · scala/scala · GitHub. Unfortunately invokedynamic is not the silver bullet you would hope it’d be, so structural calls are still significantly slower than regular ones.

The type of row is anorm.Row, I believe.

Ok, Let’s not guess.
There are source code:

  /**
   * Returns parsed column.
   *
   * @param name Column name
   * @param c Column mapping
   *
   * {{{
   * import anorm.Column.columnToString // mapping column to string
   *
   * val res: (String, String) = SQL("SELECT * FROM Test").map(row =>
   *   row("code") -> row("label") // string columns 'code' and 'label'
   * )
   * }}}
   */
  def apply[B](name: String)(implicit c: Column[B]): B =
    unsafeGet(SqlParser.get(name)(c))

Let us navigate further.

  def get[T](name: String)(implicit extractor: Column[T]): RowParser[T] =
    RowParser { row =>
      (for {
        col <- row.get(name)
        res <- extractor.tupled(col)
      } yield res).fold(Error(_), Success(_))
    }

let’s see row.get(name)

private[anorm] def get(a: String): MayErr[SqlRequestError, (Any, MetaDataItem)] = for {
    m <- MayErr(metaData.get(a.toUpperCase).toRight(ColumnNotFound(a, this)))
    data <- {
      def d = if (a.indexOf(".") > 0) {
        // if expected to be a qualified (dotted) name
        columnsDictionary.get(m.column.qualified.toUpperCase).
          orElse(m.column.alias.flatMap(aliasesDictionary.get(_)))

      } else {
        m.column.alias.flatMap(a => aliasesDictionary.get(a.toUpperCase)).
          orElse(columnsDictionary.get(m.column.qualified.toUpperCase))
      }

      MayErr(d.toRight(
        ColumnNotFound(m.column.qualified, metaData.availableColumns)))
    }
  } yield (data, m)

Quite frankly It is not good from performance point of view.
I can make faster on jexl.
So we love anorm, but we cannot use such of its api everywhere.

I’m just going to link to my post with examples of other systems with index-based records, just so that we don’t think that it’s something novel or not implemented before.

If you know all the labels in the record (e.g. row types without subtyping), calculating index is trivial by just hashing the label name at compile-time.