type-formula allows you to write lazy formulas in a type safe mode and calculate a value when it needed.
Stable version, dependencies and repository are coming soon...
typed-formula provides functionality to build formulas for numeric calculations on top of Integers, Longs and Double wrapped into Numbers.
Let's define simple formulas to calculate area and a volume of a figure. Firstly define measurements.
import karazinscalausersgroup.typed.formula.Value
import karazinscalausersgroup.typed.formula.numbers.{Number, Value}
import karazinscalausersgroup.typed.formula.numbers.operations._
scala> val length = Value[Number](1)
length: Value[Number] = Expression(IntNumber(1))
scala> val width = Value[Number](3)
width: Value[numbers.Number] = Expression(IntNumber(3))
scala> val height = Value[Number](2.0)
height: Value[numbers.Number] = Expression(DoubleNumber(2.0))
Now we can easily define formulas
scala> val area = length * width
area: Value[Number] * Value[Number] = (Expression(IntNumber(1)) * Expression(IntNumber(3)))
scala> val volume = area * height
volume: Value[Number] * Value[Number] * Value[Number] = ((Expression(IntNumber(1)) * Expression(IntNumber(3))) * Expression(DoubleNumber(2.0)))
and calculate values
scala> area.v
res0: Number = IntNumber(3)
scala> volume.v
res1: Number = DoubleNumber(6.0)
Let's rewrite the example with custom classes for formula terms
import karazinscalausersgroup.typed.formula.Value
import karazinscalausersgroup.typed.formula.numbers.{Number, Value}
import karazinscalausersgroup.typed.formula.numbers.operations._
case class Length(value: Int) extends Value[Number] {
def v = Number(value)
}
case class Width(value: Int) extends Value[Number] {
def v = Number(value)
}
case class Height(value: Double) extends Value[Number] {
def v = Number(value)
}
scala> val length = Length(1)
length: Length = Expression(IntNumber(1))
scala> val width = Width(3)
width: Width = Expression(IntNumber(3))
scala> val height = Height(2.0)
height: Height = Expression(DoubleNumber(2.0))
Now we can define formulas with custom terms
scala> val area = length * width
area: Length * Width = (Expression(IntNumber(1)) * Expression(IntNumber(3)))
scala> val volume = area * height
volume: Length * Width * Height = ((Expression(IntNumber(1)) * Expression(IntNumber(3))) * Expression(DoubleNumber(2.0)))
and calculate values
scala> area.v
res0: Number = IntNumber(3)
scala> volume.v
res1: Number = DoubleNumber(6.0)
In current version only operations for Numbers are supported.
Let's define two variables
import karazinscalausersgroup.typed.formula.Value
import karazinscalausersgroup.typed.formula.numbers.{Number, Value}
import karazinscalausersgroup.typed.formula.numbers.operations._
scala> val a = Value[Number](3)
a: Value[Number] = Expression(IntNumber(3))
scala> val b = Value[Number](5)
b: Value[Number] = Expression(IntNumber(5))
typed-formula supports following linear operation:
scala> a + b
res1: Value[Number] + Value[Number] = (Expression(IntNumber(3)) + Expression(IntNumber(5)))
scala> a - b
res2: Value[Number] - Value[Number] = (Expression(IntNumber(3)) - Expression(IntNumber(5)))
Unlike unary_
definitions for methods, it is impossible to overload types. That's why ~
(tilde) represents unary minus on type level. However it's possible to use -
as unary minus with values.
scala> -a
res3: ~[Value[Number]] = -Expression(IntNumber(3)))
scala> a * b
res4: Value[Number] * Value[Number] = (Expression(IntNumber(3)) * Expression(IntNumber(5)))
scala> a / b
res9: Value[numbers.Number] / Value[numbers.Number] = (Expression(IntNumber(3)) / Expression(IntNumber(5)))
Let's get to the example with measurements
import karazinscalausersgroup.typed.formula.numbers.Number.Zero
import karazinscalausersgroup.typed.formula.numbers.builder._
import karazinscalausersgroup.typed.formula.numbers.operations._
import karazinscalausersgroup.typed.formula.numbers.{Number, Val
case class Length(value: Int) extends Value[Number] {
def v = Number(value)
}
case class Width(value: Int) extends Value[Number] {
def v = Number(value)
}
case class Height(value: Double) extends Value[Number] {
def v = Number(value)
}
scala> val length = Length(1)
length: Length = Expression(IntNumber(1))
scala> val width = Width(3)
width: Width = Expression(IntNumber(3))
scala> val height = Height(2.0)
height: Height = Expression(DoubleNumber(2.0))
Let's pack all measurements into a list.
scala> val measurements = length :: width :: height :: Nil
measurements: List[Value[numbers.Number] with Product with Serializable{def v: Product with Serializable with Number{def v: AnyVal; type N >: Double with Int <: AnyVal}}] = List(Expression(IntNumber(1)), Expression(IntNumber(3)), Expression(DoubleNumber(2.0)))
Now we can build formulas on the fly. Take into account that build
works only for lists of Value
s.
scala> val areaOpt = build[Length * Width](measurements)
areaOpt: Option[Length * Width] = Some((Expression(IntNumber(1)) * Expression(IntNumber(3))))
scala> val volumeOpt = build[Length * Width * Height](measurements)
volumeOpt: Option[Length * Width * Height] = Some(((Expression(IntNumber(1)) * Expression(IntNumber(3))) * Expression(DoubleNumber(2.0))))
If it's possible to build formula build
returns Some
formula otherwise it returns None
. For some reasons it may be preferable to fail on compile time if it is impossible to build a formula, but this soft check is necessary for Σ
operation.
Suppose there is a series of measurements. We can calculate additive characteristics for this series.
import karazinscalausersgroup.typed.formula.Value
import karazinscalausersgroup.typed.formula.numbers.{Number, Value}
import karazinscalausersgroup.typed.formula.numbers.operations._
case class Length(value: Int) extends Value[Number] {
def v = Number(value)
}
case class Width(value: Int) extends Value[Number] {
def v = Number(value)
}
case class Height(value: Double) extends Value[Number] {
def v = Number(value)
}
Define a foldable entity for collection of measurements
case class Measurements(expressions: List[Value[Number]]) extends Foldable[Number, Value[Number]] {
lazy val op: (Expression[Number], Value[Number]) => Expression[Number] = (acc, value) => acc + value
lazy val initial = Value(Zero)
}
scala> val measurements1 = Measurements(Length(1) :: Width(3) :: Height(2.0) :: Nil)
measurements1: Measurements = Expression(DoubleNumber(6.0))
scala> val measurements2 = Measurements(Length(4) :: Width(6) :: Height(5.0) :: Nil)
measurements2: Measurements = Expression(DoubleNumber(15.0))
scala> val measurements3 = Measurements(Length(7) :: Width(9) :: Height(8.0) :: Nil)
measurements3: Measurements = Expression(DoubleNumber(24.0))
And define an implicit
foldable entity for series of measurements
case class Series(expressions: List[Measurements]) extends Foldable[Number, Measurements] {
lazy val op: (Expression[Number], Measurements) => Expression[Number] = (acc, value) => acc + value
lazy val initial = Value(Zero)
}
scala> implicit val series: Series = Series(measurements1 :: measurements2 :: measurements3 :: Nil)
series: Series = Expression(DoubleNumber(45.0))
It is also needed to add implicit
default entities. This option will be described later.
scala> implicit val defaults: List[Value[Number]] = Nil
defaults: List[Value[Number]] = List()
Now we can calculate some additive characteristics. The sum of all lengths:
scala> Σ[Length].v
res0: IntNumber = IntNumber(12)
The sum of all volumes:
scala> Σ[Length * Width * Height].v
res1: DoubleNumber = DoubleNumber(630.0)
Let's define 4th dimension for time:
case class Time(value: Long) extends Value[Number] {
def v = Number(value)
}
If we try to build formula for a volume of hypercube we get 0 because there is no Time
in the measurements and we can't build the formula
scala> Σ[Length * Width * Height * Time].v
res3: IntNumber = IntNumber(0)
To resolve this issue we can add some Time
to defaults
. Keep in mind that the value of Time
will be added to each measurements. You can't provide several values for Time
, only the first value of Time
from defaults
will be used.
scala> implicit val defaults: List[Value[Number]] = Time(10) :: Nil
res4: List[Value[Number]] = List(Time(10))
scala> Σ[Length * Width * Height * Time].v
res5: DoubleNumber = DoubleNumber(6300.0)
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.