Last active
August 23, 2018 17:13
-
-
Save throughnothing/fe524cc10a1ceaf8fcec7036a38380bb to your computer and use it in GitHub Desktop.
Phantom Types in Java for Number Constraints
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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