Skip to content

Instantly share code, notes, and snippets.

@throughnothing
Last active August 23, 2018 17:13
Show Gist options
  • Save throughnothing/fe524cc10a1ceaf8fcec7036a38380bb to your computer and use it in GitHub Desktop.
Save throughnothing/fe524cc10a1ceaf8fcec7036a38380bb to your computer and use it in GitHub Desktop.
Phantom Types in Java for Number Constraints
import com.sun.istack.internal.NotNull;
import static io.vavr.API.*;
import io.vavr.collection.List;
import io.vavr.control.Option;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Value;
import java.math.BigDecimal;
interface NumConstraint { }
class Positive implements NumConstraint { Positive(){} }
class Negative implements NumConstraint { Negative(){} }
class NonZero implements NumConstraint { NonZero(){} }
class Natural implements NumConstraint { Natural(){} }
class Any implements NumConstraint { Any(){} }
@Value
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Money<A extends NumConstraint> {
private static final BigDecimal MICROS_FACTOR = BigDecimal.valueOf(1_000_000);
@NotNull
private BigDecimal amountMicros;
/* Constructors */
public static Option<Money<Any>> of(Any c, BigDecimal amount) {
return Money.ofAny(amount);
}
public static Option<Money<Positive>> of(Positive c, BigDecimal amount) {
return Money.ofPos(amount);
}
public static Option<Money<Negative>> of(Negative c, BigDecimal amount) {
return Money.ofNeg(amount);
}
public static Option<Money<NonZero>> of(NonZero c, BigDecimal amount) {
return Money.ofNonZero(amount);
}
public static Option<Money<Natural>> of(Natural c, BigDecimal amount) {
return Money.ofNat(amount);
}
public static Option<Money<Any>> ofAny(BigDecimal amount) {
return Option.of(amount).map(Money<Any>::new);
}
public static Option<Money<Positive>> ofPos(BigDecimal amount) {
return Option.of(amount)
.filter(x -> x.compareTo(BigDecimal.ZERO) == 1)
.map(Money<Positive>::new);
}
public static Option<Money<Negative>> ofNeg(BigDecimal amount) {
return Option.of(amount)
.filter(x -> x.compareTo(BigDecimal.ZERO) == -1)
.map(Money<Negative>::new);
}
public static Option<Money<Natural>> ofNat(BigDecimal amount) {
return Option.of(amount)
.filter(x -> List.of(0, 1).contains(x.compareTo(BigDecimal.ZERO)))
.map(Money<Natural>::new);
}
public static Option<Money<NonZero>> ofNonZero(BigDecimal amount) {
return Option.of(amount)
.filter(x -> x.compareTo(BigDecimal.ZERO) != 0)
.map(Money<NonZero>::new);
}
/* Conversion Operations */
public Option<Money<Any>> toAny() {
return Money.ofAny(amountMicros);
}
public Option<Money<Positive>> toPositive() {
return Money.ofPos(amountMicros);
}
public Option<Money<Negative>> toNegative() {
return Money.ofNeg(amountMicros);
}
public Option<Money<Natural>> toNatural() {
return Money.ofNat(amountMicros);
}
public Option<Money<NonZero>> toNonZero() {
return Money.ofNonZero(amountMicros);
}
/* Math Operations */
/* Divide */
/* Any / X */
public static Money<Any> divide(Any d1, NonZero d2, Money<Any> dividend, Money<NonZero> divisor) {
return Money.ofAny(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<Any> divide(Any d1, Positive d2, Money<Any> dividend, Money<Positive> divisor) {
return Money.ofAny(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<Any> divide(Any d1, Negative d2, Money<Any> dividend, Money<Negative> divisor) {
return Money.ofAny(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
/* Positive / X */
public static Money<Positive> divide(Positive d1, Positive d2, Money<Positive> dividend, Money<Positive> divisor) {
return Money.ofPos(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<Positive> divide(Positive d1, NonZero d2, Money<Positive> dividend, Money<NonZero> divisor) {
return Money.ofPos(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<Negative> divide(Positive d1, Negative d2, Money<Positive> dividend, Money<Negative> divisor) {
return Money.ofNeg(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
/* Natural / X */
public static Money<Natural> divide(Natural d1, Positive d2, Money<Natural> dividend, Money<Positive> divisor) {
return Money.ofNat(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<Any> divide(Natural d1, NonZero d2, Money<Natural> dividend, Money<NonZero> divisor) {
return Money.ofAny(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<Any> divide(Natural d1, Negative d2, Money<Natural> dividend, Money<Negative> divisor) {
return Money.ofAny(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
/* Negative / X */
public static Money<Negative> divide(Negative d1, Positive d2, Money<Negative> dividend, Money<Positive> divisor) {
return Money.ofNeg(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<Positive> divide(Negative d1, Negative d2, Money<Negative> dividend, Money<Negative> divisor) {
return Money.ofPos(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<NonZero> divide(Negative d1, NonZero d2, Money<Negative> dividend, Money<NonZero> divisor) {
return Money.ofNonZero(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
/* NonZero / X */
public static Money<NonZero> divide(NonZero d1, Positive d2, Money<NonZero> dividend, Money<Positive> divisor) {
return Money.ofNonZero(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<NonZero> divide(NonZero d1, Negative d2, Money<NonZero> dividend, Money<Negative> divisor) {
return Money.ofNonZero(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
public static Money<NonZero> divide(NonZero d1, NonZero d2, Money<NonZero> dividend, Money<NonZero> divisor) {
return Money.ofNonZero(dividend.amountMicros.divide(divisor.amountMicros)).get();
}
/* Example Usage */
public static void main(String[] args) {
System.out.println(BigDecimal.valueOf(-100).divide(BigDecimal.valueOf(-100)));
List.of(-200, 1, 0, 15)
.map(x -> {
System.out.println(x + ":");
System.out.println("Money<Any>: " + Money.ofAny(BigDecimal.valueOf(x)));
System.out.println("Money<Positive>: " + Money.ofPos(BigDecimal.valueOf(x)));
System.out.println("Money<Negative>: " + Money.ofNeg(BigDecimal.valueOf(x)));
System.out.println("Money<Natural>: " + Money.ofNat(BigDecimal.valueOf(x)));
System.out.println("Money<NonZero>: " + Money.ofNonZero(BigDecimal.valueOf(x)));
return x;
});
System.out.println("------------- Neg / Neg");
System.out.println(
For(Money.of(new Negative(), BigDecimal.valueOf(-100)),
Money.of(new Negative(), BigDecimal.valueOf(-100)))
.yield((a, b) -> Money.divide(new Negative(), new Negative(), a, b))
.toOption()
);
System.out.println("------------- Neg / NonZero");
System.out.println(
For(Money.of(new Negative(), BigDecimal.valueOf(-100)),
Money.of(new NonZero(), BigDecimal.valueOf(-100)))
.yield((a, b) -> Money.divide(new Negative(), new NonZero(), a, b))
.toOption()
);
System.out.println("------------- NonZero / NonZero");
System.out.println(
For(Money.of(new NonZero(), BigDecimal.valueOf(-100)),
Money.of(new NonZero(), BigDecimal.valueOf(-100)))
.yield((a, b) -> Money.divide(new NonZero(), new NonZero(), a, b))
.toOption()
);
System.out.println("------------- NonZero -> toNegative() / NonZero");
System.out.println(
For(Money.of(new NonZero(), BigDecimal.valueOf(-100)).flatMap(Money::toNegative),
Money.of(new NonZero(), BigDecimal.valueOf(-100)))
.yield((a, b) -> Money.divide(new Negative(), new NonZero(), a, b))
.toOption()
);
System.out.println("------------- Invalid(Pos) / NonZero");
System.out.println(
For(Money.of(new Positive(), BigDecimal.valueOf(-100)),
Money.of(new NonZero(), BigDecimal.valueOf(-100)))
.yield((a, b) -> Money.divide(new Positive(), new NonZero(), a, b))
.toOption()
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment