Type Inference

Scautable supports a bouqet of type inference strategies via it's TypeInferrer enum. The varying strategies are discussed below.

Scautable targets "small". If you ask the compiler to infer the types of all rows in a 10 million row CSV file, strange things may begin to happen.

String Type

This is essentially "Safe Mode". it does nothing other than read the strings. It should fail, only if the CSV is poorly formed.

import io.github.quafadas.table.*

inline val csvContent = "Name,Age\nAlice,30\nBob,24\nJim,50"
// csvContent: String = """Name,Age
// Alice,30
// Bob,24
// Jim,50"""

val c: CsvIterator[("Name", "Age"), (String, String)] = CSV.fromString(csvContent, TypeInferrer.StringType)
// c: CsvIterator[Tuple2["Name", "Age"], Tuple2[String, String]] = empty iterator

c.foreach(println)
// (Alice,30)
// (Bob,24)
// (Jim,50)

FirstN

The compiler will read the firstN rows of the CSV file. It will test every cell, in every column, for the firstN rows, whether they can be decoded as

CSV.fromString(csvContent, TypeInferrer.FirstN(2))
// res1: CsvIterator[Tuple2["Name", "Age"], *:[String, *:[Int, EmptyTuple]]] = non-empty iterator

FirstRow

Is firstN, but with Rows set to be 1 🤷

CSV.fromString(csvContent, TypeInferrer.FirstRow)
// res2: CsvIterator[Tuple2["Name", "Age"], *:[String, *:[Int, EmptyTuple]]] = non-empty iterator

FromAllRows

Is firstN, but with Rows set to be Int.MaxValue.

val tmp: CsvIterator[("Name", "Age"), (String, Int)]= CSV.fromString(csvContent, TypeInferrer.FromAllRows)
// tmp: CsvIterator[Tuple2["Name", "Age"], Tuple2[String, Int]] = empty iterator

tmp.foreach(println)
// (Alice,30)
// (Bob,24)
// (Jim,50)

FromTuple

You take "manual" control of the decoding process.

val csv1: CsvIterator[("Name", "Age"), (String, Option[Double])] = CSV.fromString(
  csvContent,
  TypeInferrer.FromTuple[(String, Option[Double])]()
)
// csv1: CsvIterator[Tuple2["Name", "Age"], Tuple2[String, Option[Double]]] = empty iterator
csv1.foreach(println)
// (Alice,Some(30.0))
// (Bob,Some(24.0))
// (Jim,Some(50.0))

Using this strategy, is it possible to decode the CSV to custom types.

import io.github.quafadas.scautable.Decoder

enum Status:
      case Active, Inactive

inline given Decoder[Status] with
  def decode(str: String): Option[Status] =
    str match
      case "Active"   => Some(Status.Active)
      case "Inactive" => Some(Status.Inactive)
      case _          => None

val csv: CsvIterator[("name", "active", "status"), (String, Boolean, Status)] =
      CSV.fromString("name,active,status\nAlice,true,Active\nBob,false,Inactive", TypeInferrer.FromTuple[(String, Boolean, Status)]())
// csv: CsvIterator[Tuple3["name", "active", "status"], Tuple3[String, Boolean, Status]] = empty iterator

csv.foreach(println)
// (Alice,true,Active)
// (Bob,false,Inactive)