Skip to content

Commit 0afc0ba

Browse files
committed
explored experimental generics in kotlin contracts and what they could bring to dataframe
1 parent 5cf90d6 commit 0afc0ba

File tree

8 files changed

+46
-19
lines changed

8 files changed

+46
-19
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ allprojects {
194194
// enables support for kotlin.time.Instant as kotlinx.datetime.Instant was deprecated; Issue #1350
195195
// Can be removed once kotlin.time.Instant is marked "stable".
196196
optIn.add("kotlin.time.ExperimentalTime")
197+
freeCompilerArgs.addAll("-Xallow-contracts-on-more-functions")
197198
}
198199
}
199200

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/DataColumnType.kt

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
@file:OptIn(ExperimentalContracts::class)
1+
@file:OptIn(ExperimentalContracts::class, ExperimentalExtendedContracts::class)
22

33
package org.jetbrains.kotlinx.dataframe.api
44

55
import org.jetbrains.kotlinx.dataframe.AnyCol
6+
import org.jetbrains.kotlinx.dataframe.DataColumn
67
import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
78
import org.jetbrains.kotlinx.dataframe.columns.ColumnKind
89
import org.jetbrains.kotlinx.dataframe.columns.FrameColumn
@@ -17,6 +18,7 @@ import org.jetbrains.kotlinx.dataframe.util.IS_COMPARABLE
1718
import org.jetbrains.kotlinx.dataframe.util.IS_COMPARABLE_REPLACE
1819
import org.jetbrains.kotlinx.dataframe.util.IS_INTER_COMPARABLE_IMPORT
1920
import kotlin.contracts.ExperimentalContracts
21+
import kotlin.contracts.ExperimentalExtendedContracts
2022
import kotlin.contracts.contract
2123
import kotlin.reflect.KType
2224
import kotlin.reflect.full.isSubtypeOf
@@ -40,21 +42,36 @@ public fun AnyCol.isValueColumn(): Boolean {
4042
public fun AnyCol.isSubtypeOf(type: KType): Boolean =
4143
this.type.isSubtypeOf(type) && (!this.type.isMarkedNullable || type.isMarkedNullable)
4244

43-
public inline fun <reified T> AnyCol.isSubtypeOf(): Boolean = isSubtypeOf(typeOf<T>())
45+
public inline fun <reified T> AnyCol.isSubtypeOf(): Boolean {
46+
contract { returns(true) implies (this@isSubtypeOf is DataColumn<T>) }
47+
return isSubtypeOf(typeOf<T>())
48+
}
4449

45-
public inline fun <reified T> AnyCol.isType(): Boolean = type() == typeOf<T>()
50+
public inline fun <reified T> AnyCol.isType(): Boolean {
51+
contract { returns(true) implies (this@isType is DataColumn<T>) }
52+
return type() == typeOf<T>()
53+
}
4654

4755
/** Returns `true` when this column's type is a subtype of `Number?` */
48-
public fun AnyCol.isNumber(): Boolean = isSubtypeOf<Number?>()
56+
public fun AnyCol.isNumber(): Boolean {
57+
contract { returns(true) implies (this@isNumber is ValueColumn<Number?>) }
58+
return isSubtypeOf<Number?>()
59+
}
4960

5061
/** Returns `true` only when this column's type is exactly `Number` or `Number?`. */
51-
public fun AnyCol.isMixedNumber(): Boolean = type().isMixedNumber()
62+
public fun AnyCol.isMixedNumber(): Boolean {
63+
contract { returns(true) implies (this@isMixedNumber is ValueColumn<Number?>) }
64+
return type().isMixedNumber()
65+
}
5266

5367
/**
5468
* Returns `true` when this column has the (nullable) type of either:
5569
* [Byte], [Short], [Int], [Long], [Float], or [Double].
5670
*/
57-
public fun AnyCol.isPrimitiveNumber(): Boolean = type().isPrimitiveNumber()
71+
public fun AnyCol.isPrimitiveNumber(): Boolean {
72+
contract { returns(true) implies (this@isPrimitiveNumber is ValueColumn<Number?>) }
73+
return type().isPrimitiveNumber()
74+
}
5875

5976
/**
6077
* Returns `true` when this column has the (nullable) type of either:
@@ -63,9 +80,15 @@ public fun AnyCol.isPrimitiveNumber(): Boolean = type().isPrimitiveNumber()
6380
* Careful: Will return `true` if the column contains multiple number types that
6481
* might NOT be primitive.
6582
*/
66-
public fun AnyCol.isPrimitiveOrMixedNumber(): Boolean = type().isPrimitiveOrMixedNumber()
83+
public fun AnyCol.isPrimitiveOrMixedNumber(): Boolean {
84+
contract { returns(true) implies (this@isPrimitiveOrMixedNumber is ValueColumn<Number?>) }
85+
return type().isPrimitiveOrMixedNumber()
86+
}
6787

68-
public fun AnyCol.isList(): Boolean = typeClass == List::class
88+
public fun AnyCol.isList(): Boolean {
89+
contract { returns(true) implies (this@isList is ValueColumn<List<*>>) }
90+
return typeClass == List::class
91+
}
6992

7093
/** @include [valuesAreComparable] */
7194
@Deprecated(
@@ -84,4 +107,7 @@ public fun AnyCol.isComparable(): Boolean = valuesAreComparable()
84107
*
85108
* Technically, this means the values' common type `T(?)` is a subtype of [Comparable]`<in T>(?)`
86109
*/
87-
public fun AnyCol.valuesAreComparable(): Boolean = isValueColumn() && type().isIntraComparable()
110+
public fun <T> DataColumn<T>.valuesAreComparable(): Boolean {
111+
contract { returns(true) implies (this@valuesAreComparable is ValueColumn<Comparable<T>>) }
112+
return isValueColumn() && type().isIntraComparable()
113+
}

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/typeConversions.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ public fun DataColumn<Any>.asNumbers(): ValueColumn<Number> {
8989

9090
public fun <T : Any> DataColumn<T>.asComparable(): DataColumn<Comparable<T>> {
9191
require(valuesAreComparable())
92-
return this as DataColumn<Comparable<T>>
92+
return this
9393
}
9494

9595
@JvmName("asComparableNullable")
9696
public fun <T : Any?> DataColumn<T?>.asComparable(): DataColumn<Comparable<T>?> {
9797
require(valuesAreComparable())
98-
return this as DataColumn<Comparable<T>?>
98+
return this
9999
}
100100

101101
public fun <T> ColumnReference<T?>.castToNotNullable(): ColumnReference<T> = cast()

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/describe.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ internal fun describeImpl(cols: List<AnyCol>): DataFrame<ColumnDescription> {
5555
?.key
5656
}
5757
if (hasNumericCols) {
58-
ColumnDescription::mean from { if (it.isNumber()) it.asNumbers().mean() else null }
59-
ColumnDescription::std from { if (it.isNumber()) it.asNumbers().std() else null }
58+
ColumnDescription::mean from { if (it.isNumber()) it.mean() else null }
59+
ColumnDescription::std from { if (it.isNumber()) it.std() else null }
6060
}
6161
if (hasComparableCols || hasNumericCols) {
6262
ColumnDescription::min from inferType {
@@ -113,10 +113,10 @@ private fun List<AnyCol>.collectAll(atAnyDepth: Boolean): List<AnyCol> =
113113
@Suppress("UNCHECKED_CAST")
114114
private fun DataColumn<Any?>.convertToComparableOrNull(): DataColumn<Comparable<Any>?>? {
115115
return when {
116-
valuesAreComparable() -> asComparable()
116+
valuesAreComparable() -> this
117117

118118
// Found incomparable number types, convert all to Double first
119-
isNumber() -> cast<Number?>().map {
119+
isNumber() -> map {
120120
if (it?.isPrimitiveNumber() == false) {
121121
// Cannot calculate statistics of a non-primitive number type
122122
return@convertToComparableOrNull null

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/parse.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ internal fun <T> DataFrame<T>.parseImpl(options: ParserOptions?, columns: Column
736736

737737
// Base case, parse the column if it's a `String?` column
738738
col.isSubtypeOf<String?>() ->
739-
col.cast<String?>().tryParseImpl(options)
739+
col.tryParseImpl(options)
740740

741741
else -> col
742742
}

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ internal fun AnyFrame.toHtmlData(
170170
configuration: DisplayConfiguration,
171171
): ColumnDataForJs {
172172
val values = if (rowsLimit != null) rows().take(rowsLimit) else rows()
173-
val scale = if (col.isNumber()) col.asNumbers().scale() else 1
173+
val scale = if (col.isNumber()) col.scale() else 1
174174
val format = if (scale > 0) {
175175
RendererDecimalFormat.fromPrecision(scale)
176176
} else {

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/string.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public fun AnyFrame.renderToString(
4848
}
4949
val values = cols.map {
5050
val top = it.take(rowsLimit)
51-
val precision = if (top.isNumber()) top.asNumbers().scale() else 0
51+
val precision = if (top.isNumber()) top.scale() else 0
5252
val decimalFormat =
5353
if (precision >= 0) RendererDecimalFormat.fromPrecision(precision) else RendererDecimalFormat.of("%e")
5454
top.values().map {

dataframe-json/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/io/writeJson.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ internal fun encodeValue(col: AnyCol, index: Int, customEncoders: List<CustomEnc
176176
matchingEncoder != null -> matchingEncoder.encode(col[index])
177177

178178
col.isList() -> col[index]?.let { list ->
179-
val values = (list as List<*>).map { convert(it) }
179+
val values = list.map { convert(it) }
180180
JsonArray(values)
181181
} ?: JsonArray(emptyList())
182182

0 commit comments

Comments
 (0)