From 0b730532afbdfa6d738ffa4745fad426e53d73cf Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Thu, 24 Mar 2022 19:22:19 -0400 Subject: [PATCH] Value implementation that matches finos/morphir-elm and ditches recursion schemes (#75) * Starting alternate implementation of Value that closer matches Elm * Starting alternate implementation of Value that closer matches Elm * Add more operators to Type and Value * Value module ideas * Implement more ValueModule methods --- .../main/scala/zio/morphir/ir/Source.scala | 12 + .../scala/zio/morphir/ir/source/Located.scala | 3 + .../zio/morphir/ir/source/Location.scala | 14 + .../scala/zio/morphir/ir/source/Region.scala | 11 + .../scala/zio/morphir/ir/types/Type.scala | 219 ++++++++++--- .../zio/morphir/ir/value/Definition.scala | 40 +++ .../zio/morphir/ir/value/Specification.scala | 20 ++ .../scala/zio/morphir/ir/value/Value.scala | 289 ++++++++++++++++++ .../zio/morphir/ir/value/ValueAspect.scala | 25 ++ .../zio/morphir/ir/value/ValueModule.scala | 29 ++ .../scala/zio/morphir/ir/value/package.scala | 14 + .../ir/examples/TypeInferenceExample.scala | 114 +++++++ .../zio/morphir/ir/value/ValueSpec.scala | 43 +++ 13 files changed, 784 insertions(+), 49 deletions(-) create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/Source.scala create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Located.scala create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Location.scala create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Region.scala create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Definition.scala create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Specification.scala create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Value.scala create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/value/ValueAspect.scala create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/value/ValueModule.scala create mode 100644 morphir-ir/shared/src/main/scala/zio/morphir/ir/value/package.scala create mode 100644 morphir-ir/shared/src/test/scala/zio/morphir/ir/examples/TypeInferenceExample.scala create mode 100644 morphir-ir/shared/src/test/scala/zio/morphir/ir/value/ValueSpec.scala diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/Source.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Source.scala new file mode 100644 index 00000000..f1cc9ddd --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/Source.scala @@ -0,0 +1,12 @@ +package zio.morphir.ir + +object Source { + type Located[+A] = source.Located[A] + val Located: source.Located.type = source.Located + + type Location = source.Location + val Location: source.Location.type = source.Location + + type Region = source.Region + val Region: source.Region.type = source.Region +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Located.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Located.scala new file mode 100644 index 00000000..fb3c76b6 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Located.scala @@ -0,0 +1,3 @@ +package zio.morphir.ir.source + +final case class Located[+A](at: Region, value: A) diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Location.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Location.scala new file mode 100644 index 00000000..51c3aa44 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Location.scala @@ -0,0 +1,14 @@ +package zio.morphir.ir.source +import zio.prelude._ +final case class Location(row: Int, column: Int) +object Location { + val default: Location = Location(0, 0) + val home: Location = Location(0, 0) + + implicit val LocationIdentity: Identity[Location] = new Identity[Location] { + final val identity: Location = home + + override def combine(l: => Location, r: => Location): Location = + Location(row = l.row + r.row, column = l.column + r.column) + } +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Region.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Region.scala new file mode 100644 index 00000000..bf2b4306 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/source/Region.scala @@ -0,0 +1,11 @@ +package zio.morphir.ir.source +import zio.prelude._ +final case class Region(start: Location, end: Location) +object Region { + val default: Region = Region(Location.default, Location.default) + implicit val RegionIdentity: Identity[Region] = new Identity[Region] { + lazy val identity: Region = default + + override def combine(l: => Region, r: => Region): Region = Region(l.start <> r.start, l.end <> r.end) + } +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/types/Type.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/types/Type.scala index f62477a4..9ea8e3e4 100644 --- a/morphir-ir/shared/src/main/scala/zio/morphir/ir/types/Type.scala +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/types/Type.scala @@ -1,10 +1,10 @@ package zio.morphir.ir.types -import zio.Chunk +import zio.{Chunk, ZIO} import zio.morphir.ir.{Documented, FQName, Name} import zio.morphir.syntax.TypeModuleSyntax import zio.prelude._ - +import zio.prelude.fx._ import scala.annotation.tailrec sealed trait Type[+Attributes] { self => @@ -21,22 +21,81 @@ sealed trait Type[+Attributes] { self => else acc } - def fold[Z: Associative](f: Type[Attributes] => Z): Z = { - @tailrec - def loop(types: List[Type[Attributes]], acc: Z): Z = types match { - case (tpe @ Type.ExtensibleRecord(_, _, _)) :: tail => - loop(tpe.fields.map(_.fieldType).toList ++ tail, f(tpe) <> acc) - case (tpe @ Type.Function(_, _, _)) :: tail => - loop(tpe.paramTypes.toList ++ (tpe.returnType :: tail), f(tpe) <> acc) - case (tpe @ Type.Record(_, _)) :: tail => loop(tpe.fields.map(_.fieldType).toList ++ tail, f(tpe) <> acc) - case (tpe @ Type.Reference(_, _, _)) :: tail => loop(tpe.typeParams.toList ++ tail, f(tpe) <> acc) - case (tpe @ Type.Tuple(_, elements)) :: tail => loop(elements.toList ++ tail, f(tpe) <> acc) - case Type.Variable(_, _) :: tail => loop(tail, acc) - case Type.Unit(_) :: tail => loop(tail, acc) - case Nil => acc + // TODO: See if we can refactor to be stack safe/ tail recursive + def fold[Z]( + extensibleRecordCase: (Attributes, Name, Chunk[Field[Z]]) => Z, + functionCase: (Attributes, Chunk[Z], Z) => Z, + recordCase: (Attributes, Chunk[Field[Z]]) => Z, + referenceCase: (Attributes, FQName, Chunk[Z]) => Z, + tupleCase: (Attributes, Chunk[Z]) => Z, + variableCase: (Attributes, Name) => Z, + unitCase: Attributes => Z + ): Z = + self match { + case Type.ExtensibleRecord(attributes, name, fields) => + extensibleRecordCase( + attributes, + name, + fields.map( + _.map( + _.fold(extensibleRecordCase, functionCase, recordCase, referenceCase, tupleCase, variableCase, unitCase) + ) + ) + ) + case Type.Function(attributes, paramTypes, returnType) => + functionCase( + attributes, + paramTypes.map( + _.fold(extensibleRecordCase, functionCase, recordCase, referenceCase, tupleCase, variableCase, unitCase) + ), + returnType.fold( + extensibleRecordCase, + functionCase, + recordCase, + referenceCase, + tupleCase, + variableCase, + unitCase + ) + ) + case Type.Record(attributes, fields) => + recordCase( + attributes, + fields.map( + _.map( + _.fold(extensibleRecordCase, functionCase, recordCase, referenceCase, tupleCase, variableCase, unitCase) + ) + ) + ) + case Type.Reference(attributes, typeName, typeParams) => + referenceCase( + attributes, + typeName, + typeParams.map( + _.fold(extensibleRecordCase, functionCase, recordCase, referenceCase, tupleCase, variableCase, unitCase) + ) + ) + case Type.Tuple(attributes, elementTypes) => + tupleCase( + attributes, + elementTypes.map( + _.fold(extensibleRecordCase, functionCase, recordCase, referenceCase, tupleCase, variableCase, unitCase) + ) + ) + case Type.Unit(attributes) => unitCase(attributes) + case Type.Variable(attributes, name) => variableCase(attributes, name) } - loop(List(self), f(self)) - } + +// def identity: Type[Attributes] = +// self.fold[Type[Attributes]]( +// (attributes, name, fields) => Type.ExtensibleRecord(attributes, name, fields), +// (attributes, paramTypes, returnType) => Type.Function(attributes, paramTypes, returnType), +// (attributes, fields) => Type.Record(attributes, fields), +// (attributes, typeName, typeParams) => Type.Reference(attributes, typeName, typeParams), +// (attributes, elements) => Type.Tuple(attributes, elements), +// (attributes, name) => Type.Variable(attributes, name), +// attributes => Type.Unit(attributes) +// ) def foldLeft[Z](zero: Z)(f: (Z, Type[Attributes]) => Z): Z = { @tailrec @@ -63,46 +122,108 @@ sealed trait Type[+Attributes] { self => def foldDownSome[Z](z: Z)(pf: PartialFunction[(Z, Type[Attributes]), Z]): Z = foldDown(z)((z, recursive) => pf.lift(z -> recursive).getOrElse(z)) + + def foldM[F[+_]: AssociativeFlatten: Covariant: IdentityBoth, Z]( + extensibleRecordCase: (Attributes, Name, Chunk[Field[Z]]) => F[Z], + functionCase: (Attributes, Chunk[Z], Z) => F[Z], + recordCase: (Attributes, Chunk[Field[Z]]) => F[Z], + referenceCase: (Attributes, FQName, Chunk[Z]) => F[Z], + tupleCase: (Attributes, Chunk[Z]) => F[Z], + variableCase: (Attributes, Name) => F[Z], + unitCase: Attributes => F[Z] + ): F[Z] = + fold[F[Z]]( + ( + attributes, + name, + fields + ) => fields.forEach(_.forEach(a => a)).flatMap(extensibleRecordCase(attributes, name, _)), + (attributes, paramTypes, returnType) => + for { + paramTypes <- paramTypes.flip + returnType <- returnType + z <- functionCase(attributes, paramTypes, returnType) + } yield z, + (attributes, fields) => fields.forEach(_.forEach(a => a)).flatMap(recordCase(attributes, _)), + (attributes, typeName, typeParams) => typeParams.flip.flatMap(referenceCase(attributes, typeName, _)), + (attributes, elements) => elements.flip.flatMap(tupleCase(attributes, _)), + (attributes, name) => variableCase(attributes, name), + attributes => unitCase(attributes) + ) + + def foldPure[W, S, R, E, Z]( + extensibleRecordCase: (Attributes, Name, Chunk[Field[Z]]) => ZPure[W, S, S, R, E, Z], + functionCase: (Attributes, Chunk[Z], Z) => ZPure[W, S, S, R, E, Z], + recordCase: (Attributes, Chunk[Field[Z]]) => ZPure[W, S, S, R, E, Z], + referenceCase: (Attributes, FQName, Chunk[Z]) => ZPure[W, S, S, R, E, Z], + tupleCase: (Attributes, Chunk[Z]) => ZPure[W, S, S, R, E, Z], + variableCase: (Attributes, Name) => ZPure[W, S, S, R, E, Z], + unitCase: Attributes => ZPure[W, S, S, R, E, Z] + ): ZPure[W, S, S, R, E, Z] = foldM( + extensibleRecordCase, + functionCase, + recordCase, + referenceCase, + tupleCase, + variableCase, + unitCase + ) // - // def foldM[F[+_]: AssociativeFlatten: Covariant: IdentityBoth, Z](f: TypeCase[Z] => F[Z]): F[Z] = - // fold[F[Z]](_.flip.flatMap(f)) - // - // def foldPure[W, S, R, E, Z](f: TypeCase[Z] => ZPure[W, S, S, R, E, Z]): ZPure[W, S, S, R, E, Z] = - // foldM(f) - // - // def transformDown[Annotations0 >: Attributes]( - // f: Type[Annotations0] => Type[Annotations0] - // ): Type[Annotations0] = { - // def loop(recursive: Type[Annotations0]): Type[Attributes] = - // Type(f(recursive).caseValue.map(loop), attributes) - // loop(self) - // } - // - // def foldZIO[R, E, Z](f: TypeCase[Z] => ZIO[R, E, Z]): ZIO[R, E, Z] = - // foldM(f) - // - // def foldRecursive[Z](f: TypeCase[(Type[Attributes], Z)] => Z): Z = - // f(caseValue.map(recursive => recursive -> recursive.foldRecursive(f))) +// def transformDown[Attributes1 >: Attributes]( +// f: Type[Attributes1] => Type[Attributes1] +// ): Type[Attributes1] = +// f(self) match { +// case Type.ExtensibleRecord(attributes, name, fields) => Type.ExtensibleRecord(attributes, name, fields.map(_.map(_.transformDown(f)))) +// case Type.Function(attributes, paramTypes, returnType) => Type.Function(attributes, paramTypes.map(_.transformDown(f)), returnType.transformDown(f)) +// case Type.Record(attributes, fields) => Type.Record(attributes, fields.map(_.map(_.transformDown(f)))) +// case Type.Reference(attributes, typeName, typeParams) => +// Type.Reference(attributes, typeName, typeParams.map(_.transformDown(f))) +// case Type.Tuple(attributes, elementTypes) => Type.Tuple(attributes, elementTypes.map(_.transformDown(f))) +// case Type.Unit(attributes) => Type.Unit(attributes) +// case Type.Variable(attributes, name) => Type.Variable(attributes, name) +// } + + def transform[Attributes1 >: Attributes](f: Type[Attributes1] => Type[Attributes1]): Type[Attributes1] = + fold[Type[Attributes1]]( + (attributes, name, fields) => f(Type.ExtensibleRecord(attributes, name, fields)), + (attributes, paramTypes, returnType) => f(Type.Function(attributes, paramTypes, returnType)), + (attributes, fields) => f(Type.Record(attributes, fields)), + (attributes, typeName, typeParams) => f(Type.Reference(attributes, typeName, typeParams)), + (attributes, elements) => f(Type.Tuple(attributes, elements)), + (attributes, name) => f(Type.Variable(attributes, name)), + attributes => f(Type.Unit(attributes)) + ) + + def rewrite[Attributes1 >: Attributes](pf: PartialFunction[Type[Attributes1], Type[Attributes1]]): Type[Attributes1] = + transform[Attributes1](in => pf.lift(in).getOrElse(in)) // + def foldZIO[R, E, Z]( + extensibleRecordCase: (Attributes, Name, Chunk[Field[Z]]) => ZIO[R, E, Z], + functionCase: (Attributes, Chunk[Z], Z) => ZIO[R, E, Z], + recordCase: (Attributes, Chunk[Field[Z]]) => ZIO[R, E, Z], + referenceCase: (Attributes, FQName, Chunk[Z]) => ZIO[R, E, Z], + tupleCase: (Attributes, Chunk[Z]) => ZIO[R, E, Z], + variableCase: (Attributes, Name) => ZIO[R, E, Z], + unitCase: Attributes => ZIO[R, E, Z] + ): ZIO[R, E, Z] = + foldM(extensibleRecordCase, functionCase, recordCase, referenceCase, tupleCase, variableCase, unitCase) + def foldUp[Z](z: Z)(f: (Z, Type[Attributes]) => Z): Z = f(self.foldLeft(z)((z, recursive) => recursive.foldUp(z)(f)), self) def foldUpSome[Z](z: Z)(pf: PartialFunction[(Z, Type[Attributes]), Z]): Z = foldUp(z)((z, recursive) => pf.lift(z -> recursive).getOrElse(z)) - // TODO: See if we can refactor to be stack safe/ tail recursive - def mapAttributes[Attributes2](f: Attributes => Attributes2): Type[Attributes2] = self match { - case Type.ExtensibleRecord(attributes, name, fields) => - Type.ExtensibleRecord(f(attributes), name, fields.map(_.mapAttributes(f))) - case Type.Function(attributes, paramTypes, returnType) => - Type.Function(f(attributes), paramTypes.map(_.mapAttributes(f)), returnType.mapAttributes(f)) - case Type.Record(attributes, fields) => Type.Record(f(attributes), fields.map(_.mapAttributes(f))) - case Type.Reference(attributes, typeName, typeParams) => - Type.Reference(f(attributes), typeName, typeParams.map(_.mapAttributes(f))) - case Type.Tuple(attributes, elementTypes) => Type.Tuple(f(attributes), elementTypes.map(_.mapAttributes(f))) - case Type.Unit(attributes) => Type.Unit(f(attributes)) - case Type.Variable(attributes, name) => Type.Variable(f(attributes), name) - } + def mapAttributes[Attributes2](f: Attributes => Attributes2): Type[Attributes2] = + fold[Type[Attributes2]]( + (attributes, name, fields) => Type.ExtensibleRecord(f(attributes), name, fields), + (attributes, paramTypes, returnType) => Type.Function(f(attributes), paramTypes, returnType), + (attributes, fields) => Type.Record(f(attributes), fields), + (attributes, typeName, typeParams) => Type.Reference(f(attributes), typeName, typeParams), + (attributes, elements) => Type.Tuple(f(attributes), elements), + (attributes, name) => Type.Variable(f(attributes), name), + attributes => Type.Unit(f(attributes)) + ) def collectReferences: Set[FQName] = foldLeft(Set.empty[FQName]) { case (acc, tpe) => tpe match { diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Definition.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Definition.scala new file mode 100644 index 00000000..d675fe03 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Definition.scala @@ -0,0 +1,40 @@ +package zio.morphir.ir.value + +import zio.Chunk +import zio.morphir.ir.Name +import zio.morphir.ir.Pattern.{AsPattern, WildcardPattern} +import zio.morphir.ir.TypeModule.Type +import zio.morphir.ir.value.Value.Lambda + +final case class Definition[+TA, +VA]( + inputTypes: Chunk[(Name, VA, Type[TA])], + outputType: Type[TA], + body: Value[TA, VA] +) { self => + + def mapAttributes[TB, VB](f: TA => TB, g: VA => VB): Definition[TB, VB] = + Definition( + inputTypes.map { case (n, va, t) => (n, g(va), t.mapAttributes(f)) }, + outputType.mapAttributes(f), + body.mapAttributes(f, g) + ) + + def toValue: Value[TA, VA] = self.inputTypes.toList match { + case Nil => self.body + case (firstArgName, va, _) :: restOfArgs => + val definition = self.copy(inputTypes = Chunk.fromIterable(restOfArgs)) + Lambda( + attributes = va, + argumentPattern = AsPattern(WildcardPattern(va), firstArgName, va), + body = definition.toValue + ) + } + + def toSpecification: Specification[TA] = { + Specification( + inputTypes.map { case (n, _, t) => (n, t) }, + output = self.outputType + ) + } + +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Specification.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Specification.scala new file mode 100644 index 00000000..a4e13458 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Specification.scala @@ -0,0 +1,20 @@ +package zio.morphir.ir.value + +import zio.Chunk +import zio.morphir.ir.Name +import zio.morphir.ir.TypeModule.Type + +final case class Specification[+TA](inputs: Chunk[(Name, Type[TA])], output: Type[TA]) { self => + def map[B](f: TA => B): Specification[B] = + Specification(inputs.map { case (name, tpe) => (name, tpe.mapAttributes(f)) }, output.mapAttributes(f)) +} + +object Specification { + def create[Attributes](inputs: (Name, Type[Attributes])*): Inputs[Attributes] = + new Inputs(() => Chunk.fromIterable(inputs)) + + final class Inputs[Annotations](private val inputs: () => Chunk[(Name, Type[Annotations])]) extends AnyVal { + def apply(output: Type[Annotations]): Specification[Annotations] = + Specification(inputs(), output) + } +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Value.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Value.scala new file mode 100644 index 00000000..7c73d6d2 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/Value.scala @@ -0,0 +1,289 @@ +package zio.morphir.ir.value + +import zio.Chunk +import zio.morphir.ir.{FQName, Name, NativeFunction, Pattern, Literal => Lit} + +import scala.annotation.tailrec + +sealed trait Value[+TA, +VA] { self => + import Value.{List => ListType, Unit => UnitType, _} + + def @@[LowerTA >: TA, UpperTA >: LowerTA, LowerVA >: VA, UpperVA >: LowerVA]( + aspect: ValueAspect[LowerTA, UpperTA, LowerVA, UpperVA] + ): Value[LowerTA, LowerVA] = + aspect(self) + + def attributes: VA + + def mapAttributes[TB, VB](f: TA => TB, g: VA => VB): Value[TB, VB] = self match { + case t @ Apply(_, _, _) => + Apply(g(t.attributes), t.function.mapAttributes(f, g), t.arguments.map(_.mapAttributes(f, g))) + case t @ Constructor(_, _) => Constructor(g(t.attributes), t.name) + case t @ Destructure(_, _, _, _) => + Destructure( + g(t.attributes), + t.pattern.mapAttributes(g), + t.valueToDestruct.mapAttributes(f, g), + t.inValue.mapAttributes(f, g) + ) + case t @ Field(_, _, _) => Field(g(t.attributes), t.target.mapAttributes(f, g), t.name) + case t @ FieldFunction(_, _) => FieldFunction(g(t.attributes), t.name) + case t @ IfThenElse(_, _, _, _) => + IfThenElse( + g(t.attributes), + t.condition.mapAttributes(f, g), + t.thenBranch.mapAttributes(f, g), + t.elseBranch.mapAttributes(f, g) + ) + case t @ Lambda(_, _, _) => Lambda(g(t.attributes), t.argumentPattern.mapAttributes(g), t.body.mapAttributes(f, g)) + case t @ LetDefinition(_, _, _, _) => + LetDefinition(g(t.attributes), t.valueName, t.valueDefinition.mapAttributes(f, g), t.inValue.mapAttributes(f, g)) + case t @ LetRecursion(_, _, _) => + LetRecursion( + g(t.attributes), + t.valueDefinitions.map { case (name, definition) => (name, definition.mapAttributes(f, g)) }, + t.inValue.mapAttributes(f, g) + ) + case t @ ListType(_, _) => ListType(g(t.attributes), t.elements.map(_.mapAttributes(f, g))) + case t @ Literal(_, _) => Literal(g(t.attributes), t.literal) + case t @ NativeApply(_, _, _) => NativeApply(g(t.attributes), t.function, t.arguments.map(_.mapAttributes(f, g))) + case t @ PatternMatch(_, _, _) => + PatternMatch( + g(t.attributes), + t.branchOutOn.mapAttributes(f, g), + t.cases.map { case (p, v) => (p.mapAttributes(g), v.mapAttributes(f, g)) } + ) + case t @ Record(_, _) => Record(g(t.attributes), t.fields.map { case (n, v) => (n, v.mapAttributes(f, g)) }) + case t @ Reference(_, _) => Reference(g(t.attributes), t.name) + case t @ Tuple(_, _) => Tuple(g(t.attributes), t.elements.map(item => item.mapAttributes(f, g))) + case t @ UnitType(_) => UnitType(g(t.attributes)) + case t @ UpdateRecord(_, _, _) => + UpdateRecord( + g(t.attributes), + t.valueToUpdate.mapAttributes(f, g), + t.fieldsToUpdate.map { case (n, v) => (n, v.mapAttributes(f, g)) } + ) + case t @ Variable(_, _) => Variable(g(t.attributes), t.name) + } + + def collectVariables: Set[Name] = foldLeft(Set.empty[Name]) { + case (acc, Variable(_, name)) => acc + name + case (acc, _) => acc + } + + def collectReferences: Set[FQName] = foldLeft(Set.empty[FQName]) { + case (acc, Reference(_, name)) => acc + name + case (acc, _) => acc + } + + def indexedMapValue[VB](initial: Int)(f: (Int, VA) => VB): (Value[TA, VB], Int) = ??? + def rewrite[TA0 >: TA, VA0 >: VA](f: PartialFunction[Value[TA0, VA0], Value[TA0, VA0]]): Value[TA0, VA0] = ??? + + def transform[TB, VB](f: Value[TA, VA] => Value[TB, VB]): Value[TB, VB] = ??? + + def fold[Z]( + ) = ??? + + def foldLeft[Z](initial: Z)(f: (Z, Value[TA, VA]) => Z): Z = { + @tailrec + def loop(stack: List[Value[TA, VA]], acc: Z): Z = + stack match { + case Nil => acc + case (t @ Apply(_, _, _)) :: tail => loop(t.function :: t.arguments.toList ::: tail, f(acc, t)) + case (t @ Constructor(_, _)) :: tail => loop(tail, f(acc, t)) + case (t @ Destructure(_, _, _, _)) :: tail => loop(t.valueToDestruct :: t.inValue :: tail, f(acc, t)) + case (t @ Field(_, _, _)) :: tail => loop(t.target :: tail, f(acc, t)) + case (t @ FieldFunction(_, _)) :: tail => loop(tail, f(acc, t)) + case (t @ IfThenElse(_, _, _, _)) :: tail => + loop(t.condition :: t.thenBranch :: t.elseBranch :: tail, f(acc, t)) + case (t @ Lambda(_, _, _)) :: tail => loop(t.body :: tail, f(acc, t)) + case (t @ LetDefinition(_, _, _, _)) :: tail => loop(t.valueDefinition.body :: t.inValue :: tail, f(acc, t)) + case (t @ LetRecursion(_, _, _)) :: tail => + loop(t.valueDefinitions.map(_._2.body).toList ::: t.inValue :: tail, f(acc, t)) + case (t @ ListType(_, _)) :: tail => loop(t.elements.toList ::: tail, f(acc, t)) + case (t @ Literal(_, _)) :: tail => loop(tail, f(acc, t)) + case (t @ NativeApply(_, _, _)) :: tail => loop(t.arguments.toList ::: tail, f(acc, t)) + case (t @ PatternMatch(_, _, _)) :: tail => loop(t.branchOutOn :: t.cases.map(_._2).toList ::: tail, f(acc, t)) + case (t @ Record(_, _)) :: tail => loop(t.fields.map(_._2).toList ::: tail, f(acc, t)) + case (t @ Reference(_, _)) :: tail => loop(tail, f(acc, t)) + case (t @ Tuple(_, _)) :: tail => loop(t.elements.toList ::: tail, f(acc, t)) + case (t @ UnitType(_)) :: tail => loop(tail, f(acc, t)) + case (t @ UpdateRecord(_, _, _)) :: tail => + loop(t.valueToUpdate :: t.fieldsToUpdate.map(_._2).toList ::: tail, f(acc, t)) + case (t @ Variable(_, _)) :: tail => loop(tail, f(acc, t)) + } + + loop(List(self), initial) + } + +} + +object Value { + + final case class Apply[+TA, +VA](attributes: VA, function: Value[TA, VA], arguments: Chunk[Value[TA, VA]]) + extends Value[TA, VA] + + object Apply { + type Raw = Apply[scala.Unit, scala.Unit] + def apply(function: RawValue, arguments: Chunk[RawValue]): Raw = + Apply((), function, arguments) + } + + final case class Constructor[+VA](attributes: VA, name: FQName) extends Value[Nothing, VA] + object Constructor { + type Raw = Constructor[scala.Unit] + def apply(name: FQName): Raw = Constructor((), name) + } + + final case class Destructure[+TA, +VA]( + attributes: VA, + pattern: Pattern[VA], + valueToDestruct: Value[TA, VA], + inValue: Value[TA, VA] + ) extends Value[TA, VA] + + object Destructure { + type Raw = Destructure[scala.Unit, scala.Unit] + def apply(pattern: Pattern[scala.Unit], valueToDestruct: RawValue, inValue: RawValue): Raw = + Destructure((), pattern, valueToDestruct, inValue) + } + + final case class Field[+TA, +VA](attributes: VA, target: Value[TA, VA], name: Name) extends Value[TA, VA] + + object Field { + type Raw = Field[scala.Unit, scala.Unit] + def apply(target: RawValue, name: Name): Raw = Field((), target, name) + } + final case class FieldFunction[+VA](attributes: VA, name: Name) extends Value[Nothing, VA] + + object FieldFunction { + type Raw = FieldFunction[scala.Unit] + def apply(name: Name): Raw = FieldFunction((), name) + } + + final case class IfThenElse[+TA, +VA]( + attributes: VA, + condition: Value[TA, VA], + thenBranch: Value[TA, VA], + elseBranch: Value[TA, VA] + ) extends Value[TA, VA] + + object IfThenElse { + type Raw = IfThenElse[scala.Unit, scala.Unit] + def apply(condition: RawValue, thenBranch: RawValue, elseBranch: RawValue): Raw = + IfThenElse((), condition, thenBranch, elseBranch) + } + + final case class Lambda[+TA, +VA](attributes: VA, argumentPattern: Pattern[VA], body: Value[TA, VA]) + extends Value[TA, VA] + + object Lambda { + type Raw = Lambda[scala.Unit, scala.Unit] + def apply(argumentPattern: Pattern[scala.Unit], body: RawValue): Raw = Lambda((), argumentPattern, body) + } + + final case class LetDefinition[+TA, +VA]( + attributes: VA, + valueName: Name, + valueDefinition: Definition[TA, VA], + inValue: Value[TA, VA] + ) extends Value[TA, VA] + + object LetDefinition { + type Raw = LetDefinition[scala.Unit, scala.Unit] + def apply(valueName: Name, valueDefinition: Definition[scala.Unit, scala.Unit], inValue: RawValue): Raw = + LetDefinition((), valueName, valueDefinition, inValue) + } + + final case class LetRecursion[+TA, +VA]( + attributes: VA, + valueDefinitions: Map[Name, Definition[TA, VA]], + inValue: Value[TA, VA] + ) extends Value[TA, VA] + + object LetRecursion { + type Raw = LetRecursion[scala.Unit, scala.Unit] + def apply(valueDefinitions: Map[Name, Definition[scala.Unit, scala.Unit]], inValue: RawValue): Raw = + LetRecursion((), valueDefinitions, inValue) + } + + final case class List[+TA, +VA](attributes: VA, elements: Chunk[Value[TA, VA]]) extends Value[TA, VA] + + object List { + type Raw = List[scala.Unit, scala.Unit] + def apply(elements: Chunk[RawValue]): Raw = List((), elements) + } + + final case class Literal[+VA, +A](attributes: VA, literal: Lit[A]) extends Value[Nothing, VA] + + object Literal { + type Raw[+A] = Literal[scala.Unit, A] + def apply[A](literal: Lit[A]): Raw[A] = Literal((), literal) + } + + final case class NativeApply[+TA, +VA](attributes: VA, function: NativeFunction, arguments: Chunk[Value[TA, VA]]) + extends Value[TA, VA] + + object NativeApply { + type Raw = NativeApply[scala.Unit, scala.Unit] + def apply(function: NativeFunction, arguments: Chunk[RawValue]): Raw = + NativeApply((), function, arguments) + } + + final case class PatternMatch[+TA, +VA]( + attributes: VA, + branchOutOn: Value[TA, VA], + cases: Chunk[(Pattern[VA], Value[TA, VA])] + ) extends Value[TA, VA] + + object PatternMatch { + type Raw = PatternMatch[scala.Unit, scala.Unit] + def apply(branchOutOn: RawValue, cases: Chunk[(Pattern[scala.Unit], RawValue)]): Raw = + PatternMatch((), branchOutOn, cases) + } + + final case class Record[+TA, +VA](attributes: VA, fields: Chunk[(Name, Value[TA, VA])]) extends Value[TA, VA] + + object Record { + type Raw = Record[scala.Unit, scala.Unit] + def apply(fields: Chunk[(Name, RawValue)]): Raw = Record((), fields) + } + + final case class Reference[+VA](attributes: VA, name: FQName) extends Value[Nothing, VA] + + object Reference { + type Raw = Reference[scala.Unit] + def apply(name: FQName): Raw = Reference((), name) + } + + final case class Tuple[+TA, +VA](attributes: VA, elements: Chunk[Value[TA, VA]]) extends Value[TA, VA] + + object Tuple { + type Raw = Tuple[scala.Unit, scala.Unit] + def apply(elements: Chunk[RawValue]): Raw = Tuple((), elements) + } + + final case class Unit[+VA](attributes: VA) extends Value[Nothing, VA] + object Unit { + type Raw = Unit[scala.Unit] + def apply(): Raw = Unit(()) + } + + final case class UpdateRecord[+TA, +VA]( + attributes: VA, + valueToUpdate: Value[TA, VA], + fieldsToUpdate: Chunk[(Name, Value[TA, VA])] + ) extends Value[TA, VA] + + object UpdateRecord { + type Raw = UpdateRecord[scala.Unit, scala.Unit] + def apply(valueToUpdate: RawValue, fieldsToUpdate: Chunk[(Name, RawValue)]): Raw = + UpdateRecord((), valueToUpdate, fieldsToUpdate) + } + + final case class Variable[+VA](attributes: VA, name: Name) extends Value[Nothing, VA] + object Variable { + type Raw = Variable[scala.Unit] + def apply(name: Name): Raw = Variable((), name) + } +} diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/ValueAspect.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/ValueAspect.scala new file mode 100644 index 00000000..b61a40c0 --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/ValueAspect.scala @@ -0,0 +1,25 @@ +package zio.morphir.ir.value + +import zio.morphir.ir.UType + +trait ValueAspect[+LowerTA, -UpperTA, +LowerVA, -UpperVA] { + def apply[TA >: LowerTA <: UpperTA, VA >: LowerVA <: UpperVA](value: Value[TA, VA]): Value[TA, VA] +} + +object ValueAspect { +// type ValueAspectPoly = ValueAspect[Nothing, Any, Nothing, Any] +// type ValueAspectAtLeastVA[VA] = ValueAspect[Nothing, Any, Nothing, VA] + + def typed(tpe: UType): ValueAspect[Nothing, Any, UType, Any] = + new ValueAspect[Nothing, Any, UType, Any] { + override def apply[TA, VA >: UType](value: Value[TA, VA]): Value[TA, VA] = { + val _ = tpe + ??? + } + } + + // ZIO[Any, Nothing, Int] => ZIO[Clock, Nothing, Int] + // ZIO[Unit, Nothing, Int] => ZIO[String, Nothing, Int] // can't do it with this aspect encoding +} + +// Variable((), Name(a)):RawValue (Value[Unit,Unit]) @@ typed(Reference("Int")) | => TypedValue (Value[Unit, Type[Unit]]) diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/ValueModule.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/ValueModule.scala new file mode 100644 index 00000000..e9110aae --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/ValueModule.scala @@ -0,0 +1,29 @@ +package zio.morphir.ir.value + +import zio.morphir.ir.{FQName, Name} + +trait ValueModule { + + final type RawValue = zio.morphir.ir.value.RawValue + val RawValue: zio.morphir.ir.value.RawValue.type = zio.morphir.ir.value.RawValue + + final type TypedValue = zio.morphir.ir.value.TypedValue + val TypedValue: zio.morphir.ir.value.TypedValue.type = zio.morphir.ir.value.TypedValue + + def toRawValue[TA, VA](value: Value[TA, VA]): RawValue = + value.mapAttributes(_ => (), _ => ()) + + final def collectVariables[TA, VA](value: Value[TA, VA]): Set[Name] = value.collectVariables + + final def collectReferences[TA, VA](value: Value[TA, VA]): Set[FQName] = value.collectReferences + + def definitionToSpecification[TA, VA](definition: Definition[TA, VA]): Specification[TA] = + definition.toSpecification + + def definitionToValue[TA, VA](definition: Definition[TA, VA]): Value[TA, VA] = + definition.toValue + + def valuesAttribute[TA, VA](value: Value[TA, VA]): VA = value.attributes +} + +object ValueModule extends ValueModule diff --git a/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/package.scala b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/package.scala new file mode 100644 index 00000000..506e8e3c --- /dev/null +++ b/morphir-ir/shared/src/main/scala/zio/morphir/ir/value/package.scala @@ -0,0 +1,14 @@ +package zio.morphir.ir + +package object value { + + type RawValue = Value[Unit, Unit] + val RawValue: Value.type = Value + + type TypedValue = Value[Unit, UType] + val TypedValue: Value.type = Value + + type USpecification = Value[Unit, Unit] + val USpecification: Specification.type = Specification + +} diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/examples/TypeInferenceExample.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/examples/TypeInferenceExample.scala new file mode 100644 index 00000000..67804b6e --- /dev/null +++ b/morphir-ir/shared/src/test/scala/zio/morphir/ir/examples/TypeInferenceExample.scala @@ -0,0 +1,114 @@ +package zio.morphir.ir.examples + +import zio.morphir.ir.{FQName, UType} +import zio.morphir.ir.value.{RawValue, Value} + +object TypeInferenceExample { + + import zio.Chunk + import Value.{Unit => UnitType} + import zio.morphir.ir.Name + import zio.morphir.ir.{Literal => Lit} + + implicit final class AddTypeSyntax[TA](private val self: Value[TA, Unit]) extends AnyVal { + def setType[TA](uType: UType): Value[TA, UType] = ??? + } + + import zio.morphir.ir.TypeModule.Type + + val intType: UType = Type.Reference((), FQName.fromString("Package:Module:Int"), Chunk.empty) + val stringType: UType = Type.Reference((), FQName.fromString("Package:Module:String"), Chunk.empty) + val personType: UType = ??? + + val u: RawValue = UnitType() + u.setType(intType) + + val myRecord = Value.Record( + Chunk( + Name.fromString("name") -> Value.Literal(Lit.string("adam")), + Name.fromString("age") -> Value.Literal(Lit.int(32)) + ) + ) + + val myTypedRecord = Value + .Record( + personType, + Chunk( + Name.fromString("name") -> Value.Literal(stringType, Lit.string("adam")), + Name.fromString("age") -> Value.Literal(intType, Lit.int(32)) + ) + ) + .mapAttributes(identity(_), tpe => tpe ?? "Docs") + +// type EmptyCaps[A] = Unit +// type X = ZEnvironmentSubset[Any] + +// def inferTypes[TA](value: Value[TA,Any]): Value[TA, UType] = ??? + + trait AttributeTransformer[Attributes] { + def apply(attributes: UType): Attributes + } + + final case class AttributeApplier(initialAttributes: UType) + +// trait Value { +// def @@(attributeTransformer: AttributeTransformer): Value +// } + // myRecord.inferTypes + + final case class Annotations(annotations: Map[String, Any]) + + trait ZIO[-R, +E, +A] + +} + +object TreeDefinition { + import zio.Tag + import zio.morphir.ir.ZEnvironmentSubset + + final case class Tree[Capabilities[_], +Value, +Annotations]( + value: Value, + annotations: ZEnvironmentSubset[Capabilities, Annotations] + ) { self => + def zipWith[Value2, Value3, Annotations1 >: Annotations](that: Tree[Capabilities, Value2, Annotations1])( + f: (Value, Value2) => Value3 + )(implicit tag: Tag[Annotations1]): Tree[Capabilities, Value3, Annotations1] = + Tree(f(self.value, that.value), self.annotations.union[Annotations1](that.annotations)) + } + + // val dummyTree:Tree[] +} + +object Default { + import zio._ + + val defaultEnvironment: ZEnvironment[Clock with Console with Random with System] = ZEnvironment.default + + val test: ZEnvironment[Clock with Console with Random] = defaultEnvironment + + val test2: ZEnvironment[Any] = defaultEnvironment +} + +object Serialization { + import TreeDefinition._ + + trait Json + + trait JsonCodec[A] { + def encode(a: A): Json + def decode(json: Json): A + } + + def encodeTree[Value, Annotations](tree: Tree[JsonCodec, Value, Annotations]): Json = + ??? +} + +object Usage { + import Serialization._ + trait MyAnnotation + + object MyAnnotation { + implicit val MyAnnotationCodec: JsonCodec[MyAnnotation] = ??? + } + +} diff --git a/morphir-ir/shared/src/test/scala/zio/morphir/ir/value/ValueSpec.scala b/morphir-ir/shared/src/test/scala/zio/morphir/ir/value/ValueSpec.scala new file mode 100644 index 00000000..cd8d2387 --- /dev/null +++ b/morphir-ir/shared/src/test/scala/zio/morphir/ir/value/ValueSpec.scala @@ -0,0 +1,43 @@ +package zio.morphir.ir.value + +import zio.Chunk +import zio.morphir.ir.{Name, NativeFunction} +import zio.morphir.ir.Source.Location +import zio.morphir.testing.MorphirBaseSpec +import zio.test._ +import zio.test.Assertion.equalTo +import zio.prelude._ + +object ValueSpec extends MorphirBaseSpec { + def spec = suite("ValueSpec") { + suite("NativeApply")( + test("foldLeft should work as expected with a native function application") { + val a = Value.Variable(Name("a")) + val b = Value.Variable(Name("b")) + val sut = Value.NativeApply(NativeFunction.Addition, Chunk(a, b)) + val actual = sut.foldLeft[(Int, List[RawValue])](0 -> List.empty[RawValue]) { + // Strip out the NativeApply node to be sure it is visited + case ((ct, items), Value.NativeApply(attributes, function, args)) => + (ct + 1, Value.NativeApply(attributes, function, args) :: items) + case ((ct, items), _) => (ct + 1, items) + } + assert(actual)(equalTo((3, List(sut)))) + } + ) + suite("Unit")( + test("foldLeft should work as expected with a unit value") { + val givenLocation = Location(1, 42) + val actual = Value.Unit(givenLocation) + assertTrue( + actual.foldLeft(Location.home)((acc, v) => acc <> v.attributes) == givenLocation + ) + } + ) + suite("Variable")( + test("foldLeft should work as expected a variable value") { + val actual = Value.Variable(Name.fromString("foo")) + assertTrue( + actual.foldLeft(List.empty[RawValue])((acc, v) => v :: acc) == List(actual) + ) + } + ) + } +}