Introducing top and bottom types (#199)

* - Added top and bottom types
- Added nil for empty streams, options, arrays
- Fixed product variance

* Version bump due to syntax changes in the type system
This commit is contained in:
Dmitry Kurinskiy 2021-07-13 17:43:24 +03:00 committed by GitHub
parent fbc34a793a
commit 83d5a7b2a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 68 additions and 9 deletions

View File

@ -2,6 +2,9 @@ service OpH("op"):
puk(s: string) -> string puk(s: string) -> string
pek(s: string, -- trgtr pek(s: string, -- trgtr
c: string) -> string c: string) -> string
pyk: []u32 -> string
pok: []bool -> string
identity: -> ⊥
func a( -- ferkjn func a( -- ferkjn
b: string, -- fr b: string, -- fr
@ -17,4 +20,15 @@ func a( -- ferkjn
catch err: catch err:
OpH.puk(err.msg) OpH.puk(err.msg)
<- f <- f
-- hello -- hello
func z(t: ?string, b: []u32, c: *bool):
OpH.puk(t!)
OpH.pyk(b)
OpH.pok(c)
c <- OpH.identity(true)
c <- OpH.identity("string")
func k():
z(nil, nil, nil)

View File

@ -28,7 +28,7 @@ val cats = "org.typelevel" %% "cats-core" % catsV
name := "aqua-hll" name := "aqua-hll"
val commons = Seq( val commons = Seq(
baseAquaVersion := "0.1.8", baseAquaVersion := "0.1.9",
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"), version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
scalaVersion := dottyVersion, scalaVersion := dottyVersion,
libraryDependencies ++= Seq( libraryDependencies ++= Seq(

View File

@ -1,6 +1,6 @@
package aqua.model package aqua.model
import aqua.types.{ProductType, ScalarType, Type} import aqua.types.{DataType, ProductType, ScalarType, StreamType, Type}
import cats.Eq import cats.Eq
import cats.data.{Chain, NonEmptyMap} import cats.data.{Chain, NonEmptyMap}
import wvlet.log.LogSupport import wvlet.log.LogSupport
@ -110,4 +110,9 @@ object VarModel {
) )
) )
) )
val nil: VarModel = VarModel(
"nil",
StreamType(DataType.Bottom)
)
} }

View File

@ -1,6 +1,7 @@
package aqua.model.transform package aqua.model.transform
import aqua.model.{AquaContext, LiteralModel, ValueModel, VarModel} import aqua.model.{AquaContext, LiteralModel, ValueModel, VarModel}
import aqua.types.{DataType, OptionType}
import cats.kernel.Monoid import cats.kernel.Monoid
case class Constant(name: String, value: ValueModel) case class Constant(name: String, value: ValueModel)
@ -26,7 +27,12 @@ case class BodyConfig(
AquaContext AquaContext
.implicits( .implicits(
AquaContext.blank AquaContext.blank
.copy(values = Map(VarModel.lastError.name -> VarModel.lastError) ++ constantsMap) .copy(values =
Map(
VarModel.lastError.name -> VarModel.lastError,
VarModel.nil.name -> VarModel.nil
) ++ constantsMap
)
) )
.aquaContextMonoid .aquaContextMonoid
} }

View File

