この記事では、コードを設計するときに静的型システムを最大限に活用する方法について説明します。この記事は言語にとらわれないように努めており(常に機能するとは限りません)、例はJavaであり、実際から引用されています。Idrisのような学術言語では、静的型付けでより便利なトリックを実行でき、完全な型推論によって式典のサイズが大幅に削減されますが、職場では他の種類の言語で記述しているため、十分な知識を応用できるようにしたいと考えています。実際には、それが今日の私たちの生活をより良くするので...
記事の筋書きの簡単な説明
, , , , , .
, - ( , ). , , .
, .. , . .
, . , .
, : , ( , , , Java) .
, , , .
:
: , .
, , , .
. — , . — .
, .
, .
: DSL, .
.
, .
, , , . Rust ( non lexical lifetimes: , , , ), . ( ": "). , , , , , , .
, , , .. .
, : , , , . :
, , , . . , . , , .
Java:
Optional<? extends CharSequence> x = getContent();
/*
:
incompatible types: java.lang.String cannot be converted to
capture#1 of ? extends java.lang.CharSequenc
*/
CharSequence y = x.orElse("");
// :
// CharSequence y = ( (Optional<CharSequence>) x).orElse("");
, . , , .
, , .
x
Optional
— maybe Java, Rust Scala Option
. C# , , nullable , Optional
. Optional.orElse
, null
, .
? extends CharSequence
, , CharSequence
. Java ""
String
, CharSequence
.
, CharSequence
x
y
, ""
. . Java .
, .. CharSequence
, CharSequence
. Optional
. , .
, C++ Java : , , . , , , .
.
, . , , . , , .
, , , . , , , , — .
, : " "? — , , .
: , , . , , : -, , -, , -, , - . , , , .
, , , — , , , .
C Java? , C , ? . C , , . , , . , , , - :
void qsort (
void* base,
size_t num,
size_t size,
int (*comparator)(const void*, const void*)
);
. void*
.
, , . , ad-hoc , , C ( , ). - .
Java 3 . : ( ) ( ), ad-hoc: .
— . , . , , : . , Rust , .
, , , .
Rust , : .. ( ) .
Java , , , :
class Builder {
void addNames(String... names) {addNames(List.of(names))}
void addNames(Iterable<String> names) {/*...*/}
}
, , . : , , , .
, - . , -, .
, :
// run String T T .
<T> T run(Function<String, T> x) {
return x.apply("");
}
// run String ,
// .
void run(Consumer<String> x) {
run(y-> {
x.accept(y);
return null;
});
}
void doWork() {
run(x-> System.out.println(x)); // .
run((String x)-> System.out.println(x)); // .
}
— : . Java , run(Function)
run(Consumer)
, , : run(Consumer)
, , .. , , run(Function)
. , , , -, , .
, , .
, - , . , — , — . , , , .
, , , . . - . , .
, ClassA
, ClassB
, ClassC
. foo
, bar
, baz
. foo
bar
, baz
. ClassB
foo
baz
, ClassC
baz
, , ClassC.foo()
. : ClassC.foo()
ClassB.foo()
ClassA.bar
ClassA.baz
, ClassC.baz
. , , , .
— , .
, . : , , , API.
List.of
— Java, List
. , . List12
, 2 , , , . , .
— , , , ++ . , . , . , , , , .
, , language agnostic , java- , . , , .
, List<T>
, : X
Y
. , List<X>
, List<Y>
? , , — . , X
Z
, , List<Y>
, List<X>
: Z
, List<Y>
. , java heap pollution. ( , )
, , .
, .
:
— : , . Java .
— ,
List<Y>
. . JavaList<? extends X>
.
— ,
List<Y>
List<X>
. . JavaList<? super Y>
.
, , , Java . , Kotlin . , ( ) Java .
Java. List<? extends X>
- , heap pollution. Java : , , null
. : , null
, , (.. List<? extends X>
X
).
orElse(T default)
: T
? extends CharSequence
null
, T get()
CharSequence
. Java — , , . null
heap pollution.
: List<? super Y>
add(T)
Y
, T get(int)
Object
. , Y
List<Y>
, List<X>
List<Object>
, , get
.
, — , .
. , List<Y>
List<? extends X>
, . , Map<K, List<X>>
, Map<K, List<Y>>
, : Map<K, ? extends List<? extends X>>
. , . Map<K, List<? extends X>>
, Map<K, List<Y>>
, Map<K, List<X>>
, .. List<? extends X>
, , .
.
Rust C++ / , . .
C
, , .
, , T
source
, blacklist
. :
<T> int filterCount(Collection<T> source, Set<T> blacklist) {
if (blacklist.isEmpty()) {
return source.size();
}
return (int) source.stream().filter(x->!blacklist.contains(x)).count();
}
, blacklist
Set
.
Java, , : Collection
, Set
, List
. Collection
contains
, Set
.
, contains
Set
: O(1) HashSet
O(log n) TreeSet
. - Set
-, , Set
. .
, : . , , Java , .
,
, : - - , . - , .
, : . String->String
, , - : .
: SchemaDiff
String name
Nullable
.
final class SchemaDiff {
final String name;
final @Nullable String oldType;
final @Nullable String newType;
SchemaDiff(
String name,
@Nullable String oldType,
@Nullable String newType
) {
this.name = name;
this.oldType = oldType;
this.newType = newType;
}
@Override
public String toString() {
if (oldType != null && newType != null) {
return String.format(
"Column %s changed the type: %s->%s",
name,
oldType,
newType
);
}
if (oldType == null) {
return String.format(
"Column %s with type %s has been added",
name,
newType
);
}
return String.format(
"Column %s with type %s has been removed",
name,
oldType
);
}
}
null
. NPE: Optional
, .. : null
, , , , null
, .
toString
. , , oldType
null
.
, , : RemovedColumn
, AddedColumn
TypeChanged
. SchemaDiff
, .
abstract class SchemaDiff {
final String name;
protected SchemaDiff(String name) {
this.name = name;
}
}
final class RemovedColumn extends SchemaDiff {
final String type;
RemovedColumn(String name, String type) {
super(name);
this.type = type;
}
@Override
public String toString() {
return String.format(
"Column %s with type %s has been removed",
name,
type
);
}
}
final class AddedColumn extends SchemaDiff {
final String type;
AddedColumn(String name, String type) {
super(name);
this.type = type;
}
@Override
public String toString() {
return String.format(
"Column %s with type %s has been added",
name,
type
);
}
}
final class TypeChanged extends SchemaDiff {
final String oldType;
final String newType;
TypeChanged(String name, String oldType, String newType) {
super(name);
this.oldType = oldType;
this.newType = newType;
}
@Override
public String toString() {
return String.format(
"Column's %s type has been changed: %s->%s",
name,
oldType,
newType
);
}
}
, , . toString
.
, , , . . , , — .
- , :
void process(List<Item> items) {
if (isLegacy) {
List<Legacy> docs = items.stream()
.map(x -> toLegacy(x))
.collect(toList);
legacyTable.store(docs);
logEvent(docs.stream().map(x->x.getId()).collect(toList()));
} else {
List<Modern> docs = items.stream()
.map(x->toModern(x, context))
.collect(toList);
modernTable.store(docs);
logEvent(docs.stream().map(x->x.getId()).collect(toList()));
}
}
, Legacy
Modern
. toLegacy
toModern
, . legacyTable
modernTable
, .
- . : , - — .
— — .
, :
<T extends WithId> List<T> store(
List<Item> items,
Function<Item, T> mapper,
Table<T> table
) {
result = items.map(mapper).collect(toList());
table.store(result);
return result;
}
:
void process(List<Item> items) {
List<? extends WithId> docs = isLegacy ?
store(items, x -> toLegacy(x), legacyTable) :
store(items, x -> toModern(x, context), modernTable);
logEvent(docs);
}
logEvent
, , id
.
, - , .. , .
, , var
Kotlin, C# Java, , .. "" : .
Java:
var list = new ArrayList<>(); // list ArrayList<Object>.
list.add(1);
Rust:
let mut vec = Vec::new(); // vec Vec<i32>
vec.push(1);
, , . .
. , , :
HashMap<String, String> map = new HashMap<>();
:
Map<String, String> map = new HashMap<>();
: HashMap
TreeMap
- Map
, . , , .
Java 11 , , var
, .
, - :
Long2LongOpenHashMap createMap(long[] keys, long[] values);
- long->long
- -. , .. , . , .
, , , .
Long2LongMap createMap(long[] keys, long[] values);
- :
var map = createMap(keys, values);
for(long x: xs) {
f(map.get(x));
}
! , , .. API . , — - ( ).
, . , ML- , API . , . — . , — , . -.
, , enum-:
enum FeatureType {
ELMO,
SLANG,
LIFETIME_7D,
}
. , ELMO
EmbeddingEntry
— float, LIFETIME_7D
— FloatEntry
, float
— , 7 , SLANG
BlacklistEntry
— . FeatureEntry
, id
, .
, , API:
<TEntry extends FeatureEntry> Collection<TEntry> find(Collection<Id> ids, FeatureType type);
id
. .
: TEntry
FeatureType
? , :
enum FeatureType() {
ELMO(EmbeddingEntry.class),
SLANG(BlacklistEntry.class),
LIFETIME_7D(FloatEntry.class),
;
private final Class<? extends FeatureEntry> entryClass;
public FeatureType(Class<? extends FeatureEntry> entryClass) {
this.entryClass = entryClass;
}
public Class<? extends FeatureEntry> getEntryClass() {
return entryClass;
}
}
, , enum-. , .
, . find
TEntry
, . 3, 56, , .
:
final class FeatureType<TEntry extends FeatureEntry> {
public static final FeatureType<EmbeddingEntry> ELMO =
new FeatureType("elmo", EmbeddingEntry.class);
public static final FeatureType<BlacklistEntry> SLANG =
new FeatureType("slang", BlacklistEntry.class);
public static final FeatureType<FloatEntry> LIFETIME_7D =
new FeatureType("lifetime_7d", FloatEntry.class);
private final Class<TEntry> entryClass;
private final String name;
private FeatureType(String name, Class<TEntry> entryClass) {
this.name = name;
this.entryClass = entryClass;
}
public String getName() {
return name;
}
public Class<TEntry> getEntryClass() {
return entryClass;
}
}
: , enum. — — API :
<TEntry extends FeatureEntry> Collection<TEntry> find(
Collection<Id> ids,
FeatureType<TEntry> type
);
API, , - . , , API.
, , - - , , ( Java). .
, " ", , , .
. :
static <TLeft, TRight, TResult>
BiFunction<Optional<TLeft>, Optional<TRight>, Optional<TResult>>
lift(BiFunction<TLeft, TRight, TResult> function) {
return (left, right) -> left.flatMap(
leftVal -> right.map(rightVal -> function.apply(leftVal, rightVal))
);
}
, Optional
.
, FeatureType
, "" FeatureType
FeatureEntry
. , FeatureType
, "" FeatureEntry
.
, , . , , , . , , . , - , . ML .
:
<TEntry extends FeatureEntry> Collection<TEntry> find(
Collection<Id> ids,
FeatureType<TEntry> type,
Language language
);
<TEntry extends FeatureEntry> Collection<TEntry> find(
Collection<Id> ids,
FeatureType<TEntry> type,
Country country
);
: , runtime , .
:
class FeatureType<TEntry extends FeatureEntry> {
public static final ByLanguage<EmbeddingEntry> ELMO =
new ByLanguage<>("elmo", EmbeddingEntry.class);
public static final ByCountry<BlacklistEntry> SLANG =
new ByCountry<>("slang", BlacklistEntry.class);
public static final ByCountry<FloatEntry> LIFETIME_7D =
new ByCountry<>("lifetime_7d", FloatEntry.class);
private final Class<TEntry> entryClass;
private final String name;
private FeatureType(String name, Class<TEntry> entryClass) {
this.name = name;
this.entryClass = entryClass;
}
public String getName() {
return name;
}
static final class ByLanguage<TEntry extends FeatureEntry>
extends FeatureType<TEntry> {...}
static final class ByCountry<TEntry extends FeatureEntry>
extends FeatureType<TEntry> {...}
}
API :
<TEntry extends FeatureEntry> Collection<TEntry> find(
Collection<Id> ids,
FeatureType.ByLanguage<TEntry> type,
Language language
);
<TEntry extends FeatureEntry> Collection<TEntry> find(
Collection<Id> ids,
FeatureType.ByCountry<TEntry> type,
Country country
);
.
. , , , , , , .
, . , enum . , - , . , , . . , , .
-, , . , Java generic enum- , — , . , .
— . . , , , -, .
— , - — . , ? ? ? , , ? .
, -, , .
. , heap dump-, . heap dump- - : , . , heap dump- , , LevelDB, .
, , , . , , , Inspection
, , API Inspection
, : , UI.
— , — . , , .
, : - . , , -.
DSL vs composability
, - . , , , Java . , - DSL composability ( ).
DSL : , , API, - DSL. , , - , - DSL. , .. , DSL , .
- , , — . , composability.
, , : CompletableFuture<Optional<T>>
. Java :
OptionalFuture
.
-
FutureUtils
.
-. composable. , OptionalFuture
CompletableFuture
, Optional
— CompletableFuture
, . , - CompletableFuture
, , thenCompose
( ), , .
Java, composable : , . , , . -, vs . , , .
, , , composability , . , Apache Lucene.
Apache Lucene — : Twitter, Elasticsearch. , : , — . — API.
, , , , - - : ( Java), ( , , T[]
int[]
) , ( ).
, , , . , ? , Java " T, T, ", .
Apache Lucene InPlaceMergeSorter, , :
// :
private BlendedTermQuery(Term[] terms, float[] boosts, TermStates[] contexts) {
assert terms.length == boosts.length;
assert terms.length == contexts.length;
this.terms = terms;
this.boosts = boosts;
this.contexts = contexts;
// terms, boosts contexts
// : .
new InPlaceMergeSorter() {
// terms, .
@Override
protected int compare(int i, int j) {
return terms[i].compareTo(terms[j]);
}
// , terms
@Override
protected void swap(int i, int j) {
Term tmpTerm = terms[i];
terms[i] = terms[j];
terms[j] = tmpTerm;
TermStates tmpContext = contexts[i];
contexts[i] = contexts[j];
contexts[j] = tmpContext;
float tmpBoost = boosts[i];
boosts[i] = boosts[j];
boosts[j] = tmpBoost;
}
// , .. : -.
}.sort(0, terms.length);
}
, , , . , .
API , , , - - , . API: , , — .
API , :
Foo(float[] boosts) {
this.boosts = boosts.clone();
new InPlaceMergeSorter() {
@Override
protected int compare(int i, int j) {
return Float.compare(boosts[i], boosts[j]);
}
@Override
protected void swap(int i, int j) {
float tmpBoost = this.boosts[i];
this.boosts[i] = this.boosts[j];
this.boosts[j] = tmpBoost;
}
}.sort(0, terms.length);
}
, , .. boosts
this.boosts
, .
. , , , , .
- , , . , - composability, composability, .
API . .
, , , . , , , . , . : , . , , , .
, , , , . , null
null
. . , , , . , , ? , . : UB , null, , , .
, , . , , 1 , . , -, , , .. , . Java.
P.S.
C. .
公開前に記事をレビューしてくれたこれらの人々に感謝します。
-
ドミトリー・ペトロフ
ニコライ・ミシュク
アナスタシアパブロフスカヤ
-
スヴェトラーナ・イェセンコヴァ
ヤン・コルネフ