-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathFunctjonal.java
More file actions
174 lines (154 loc) · 6.01 KB
/
Functjonal.java
File metadata and controls
174 lines (154 loc) · 6.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package de.variantsync.functjonal;
import de.variantsync.functjonal.functions.FragileFunction;
import de.variantsync.functjonal.functions.FragileProcedure;
import de.variantsync.functjonal.functions.FragileSupplier;
import de.variantsync.functjonal.functions.Procedure;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Helper class containing methods for functional programming missing in the standard library
* (or that we could not find).
* Contains also methods for pattern matching.
*/
public class Functjonal {
/// Containers
public static <K1, K2, V1, V2> Map<K2, V2> bimap(
final Map<K1, V1> m,
final Function<? super K1, ? extends K2> key,
final Function<? super V1, ? extends V2> val) {
return bimap(m, key, val, HashMap::new);
}
public static <K1, K2, V1, V2, M extends Map<K2, V2>> M bimap(
final Map<K1, V1> m,
final Function<? super K1, ? extends K2> key,
final Function<? super V1, ? extends V2> val,
final Supplier<M> mapFactory) {
final M result = mapFactory.get();
for (final Map.Entry<K1, V1> e : m.entrySet()) {
result.put(key.apply(e.getKey()), val.apply(e.getValue()));
}
return result;
}
public static <T, U> List<U> map(final List<? extends T> a, final Function<T, U> f) {
return a.stream().map(f).collect(Collectors.toList());
}
/// Pattern matching
public static <A, B> B match(
final Optional<A> ma,
final Function<A, ? extends B> just,
final Supplier<? extends B> nothing
) {
final Optional<B> x = ma.map(just);
return x.orElseGet(nothing);
}
/**
* Curried version of the above.
*/
public static <A, B> Function<Optional<A>, B> match(
final Function<A, B> just,
final Supplier<? extends B> nothing
) {
return ma -> match(ma, just, nothing);
}
public static <A, B, C> Function<Result<A, B>, C> match(
final Function<A, C> success,
final Function<B, C> failure
) {
return ma -> ma.match(success, failure);
}
public static <T> Consumer<T> when(
final Predicate<T> condition,
final Consumer<? super T> task
) {
return t -> {
if (condition.test(t)) {
task.accept(t);
}
};
}
/**
* Creates a branching function for given condition, then and else case.
*
* @param condition The condition choosing whether to run 'then' or 'otherwise'.
* @param then The function to apply when the given condition is met for a given a.
* @param otherwise The function to apply when the given condition is not met for a given a.
* @return A function that for a given a, returns then(a) if the given condition is met,
* and otherwise returns otherwise(a).
*/
public static <A, B> Function<A, B> when(
final Predicate<A> condition,
final Function<A, B> then,
final Function<A, B> otherwise
) {
return a -> condition.test(a) ? then.apply(a) : otherwise.apply(a);
}
/**
* The same as @see when but without an else case (i.e., else case function identity).
*/
public static <A> Function<A, A> when(final Predicate<A> condition, final Function<A, A> then) {
return when(condition, then, Function.identity());
}
/**
* A variant of @see when with a boolean value instead of a predicate.
*/
public static <B> Function<Boolean, B> when(final Supplier<B> then, final Supplier<B> otherwise) {
return condition -> condition ? then.get() : otherwise.get();
}
/// Java to FP
public static <A> Function<A, Unit> lift(final Consumer<A> f) {
return a -> {
f.accept(a);
return Unit.instance();
};
}
public static Supplier<Unit> lift(final Procedure f) {
return () -> {
f.run();
return Unit.instance();
};
}
public static <E extends Exception> FragileSupplier<Unit, E> liftFragile(final FragileProcedure<E> f) {
return () -> {
f.run();
return Unit.instance();
};
}
/**
* Maps the given function f onto the given value a if a is not null.
*
* @param n A nullable value that should be converted to a value of type B via f.
* @param f A function that should be mapped onto a.
* f can safely assume that any arguments passed to it are not null.
* @param errorMessage Creates an error message in case f threw an exception of type E.
* @param <Nullable> The type of the nullable value a.
* @param <B> The result type.
* @param <E> The type of an exception that might be thrown by f.
* @return Returns the result of f(a) if a is not null and f(a) did not throw an exception of type E.
* Returns Optional.empty() if a is null or f(a) threw an exception of type E.
*/
public static <Nullable, B, E extends Exception> Optional<B> mapFragile(
final Nullable n,
final FragileFunction<Nullable, B, E> f,
final Supplier<String> errorMessage
) {
return Optional.ofNullable(n).flatMap(a ->
Result.Try(() -> f.run(a)).match(
Optional::ofNullable, // actually the returned B can also be null, thus ofNullable here
exception -> Optional.empty()
)
);
}
public static <A, B, E extends Exception> Lazy<Optional<B>> mapFragileLazily(
final A a, final FragileFunction<A, B, E> f,
final Supplier<String> errorMessage
) {
return Lazy.of(() -> mapFragile(a, f, errorMessage));
}
}