@ -65,6 +65,8 @@ object Token {
val `*` : P[Unit] = P.char('*') val `*` : P[Unit] = P.char('*')
val `!` : P[Unit] = P.char('!') val `!` : P[Unit] = P.char('!')
val `[]` : P[Unit] = P.string("[]") val `[]` : P[Unit] = P.string("[]")
val `` : P[Unit] = P.char('')
val `⊥` : P[Unit] = P.char('⊥')
val `(` : P[Unit] = P.char('(').surroundedBy(` `.?) val `(` : P[Unit] = P.char('(').surroundedBy(` `.?)
val `)` : P[Unit] = P.char(')').surroundedBy(` `.?) val `)` : P[Unit] = P.char(')').surroundedBy(` `.?)
val `()` : P[Unit] = P.string("()") val `()` : P[Unit] = P.string("()")

View File

@ -12,6 +12,12 @@ import cats.syntax.functor._
sealed trait TypeToken[F[_]] extends Token[F] sealed trait TypeToken[F[_]] extends Token[F]
sealed trait DataTypeToken[F[_]] extends TypeToken[F] sealed trait DataTypeToken[F[_]] extends TypeToken[F]
case class TopBottomToken[F[_]: Comonad](override val unit: F[Unit], isTop: Boolean)
extends DataTypeToken[F] {
override def as[T](v: T): F[T] = unit.as(v)
def isBottom: Boolean = !isTop
}
case class ArrayTypeToken[F[_]: Comonad](override val unit: F[Unit], data: DataTypeToken[F]) case class ArrayTypeToken[F[_]: Comonad](override val unit: F[Unit], data: DataTypeToken[F])
extends DataTypeToken[F] { extends DataTypeToken[F] {
override def as[T](v: T): F[T] = unit.as(v) override def as[T](v: T): F[T] = unit.as(v)
@ -106,13 +112,17 @@ object DataTypeToken {
def `arraytypedef`[F[_]: LiftParser: Comonad]: P[ArrayTypeToken[F]] = def `arraytypedef`[F[_]: LiftParser: Comonad]: P[ArrayTypeToken[F]] =
(`[]`.lift ~ `datatypedef`[F]).map(ud => ArrayTypeToken(ud._1, ud._2)) (`[]`.lift ~ `datatypedef`[F]).map(ud => ArrayTypeToken(ud._1, ud._2))
def `topbottomdef`[F[_]: LiftParser: Comonad]: P[TopBottomToken[F]] =
`⊥`.lift.map(TopBottomToken(_, isTop = false)) | ``.lift.map(TopBottomToken(_, isTop = true))
def `datatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] = def `datatypedef`[F[_]: LiftParser: Comonad]: P[DataTypeToken[F]] =
P.oneOf( P.oneOf(
P.defer(`arraytypedef`[F]) :: P.defer(StreamTypeToken.`streamtypedef`) :: P.defer( P.defer(`arraytypedef`[F]) :: P.defer(StreamTypeToken.`streamtypedef`) :: P.defer(
OptionTypeToken.`optiontypedef` OptionTypeToken.`optiontypedef`
) :: BasicTypeToken ) :: BasicTypeToken
.`basictypedef`[F] :: CustomTypeToken.ct[F] :: Nil .`basictypedef`[F] :: CustomTypeToken.ct[F] :: `topbottomdef` :: Nil
) )
} }
object TypeToken { object TypeToken {

View File

@ -14,6 +14,7 @@ import aqua.parser.lexer.{
OptionTypeToken, OptionTypeToken,
StreamTypeToken, StreamTypeToken,
Token, Token,
TopBottomToken,
TypeToken TypeToken
} }
import aqua.types.{ArrayType, ArrowType, DataType, OptionType, ProductType, StreamType, Type} import aqua.types.{ArrayType, ArrowType, DataType, OptionType, ProductType, StreamType, Type}
@ -30,6 +31,8 @@ case class TypesState[F[_]](
def resolveTypeToken(tt: TypeToken[F]): Option[Type] = def resolveTypeToken(tt: TypeToken[F]): Option[Type] =
tt match { tt match {
case TopBottomToken(_, isTop) =>
Option(if (isTop) DataType.Top else DataType.Bottom)
case ArrayTypeToken(_, dtt) => case ArrayTypeToken(_, dtt) =>
resolveTypeToken(dtt).collect { case it: DataType => resolveTypeToken(dtt).collect { case it: DataType =>
ArrayType(it) ArrayType(it)

View File

@ -15,6 +15,11 @@ sealed trait Type {
} }
sealed trait DataType extends Type sealed trait DataType extends Type
object DataType {
case object Top extends DataType
case object Bottom extends DataType
}
case class ScalarType private (name: String) extends DataType { case class ScalarType private (name: String) extends DataType {
override def toString: String = name override def toString: String = name
} }
@ -137,19 +142,21 @@ object Type {
lf.toSortedMap.toList.map(_._2), lf.toSortedMap.toList.map(_._2),
rf.toSortedMap.view.filterKeys(lf.keys.contains).toList.map(_._2) rf.toSortedMap.view.filterKeys(lf.keys.contains).toList.map(_._2)
) == -1.0 ) == -1.0
) -1.0 ) 1.0
else if ( else if (
rf.keys.forall(lf.contains) && cmpTypesList( rf.keys.forall(lf.contains) && cmpTypesList(
lf.toSortedMap.view.filterKeys(rf.keys.contains).toList.map(_._2), lf.toSortedMap.view.filterKeys(rf.keys.contains).toList.map(_._2),
rf.toSortedMap.toList.map(_._2) rf.toSortedMap.toList.map(_._2)
) == 1.0 ) == 1.0
) 1.0 ) -1.0
else NaN else NaN
private def cmp(l: Type, r: Type): Double = private def cmp(l: Type, r: Type): Double =
if (l == r) 0.0 if (l == r) 0.0
else else
(l, r) match { (l, r) match {
case (DataType.Top, _: DataType) | (_: DataType, DataType.Bottom) => 1.0
case (DataType.Bottom, _: DataType) | (_: DataType, DataType.Top) => -1.0
case (x: ScalarType, y: ScalarType) => ScalarType.scalarOrder.partialCompare(x, y) case (x: ScalarType, y: ScalarType) => ScalarType.scalarOrder.partialCompare(x, y)
case (LiteralType(xs, _), y: ScalarType) if xs == Set(y) => 0.0 case (LiteralType(xs, _), y: ScalarType) if xs == Set(y) => 0.0
case (LiteralType(xs, _), y: ScalarType) if xs(y) => -1.0 case (LiteralType(xs, _), y: ScalarType) if xs(y) => -1.0

View File

@ -37,6 +37,18 @@ class TypeSpec extends AnyFlatSpec with Matchers {
accepts(f32, LiteralType.number) should be(true) accepts(f32, LiteralType.number) should be(true)
} }
"top type" should "accept anything" in {
accepts(DataType.Top, u64) should be(true)
accepts(DataType.Top, LiteralType.bool) should be(true)
accepts(DataType.Top, `*`(u64)) should be(true)
}
"bottom type" should "be accepted by everything" in {
accepts(u64, DataType.Bottom) should be(true)
accepts(LiteralType.bool, DataType.Bottom) should be(true)
accepts(`*`(u64), DataType.Bottom) should be(true)
}
"arrays of scalars" should "be variant" in { "arrays of scalars" should "be variant" in {
(`[]`(u32): Type) <= u32 should be(false) (`[]`(u32): Type) <= u32 should be(false)
(`[]`(u32): Type) >= u32 should be(false) (`[]`(u32): Type) >= u32 should be(false)
@ -55,8 +67,8 @@ class TypeSpec extends AnyFlatSpec with Matchers {
val two: Type = ProductType("two", NonEmptyMap.of("field" -> u64, "other" -> string)) val two: Type = ProductType("two", NonEmptyMap.of("field" -> u64, "other" -> string))
val three: Type = ProductType("three", NonEmptyMap.of("field" -> u32)) val three: Type = ProductType("three", NonEmptyMap.of("field" -> u32))
one < two should be(true) accepts(one, two) should be(true)
two > one should be(true) accepts(two, one) should be(false)
PartialOrder[Type].eqv(one, three) should be(true) PartialOrder[Type].eqv(one, three) should be(true)
} }