Skip to content

Commit d22dbd9

Browse files
committed
[#1] Split project into individual files
1 parent e9cbaba commit d22dbd9

49 files changed

Lines changed: 2217 additions & 1898 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/com/garciat/typeclasses/Main.java

Lines changed: 0 additions & 1886 deletions
This file was deleted.
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package com.garciat.typeclasses;
2+
3+
import static com.garciat.typeclasses.api.TypeClass.Witness.Overlap.OVERLAPPABLE;
4+
import static com.garciat.typeclasses.api.TypeClass.Witness.Overlap.OVERLAPPING;
5+
import static java.lang.reflect.AccessFlag.PUBLIC;
6+
import static java.lang.reflect.AccessFlag.STATIC;
7+
8+
import com.garciat.typeclasses.api.Ctx;
9+
import com.garciat.typeclasses.api.Ty;
10+
import com.garciat.typeclasses.api.TypeClass;
11+
import com.garciat.typeclasses.impl.FuncType;
12+
import com.garciat.typeclasses.impl.ParsedType;
13+
import com.garciat.typeclasses.impl.Unification;
14+
import com.garciat.typeclasses.impl.utils.Lists;
15+
import com.garciat.typeclasses.impl.utils.ZeroOneMore;
16+
import com.garciat.typeclasses.types.Either;
17+
import com.garciat.typeclasses.types.Maybe;
18+
import java.lang.reflect.Method;
19+
import java.util.Arrays;
20+
import java.util.List;
21+
import java.util.stream.Collectors;
22+
import java.util.stream.Stream;
23+
24+
public class TypeClasses {
25+
public static <T> T witness(Ty<T> ty, Ctx<?>... context) {
26+
return switch (summon(ParsedType.parse(ty.type()), parseContext(context))) {
27+
case Either.Left<SummonError, Object>(SummonError error) ->
28+
throw new WitnessResolutionException(error);
29+
case Either.Right<SummonError, Object>(Object instance) -> {
30+
@SuppressWarnings("unchecked")
31+
T typedInstance = (T) instance;
32+
yield typedInstance;
33+
}
34+
};
35+
}
36+
37+
private static List<ContextInstance> parseContext(Ctx<?>[] context) {
38+
return Arrays.stream(context)
39+
.map(ctx -> new ContextInstance(ctx.instance(), ParsedType.parse(ctx.type())))
40+
.toList();
41+
}
42+
43+
public static class WitnessResolutionException extends RuntimeException {
44+
private WitnessResolutionException(SummonError error) {
45+
super(error.format());
46+
}
47+
}
48+
49+
private sealed interface SummonError {
50+
record NotFound(ParsedType target) implements SummonError {}
51+
52+
record Ambiguous(ParsedType target, List<Candidate> candidates) implements SummonError {}
53+
54+
record Nested(ParsedType target, SummonError cause) implements SummonError {}
55+
56+
default String format() {
57+
return switch (this) {
58+
case NotFound(ParsedType target) -> "No witness found for type: " + target.format();
59+
case Ambiguous(ParsedType target, List<Candidate> candidates) ->
60+
"Ambiguous witnesses found for type: "
61+
+ target.format()
62+
+ "\nCandidates:\n"
63+
+ candidates.stream()
64+
.map(c -> c.rule().toString())
65+
.collect(Collectors.joining("\n"))
66+
.indent(2);
67+
case Nested(ParsedType target, SummonError cause) ->
68+
"While summoning witness for type: "
69+
+ target.format()
70+
+ "\nCaused by: "
71+
+ cause.format().indent(2);
72+
};
73+
}
74+
}
75+
76+
private static Either<SummonError, Object> summon(
77+
ParsedType target, List<ContextInstance> context) {
78+
return switch (ZeroOneMore.of(findCandidates(target, context))) {
79+
case ZeroOneMore.One<Candidate>(Candidate(var rule, var requirements)) ->
80+
summonAll(requirements, context)
81+
.map(rule::instantiate)
82+
.mapLeft(error -> new SummonError.Nested(target, error));
83+
case ZeroOneMore.Zero<Candidate>() -> Either.left(new SummonError.NotFound(target));
84+
case ZeroOneMore.More<Candidate>(var candidates) ->
85+
Either.left(new SummonError.Ambiguous(target, candidates));
86+
};
87+
}
88+
89+
private static Either<SummonError, List<Object>> summonAll(
90+
List<ParsedType> targets, List<ContextInstance> context) {
91+
return Either.traverse(targets, target -> summon(target, context));
92+
}
93+
94+
private record Candidate(WitnessRule rule, List<ParsedType> requirements) {}
95+
96+
private static List<Candidate> findCandidates(ParsedType target, List<ContextInstance> context) {
97+
return Stream.<WitnessRule>concat(
98+
context.stream(), reduceOverlapping(findRules(target)).stream())
99+
.flatMap(
100+
rule ->
101+
rule
102+
.tryMatch(target)
103+
.map(requirements -> new Candidate(rule, requirements))
104+
.stream())
105+
.toList();
106+
}
107+
108+
/**
109+
* @implSpec <a href=
110+
* "https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/instances.html#overlapping-instances">6.8.8.5.
111+
* Overlapping instances</a>
112+
*/
113+
private static List<InstanceConstructor> reduceOverlapping(List<InstanceConstructor> candidates) {
114+
return candidates.stream()
115+
.filter(
116+
iX ->
117+
candidates.stream().filter(iY -> iX != iY).noneMatch(iY -> isOverlappedBy(iX, iY)))
118+
.toList();
119+
}
120+
121+
private static boolean isOverlappedBy(InstanceConstructor iX, InstanceConstructor iY) {
122+
return (iX.overlap() == OVERLAPPABLE || iY.overlap() == OVERLAPPING)
123+
&& isSubstitutionInstance(iX, iY)
124+
&& !isSubstitutionInstance(iY, iX);
125+
}
126+
127+
private static boolean isSubstitutionInstance(
128+
InstanceConstructor base, InstanceConstructor reference) {
129+
return Unification.unify(base.func().returnType(), reference.func().returnType())
130+
.fold(() -> false, map -> !map.isEmpty());
131+
}
132+
133+
private static List<InstanceConstructor> findRules(ParsedType target) {
134+
return switch (target) {
135+
case ParsedType.App(var fun, var arg) -> Lists.concat(findRules(fun), findRules(arg));
136+
case ParsedType.Const(var java) -> rulesOf(java);
137+
case ParsedType.Var(var java) -> List.of();
138+
case ParsedType.ArrayOf(var elem) -> List.of();
139+
case ParsedType.Primitive(var java) -> List.of();
140+
};
141+
}
142+
143+
private static List<InstanceConstructor> rulesOf(Class<?> cls) {
144+
return Arrays.stream(cls.getDeclaredMethods())
145+
.filter(TypeClasses::isWitnessMethod)
146+
.map(FuncType::parse)
147+
.map(InstanceConstructor::new)
148+
.toList();
149+
}
150+
151+
private static boolean isWitnessMethod(Method m) {
152+
return m.accessFlags().contains(PUBLIC)
153+
&& m.accessFlags().contains(STATIC)
154+
&& m.isAnnotationPresent(TypeClass.Witness.class);
155+
}
156+
157+
private sealed interface WitnessRule {
158+
Maybe<List<ParsedType>> tryMatch(ParsedType target);
159+
160+
Object instantiate(List<Object> dependencies);
161+
}
162+
163+
private record ContextInstance(Object instance, ParsedType type) implements WitnessRule {
164+
@Override
165+
public Maybe<List<ParsedType>> tryMatch(ParsedType target) {
166+
return target.equals(type) ? Maybe.just(List.of()) : Maybe.nothing();
167+
}
168+
169+
@Override
170+
public Object instantiate(List<Object> dependencies) {
171+
return instance;
172+
}
173+
174+
@Override
175+
public String toString() {
176+
return "context instance: " + type.format();
177+
}
178+
}
179+
180+
private record InstanceConstructor(FuncType func) implements WitnessRule {
181+
public TypeClass.Witness.Overlap overlap() {
182+
return func.java().getAnnotation(TypeClass.Witness.class).overlap();
183+
}
184+
185+
@Override
186+
public Maybe<List<ParsedType>> tryMatch(ParsedType target) {
187+
return Unification.unify(func.returnType(), target)
188+
.map(map -> Unification.substituteAll(map, func.paramTypes()));
189+
}
190+
191+
@Override
192+
public Object instantiate(List<Object> dependencies) {
193+
try {
194+
return func.java().invoke(null, dependencies.toArray());
195+
} catch (Exception e) {
196+
throw new RuntimeException(e);
197+
}
198+
}
199+
200+
@Override
201+
public String toString() {
202+
return func.format();
203+
}
204+
}
205+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.garciat.typeclasses.api;
2+
3+
import static java.util.Objects.requireNonNull;
4+
5+
import java.lang.reflect.ParameterizedType;
6+
import java.lang.reflect.Type;
7+
8+
public abstract class Ctx<T> {
9+
private final T instance;
10+
11+
public Ctx(T instance) {
12+
this.instance = instance;
13+
}
14+
15+
public T instance() {
16+
return instance;
17+
}
18+
19+
public Type type() {
20+
return requireNonNull(
21+
((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
22+
}
23+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.garciat.typeclasses.api;
2+
3+
import static java.util.Objects.requireNonNull;
4+
5+
import java.lang.reflect.ParameterizedType;
6+
import java.lang.reflect.Type;
7+
8+
public interface Ty<T> {
9+
default Type type() {
10+
return requireNonNull(
11+
((ParameterizedType) getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0]);
12+
}
13+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.garciat.typeclasses.api;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.TYPE)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface TypeClass {
11+
@Target(ElementType.METHOD)
12+
@Retention(RetentionPolicy.RUNTIME)
13+
@interface Witness {
14+
Overlap overlap() default Overlap.NONE;
15+
16+
enum Overlap {
17+
NONE,
18+
OVERLAPPING,
19+
OVERLAPPABLE
20+
}
21+
}
22+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.garciat.typeclasses.api.hkt;
2+
3+
/** This is how we get basic kind checking in Java */
4+
public interface Kind<K extends Kind.Base> {
5+
sealed interface Base {}
6+
7+
/** KStar = * */
8+
final class KStar implements Base {}
9+
10+
/** KArr k = * -> k */
11+
final class KArr<K extends Base> implements Base {}
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.garciat.typeclasses.api.hkt;
2+
3+
import com.garciat.typeclasses.api.hkt.Kind.KArr;
4+
import com.garciat.typeclasses.api.hkt.Kind.KStar;
5+
6+
/**
7+
* Full application of a unary type constructor.
8+
*
9+
* <p>TApp :: (* -> *) -> * -> *
10+
*/
11+
public interface TApp<Tag extends Kind<KArr<KStar>>, A> extends Kind<KStar> {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.garciat.typeclasses.api.hkt;
2+
3+
import com.garciat.typeclasses.api.hkt.Kind.KArr;
4+
import com.garciat.typeclasses.api.hkt.Kind.KStar;
5+
6+
/**
7+
* Partial application of a binary type constructor.
8+
*
9+
* <p>TPar :: (* -> * -> *) -> * -> (* -> *)
10+
*/
11+
public interface TPar<Tag extends Kind<KArr<KArr<KStar>>>, A> extends Kind<KArr<KStar>> {}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.garciat.typeclasses.api.hkt;
2+
3+
public abstract class TagBase<K extends Kind.Base> implements Kind<K> {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.garciat.typeclasses.classes;
2+
3+
import com.garciat.typeclasses.api.TypeClass;
4+
import com.garciat.typeclasses.api.hkt.Kind;
5+
import com.garciat.typeclasses.api.hkt.Kind.KArr;
6+
import com.garciat.typeclasses.api.hkt.Kind.KStar;
7+
import com.garciat.typeclasses.api.hkt.TApp;
8+
9+
@TypeClass
10+
public interface Alternative<F extends Kind<KArr<KStar>>> extends Applicative<F> {
11+
<A> TApp<F, A> empty();
12+
13+
<A> TApp<F, A> alt(TApp<F, A> fa1, TApp<F, A> fa2);
14+
}

0 commit comments

Comments
 (0)