Skip to content

Commit

Permalink
Morphir Core and IR work (#91)
Browse files Browse the repository at this point in the history
* Further application of recursion schemes for working with the Type AST

* Rollback un-needed changes

* Change over to using scala.BigDecimal to back the Decimal type

* Ensure better ergonomics with the Decimal type
  • Loading branch information
DamianReeves authored Feb 14, 2021
1 parent d89088a commit a345218
Show file tree
Hide file tree
Showing 10 changed files with 384 additions and 51 deletions.
83 changes: 60 additions & 23 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object Deps {
val zioLogging = "0.5.6"
val zioMagic = "0.1.8"
val zioNio = "1.0.0-RC10"
val zioPrelude = "1.0.0-RC1"
val zioPrelude = "1.0.0-RC2"
val zioProcess = "0.2.0"
val newtype = "0.4.4"
def decline(scalaVersion: String) = scalaVersion match {
Expand All @@ -52,6 +52,7 @@ object Deps {
val slf4zio = "1.0.0"
val scalactic = "3.1.2"
val scalaUri = "2.2.2"
val spark = "2.4.7"
val oslib = "0.6.2"
val quill = "3.6.0-RC3"
}
Expand Down Expand Up @@ -220,28 +221,28 @@ object morphir extends Module {
}
}
}

object scala extends Module {

object jvm extends Cross[JvmMorphirScalaModule](Versions.scala213)
class JvmMorphirScalaModule(val crossScalaVersion: String)
extends CrossScalaModule
with CommonJvmModule
with ScalaMacroModule
with MorphirPublishModule { self =>
def artifactName = "morphir-scala"
def moduleDeps = Seq(morphir.ir.jvm(crossScalaVersion))

def ivyDeps = Agg(
ivy"org.scalameta::scalameta:${Versions.scalameta}"
)

object test extends Tests {
def platformSegment: String = self.platformSegment
def crossScalaVersion = JvmMorphirScalaModule.this.crossScalaVersion
}
}
}
//
// object scala extends Module {
//
// object jvm extends Cross[JvmMorphirScalaModule](Versions.scala213)
// class JvmMorphirScalaModule(val crossScalaVersion: String)
// extends CrossScalaModule
// with CommonJvmModule
// with ScalaMacroModule
// with MorphirPublishModule { self =>
// def artifactName = "morphir-scala"
// def moduleDeps = Seq(morphir.ir.jvm(crossScalaVersion))
//
// def ivyDeps = Agg(
// ivy"org.scalameta::scalameta:${Versions.scalameta}"
// )
//
// object test extends Tests {
// def platformSegment: String = self.platformSegment
// def crossScalaVersion = JvmMorphirScalaModule.this.crossScalaVersion
// }
// }
// }
object sdk extends Module {

object core extends Module {
Expand All @@ -263,6 +264,42 @@ object morphir extends Module {
}
}
}

object spark extends Module {
object jvm
extends Cross[JvmMorphirSdkSpark](
Versions.scala212,
Versions.scala211
)
class JvmMorphirSdkSpark(val crossScalaVersion: String)
extends CrossScalaModule
with CommonJvmModule
with MorphirPublishModule { self =>

def artifactName = "morphir-sdk-spark"
def compileIvyDeps = Agg(
ivy"org.apache.spark::spark-sql:2.4.7",
ivy"com.github.ghik:::silencer-lib:${Versions.silencer}"
)
def ivyDeps = Agg(
ivy"dev.zio::zio-prelude:${Versions.zioPrelude}"
)
def scalacPluginIvyDeps = Agg(ivy"com.github.ghik:::silencer-plugin:${Versions.silencer}")
def moduleDeps = Seq(morphir.sdk.core.jvm(crossScalaVersion))

object test extends Tests {
def platformSegment: String = self.platformSegment
def crossScalaVersion = JvmMorphirSdkSpark.this.crossScalaVersion

override def ivyDeps = super.ivyDeps() ++
Agg(
ivy"dev.zio::zio-logging:${Versions.zioLogging}",
ivy"dev.zio::zio-logging-slf4j:${Versions.zioLogging}",
ivy"org.apache.spark::spark-sql:2.4.7"
)
}
}
}
}

