Este artículo trata sobre cómo aprovechar al máximo el sistema de tipos estáticos al diseñar su código. El artículo intenta ser independiente del lenguaje (no siempre funciona), los ejemplos están en Java y se toman de la vida. Aunque los lenguajes académicos como Idris nos permiten hacer trucos más útiles con la escritura estática , y la inferencia de tipo completo reduce significativamente el tamaño de la ceremonia , en el trabajo escribimos en otros tipos de lenguajes y queremos poder aplicar buenos conocimientos. en la práctica, ya que mejorará nuestra vida hoy ...
Breve recuento de la trama del artículo.
, , , , , .
, - ( , ). , , .
, .. , . .
, . , .
, : , ( , , , 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. .
Muchas gracias a estas personas por revisar el artículo antes de su publicación:
-
Dmitry Petrov
Nikolay Mishuk
Anastasia Pavlovskaya
-
Svetlana Yesenkova
Yan Kornev