object flowz extends Module {
Expand Down
99 changes: 89 additions & 10 deletions morphir/ir/src/morphir/ir/recursions.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package morphir.ir

import zio.ZIO

/**
* In look at how we can potentially model the Morphir IR, research has shown that recursIt woulkd
*/
Expand All @@ -24,27 +26,88 @@ object recursions {
type Name = String
type FQNAme = String

final case class Field[+Self](name: Name, value: Self)
final case class Field[+Self](name: Name, value: Self) {
def map[Self2](fn: Self => Self2): Field[Self2] = Field(name, fn(value))
def mapM[R, E, Self2](fn: Self => ZIO[R, E, Self2]): ZIO[R, E, Field[Self2]] = fn(value).map(Field(name, _))
}

sealed trait TypeCase[+Self, +Attrib] {
def map[Self2](fn: Self => Self2): TypeCase[Self2, Attrib] = ???
def map[Self2](fn: Self => Self2): TypeCase[Self2, Attrib]
def mapAttrib[Attrib2](f: Attrib => Attrib2): TypeCase[Self, Attrib2]
def mapM[R, E, Self2](fn: Self => ZIO[R, E, Self2]): ZIO[R, E, TypeCase[Self2, Attrib]]
}
object TypeCase {
final case class Variable[Attrib](a: Attrib) extends TypeCase[Nothing, Attrib]
final case class Variable[Attrib](a: Attrib) extends TypeCase[Nothing, Attrib] { self =>
def map[Self2](fn: Nothing => Self2): TypeCase[Self2, Attrib] = self

def mapAttrib[Attrib2](f: Attrib => Attrib2): TypeCase[Nothing, Attrib2] = copy(a = f(a))

def mapM[R, E, Self2](fn: Nothing => ZIO[R, E, Self2]): ZIO[R, E, TypeCase[Self2, Attrib]] = ZIO.succeed(self)
}
final case class Reference[Self, Attrib](attribute: Attrib, name: FQName, types: List[Self])
extends TypeCase[Self, Attrib]
final case class Tuple[Self, Attrib](attribute: Attrib, types: List[Self]) extends TypeCase[Self, Attrib]
final case class Record[Self, Attrib](attributes: Attrib, fields: List[Field[Self]]) extends TypeCase[Self, Attrib]
final case class ExtensibleRecord[Self, Attrib](attributes: Attrib, name: Name, fields: List[Field[Self]])
extends TypeCase[Self, Attrib]
final case class Function[Self, Attrib](attribute: Attrib, input: Self, output: Self) extends TypeCase[Self, Attrib]
final case class Unit[+Attrib](attribute: Attrib) extends TypeCase[Nothing, Attrib]
extends TypeCase[Self, Attrib] {
def map[Self2](fn: Self => Self2): TypeCase[Self2, Attrib] = Reference(attribute, name, types.map(fn))

def mapAttrib[Attrib2](f: Attrib => Attrib2): TypeCase[Self, Attrib2] = copy(attribute = f(attribute))

def mapM[R, E, Self2](fn: Self => ZIO[R, E, Self2]): ZIO[R, E, TypeCase[Self2, Attrib]] =
ZIO.foreach(types)(fn).map(Reference(attribute, name, _))
}
final case class Tuple[Self, Attrib](attribute: Attrib, types: List[Self]) extends TypeCase[Self, Attrib] {
def map[Self2](fn: Self => Self2): TypeCase[Self2, Attrib] = Tuple(attribute, types.map(fn))

def mapAttrib[Attrib2](f: Attrib => Attrib2): TypeCase[Self, Attrib2] = copy(attribute = f(attribute))

def mapM[R, E, Self2](fn: Self => ZIO[R, E, Self2]): ZIO[R, E, TypeCase[Self2, Attrib]] =
ZIO.foreach(types)(fn).map(Tuple(attribute, _))
}
final case class Record[Self, Attrib](attribute: Attrib, fields: List[Field[Self]]) extends TypeCase[Self, Attrib] {
def map[Self2](fn: Self => Self2): TypeCase[Self2, Attrib] = Record(attribute, fields.map(_.map(fn)))
def mapAttrib[Attrib2](f: Attrib => Attrib2): TypeCase[Self, Attrib2] = copy(attribute = f(attribute))

def mapM[R, E, Self2](fn: Self => ZIO[R, E, Self2]): ZIO[R, E, TypeCase[Self2, Attrib]] =
ZIO.foreach(fields)(_.mapM(fn)).map(Record(attribute, _))
}
final case class ExtensibleRecord[Self, Attrib](attribute: Attrib, name: Name, fields: List[Field[Self]])
extends TypeCase[Self, Attrib] {
def map[Self2](fn: Self => Self2): TypeCase[Self2, Attrib] =
ExtensibleRecord(attribute, name, fields.map(_.map(fn)))

def mapAttrib[Attrib2](f: Attrib => Attrib2): TypeCase[Self, Attrib2] = copy(attribute = f(attribute))

def mapM[R, E, Self2](fn: Self => ZIO[R, E, Self2]): ZIO[R, E, TypeCase[Self2, Attrib]] =
ZIO.foreach(fields)(_.mapM(fn)).map(ExtensibleRecord(attribute, name, _))
}
final case class Function[Self, Attrib](attribute: Attrib, input: Self, output: Self)
extends TypeCase[Self, Attrib] {
def map[Self2](fn: Self => Self2): TypeCase[Self2, Attrib] = Function(attribute, fn(input), fn(output))
def mapAttrib[Attrib2](f: Attrib => Attrib2): TypeCase[Self, Attrib2] = copy(attribute = f(attribute))

def mapM[R, E, Self2](fn: Self => ZIO[R, E, Self2]): ZIO[R, E, TypeCase[Self2, Attrib]] =
ZIO.mapN(fn(input), fn(output)) { case (input1, output1) =>
Function(attribute, input1, output1)
}
}
final case class Unit[+Attrib](attribute: Attrib) extends TypeCase[Nothing, Attrib] { self =>
def map[Self2](fn: Nothing => Self2): TypeCase[Self2, Attrib] = self
def mapAttrib[Attrib2](f: Attrib => Attrib2): TypeCase[Nothing, Attrib2] = copy(attribute = f(attribute))

def mapM[R, E, Self2](fn: Nothing => ZIO[R, E, Self2]): ZIO[R, E, TypeCase[Self2, Attrib]] = ZIO.succeed(self)
}
}

final case class Type[+Attrib](value: TypeCase[Type[Attrib], Attrib]) { self =>

//NOTE: Useful for bottom up type inference for example
def annotate[Attrib2](f: TypeCase[Type[Attrib2], Attrib] => Attrib2): Type[Attrib2] =
transformUpRecursive[Attrib2](value => value.mapAttrib(_ => f(value)))

def fold[Z](f: TypeCase[Z, Attrib] => Z): Z =
f(value.map(_.fold(f)))

def foldM[R, E, Z](f: TypeCase[Z, Attrib] => ZIO[R, E, Z]): ZIO[R, E, Z] =
value.mapM(_.foldM(f)).flatMap(f)

/**
* Transform the whole tree.
* Top down
Expand All @@ -58,13 +121,29 @@ object recursions {
def transformUp[Attrib2](f: TypeMapper[Attrib, Attrib2]): Type[Attrib2] =
Type(f(value.map(_.transformUp(f))))

def transformDownRecursive[Attrib1 >: Attrib, Attrib2](
f: TypeCase[Type[Attrib1], Attrib1] => TypeCase[Type[Attrib1], Attrib2]
): Type[Attrib2] =
Type(f(value).map(_.transformDownRecursive(f)))

def transformUpRecursive[Attrib2](
f: TypeCase[Type[Attrib2], Attrib] => TypeCase[Type[Attrib2], Attrib2]
): Type[Attrib2] =
Type(f(value.map(_.transformUpRecursive(f))))

}

object Type {
def unfold[Z, Attrib](initial: Z)(f: Z => TypeCase[Z, Attrib]): Type[Attrib] =
Type(f(initial).map(unfold(_)(f)))
}

final case class TypeMapperRec[A, B, C, D](fn: TypeCase[Type[A], B] => TypeCase[Type[C], D])
extends Function[TypeCase[Type[A], B], TypeCase[Type[C], D]] {
override def apply(v1: TypeCase[Type[A], B]): TypeCase[Type[C], D] = fn(v1)

}

trait TypeMapper[-AttribIn, +AttribOut] {
def apply[Self](value: TypeCase[Self, AttribIn]): TypeCase[Self, AttribOut]
}
Expand Down
14 changes: 14 additions & 0 deletions morphir/sdk/core/src-2.11/morphir/sdk/DecimalModuleCompat.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package morphir.sdk

trait DecimalModuleCompat {
import DecimalModuleCompat._
implicit def toBigDecimalOps(value: BigDecimal): BigDecimalOps =
new BigDecimalOps(value)
}

object DecimalModuleCompat {
class BigDecimalOps(private val self: BigDecimal) extends AnyVal {
def compareTo(that: BigDecimal): Int =
self.compare(that)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package morphir.sdk

trait DecimalModuleCompat {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package morphir.sdk

trait DecimalModuleCompat {}
41 changes: 23 additions & 18 deletions morphir/sdk/core/src/morphir/sdk/Decimal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,32 @@ package morphir.sdk
import morphir.sdk.Maybe.Maybe
import morphir.sdk.Basics.Order

import java.math.{ BigDecimal => BigDec, RoundingMode }
import java.math.{ BigDecimal => BigDec }
import scala.util.control.NonFatal

object Decimal {
object Decimal extends DecimalModuleCompat {

type Decimal = BigDec
type Decimal = BigDecimal

object Decimal {
def apply(value: BigDec): Decimal = value
def apply(value: scala.BigDecimal): Decimal = value.bigDecimal
def apply(value: BigDec): Decimal = BigDecimal(value)
def apply(value: scala.BigDecimal): Decimal = value
def apply(value: morphir.sdk.Float.Float): Decimal = BigDecimal.exact(value).bigDecimal
def apply(value: morphir.sdk.Int.Int): Decimal = BigDecimal(value).bigDecimal

}

@inline def apply(value: BigDec): Decimal = Decimal.apply(value)
@inline def apply(value: scala.BigDecimal): Decimal = Decimal.apply(value)
@inline def apply(value: morphir.sdk.Float.Float): Decimal = Decimal.apply(value)
@inline def apply(value: morphir.sdk.Int.Int): Decimal = Decimal.apply(value)

/**
* Absolute value (sets the sign as positive)
*/
def abs(value: Decimal): Decimal = value.abs()
def abs(value: Decimal): Decimal = value.abs

def add(a: Decimal)(b: Decimal): Decimal = a.add(b)
def add(a: Decimal)(b: Decimal): Decimal = a + b

def bps(n: morphir.sdk.Int.Int): Decimal = Decimal(n * 0.0001)

Expand All @@ -37,7 +42,7 @@ object Decimal {
if (b.compareTo(zero) == 0) Maybe.nothing
else
try {
Maybe.just(a.divide(b))
Maybe.just(a / b)
} catch {
case NonFatal(_) => Maybe.nothing
}
Expand Down Expand Up @@ -83,39 +88,39 @@ object Decimal {
def millionth(n: morphir.sdk.Int.Int): Decimal =
Decimal(n * 0.000001)

def mul(a: Decimal)(b: Decimal): Decimal = a.multiply(b)
def mul(a: Decimal)(b: Decimal): Decimal = a * b

@inline def ne(a: Decimal)(b: Decimal): morphir.sdk.Bool.Bool = neq(a)(b)
def neq(a: Decimal)(b: Decimal): morphir.sdk.Bool.Bool = a.compareTo(b) != 0

def negate(value: Decimal): Decimal = value.negate()
def negate(value: Decimal): Decimal = -value

def round(decimal: Decimal): Decimal = {
val scale = decimal.scale()
decimal.setScale(scale, RoundingMode.HALF_EVEN)
val scale = decimal.scale
decimal.setScale(scale, BigDecimal.RoundingMode.HALF_EVEN)
}

def shiftDecimalLeft(n: morphir.sdk.Int.Int)(value: Decimal): Decimal =
value.scaleByPowerOfTen(-n.intValue()) //TODO: When we align Int to Int this should settle in correctly
value.bigDecimal.scaleByPowerOfTen(-n.intValue()) //TODO: When we align Int to Int this should settle in correctly

def shiftDecimalRight(n: morphir.sdk.Int.Int)(value: Decimal): Decimal =
value.scaleByPowerOfTen(n.intValue()) //TODO: When we align Int to Int this should settle in correctly
value.bigDecimal.scaleByPowerOfTen(n.intValue()) //TODO: When we align Int to Int this should settle in correctly

def sub(a: Decimal)(b: Decimal): Decimal = a.subtract(b)
def sub(a: Decimal)(b: Decimal): Decimal = a - b

def thousand(n: morphir.sdk.Int.Int): Decimal =
Decimal(n * 1000)

def toFloat(value: Decimal): morphir.sdk.Float.Float =
morphir.sdk.Float.Float(value.doubleValue())
morphir.sdk.Float.Float(value.toDouble)

//TODO: Make sure the Elm call and this call return the same value
def toString(value: Decimal): morphir.sdk.String.String = value.toString

def truncate(decimal: Decimal): Decimal = {
// Since morphir's Int is actually a Long this isn't really safe
val scale = decimal.scale()
decimal.setScale(scale, RoundingMode.DOWN)
val scale = decimal.scale
decimal.setScale(scale, BigDecimal.RoundingMode.DOWN)
}

/**
Expand Down
12 changes: 12 additions & 0 deletions morphir/sdk/core/test/src/morphir/sdk/DecimalSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package morphir.sdk

import zio.test.Assertion._
import zio.test.{ assert, DefaultRunnableSpec }

object DecimalSpec extends DefaultRunnableSpec {
def spec = suite("Decimal Spec")(
test("It should be possible to assign an int value to the Decimal") {
assert(Decimal(42))(equalTo(Decimal.fromInt(42)))
}
)
}
Loading

0 comments on commit a345218

Please sign in to comment.