Утилита javah больше не нужна, потому что нативные заголовки теперь может делать javac
Очень интересно. Я правильно понимаю, что если у меня сорцы на котлине, то теперь хрен мне, а не jni? Спасибо тебе, Оракл, что заставляешь в полностью котлиновском проекте заводить единственный джавовый класс со всеми нативными методами.
Если я правильно помню, то в идее тоже сразу редактируется не файлы на диске, а некоторое внутреннее представление. Рассказывали в каком-то докладе что основной паттерн для иде — огромное количество маленьких файлов. И для диска это плохо. И внутри используется один гигантский файл — кэш. Так что у эклипса явно другая проблема — изменения не скидываются на диск. В идее сброс на диск производится не только при сохранении файла, но и просто при смене фокуса с иде на что-то другое. А при обратной фокусировке диск сканируется на наличие изменений.
Смотря как посмотреть. Если строго формально, то да, вы правы. Но только с тем же успехом можно утверждать что удаление из хэшсета у нас O(n!). Не, ну а чё? Константа действительно растет не быстрее факториала. Только есть одна проблема — данный ответ, не смотря на свою абсолютную правильность, не несет абсолютно никакой пользы. Так что для практического использования важно брать не просто верхнюю оценку, а «близкую» верхнюю оценку. И вот здесь допустимость замены sum(2,n,log^2(x)) -> nlog^2(n) уже не так очевидна. (сорри, на знаю как красиво формулы писать сюда)
Очевидное предположение, но хотелось проверить. Да, именно они. У меня получается их 16-19кк обычно. По 40 байт на каждый. 840кк все равно не получается, но тут уже близко очень. Может не везет просто. Ладно, я всё. Дальнейший анализ оставляю вам.
Ну так я же не сам придумал. Честно, был безумно удивлен когда это обнаружил. На что именно уходит память не анализировал еще. Получается что QuickSort и Arrays.sort не выделяют ничего. Arrays.parallelSort выделяет 400кк байт — это ровно еще один наш массив интов (и там действительно внутри есть new int[n]). А вот стримы (независимо от параллельности) и ParallelQuickSort выделяют дополнительно 800кк.
Более того, в стримах результат очень стабильный и там 800кк почти ровно.
Вот что нашел при быстром поиске (возможно есть еще что-то, но не копал уже).
1. В java-сообществе уже давно публиковать бенчи не на jmh считается неприлично.
2. Ваш вариант с int v = a[lo]; все-таки уж очень плохой. Если запустить повторную сортировку на массиве из примера, то ее результата мы уже никогда не дождемся. Банальная замена на int v = a[ThreadLocalRandom.current().nextInt(lo, hi + 1)]; полностью все исправляет.
3. Сравнение со стримами тоже плохое. Вы сортируете массив на месте, а стримы создают новый не трогая исходный.
4. В связи с последним особенно интересно что памяти ваш вариант выделяет столько же, сколько и стримы.
Но вообще очень показательно получилось. Очень хорошо видно что на таком кол-ве данных в первую очередь важна именно асимптотика, и не смотря на все отличия/оптимизации в решении, итоговый результат зависит от них довольно мало.
Я, конечно, тоже молодец, слишком доверчивый. Раз написано про ошибку в композиции, то проверил именно ее. А получилось что это как раз единственный закон из трех, который работает полностью.
Но вообще, мне кажется что все это некорректно. Если за базу мы берем haskell, то с нулами получается интересно. Ведь там их нет, именно за этим и нужен Maybe. Соответственно, логично предположить что если функция принимает какой-то тип, то она принимает именно этот тип, а не null. И тогда получается что (return v) >>= f ≡ f v для случая v = null просто не имеет смысла, т.к. конструкции (return null) и (f null) не существуют. На простой map, я думаю, данное ограничение тоже распространяется.
Так что еще большой вопрос является ли Optional монадой в строгом значении.
Но вообще, по поводу монад у меня есть другая мысль. Рискну быть заминусованным, но предположу что когда про монады говорит кто-нибудь, кто не является профессиональным фп-программистом (что-бы это не значило), он имеет в виду не строгий математический объект, а некую monad-like «контейнерную» абстракцию, позволяющую легко работать напрямую с содержимым в «нормальных» сценариях. А что при этом оно почти всегда разваливается на границах — да кому какое дело?! В конце концов, не надо мешать нулы и опшионалы.
Распознают, и в профиль заглядывают, и даже видят там уже поставленный плюс в карму. Но конкретно этот коммент, что называется, «слишком толстый». И парсится уже не как шутка, а как глупое отвлечение от темы.
Простой тест с перебором всех возможных значений показывает что разный результат будет только когда m == empty() && (f == null || g == null). Честно говоря, мне кажется что тестирование монадических законов на нулевых функциях — это читерство и так делать нельзя.
сам тест
public class MonadTest {
public static void main(String[] args) {
List<Optional<String>> ms = asList(ofNullable(null), ofNullable("123"), null);
List<Function<String, Optional<Integer>>> fs = asList(s -> ofNullable(null), s -> ofNullable(s.length()), s -> null, null);
List<Function<Integer, Optional<Boolean>>> gs = asList(s -> ofNullable(null), i -> ofNullable(i.intValue() == 0), i -> null, null);
for (int i = 0; i < ms.size(); i++) {
Optional<String> m = ms.get(i);
for (int j = 0; j < fs.size(); j++) {
Function<String, Optional<Integer>> f = fs.get(j);
for (int k = 0; k < gs.size(); k++) {
Function<Integer, Optional<Boolean>> g = gs.get(k);
try {
test(m, f, g);
} catch (AssertionError e) {
System.out.println(i + " " + j + " " + k);
}
}
}
}
}
private static void test(Optional<String> m, Function<String, Optional<Integer>> f, Function<Integer, Optional<Boolean>> g) {
Optional<Boolean> left;
boolean npeOnLeft;
Optional<Boolean> right;
boolean npeOnRight;
try {
left = m.flatMap(f).flatMap(g);
npeOnLeft = false;
} catch (NullPointerException e) {
left = null;
npeOnLeft = true;
}
try {
right = m.flatMap(x -> f.apply(x).flatMap(g));
npeOnRight = false;
} catch (NullPointerException e) {
right = null;
npeOnRight = true;
}
assertEquals(npeOnLeft, npeOnRight);
if (!npeOnLeft)
assertEquals(left, right);
}
}
А, блин, лоханулся я. Это же фича джава компилятора будет, так что надо еще допиливать котлиновский чтобы он про Intrinsics.invokedynamic тоже узнал. А если в любом случае надо его пилить, то и JEP-303 уже можно не ждать и сделать свой велосипед.
val public_strange_name_do_not_use_it = ConcurrentHashMap<String, Logger>()
inline val logger: Logger
get() {
return public_strange_name_do_not_use_it.computeIfAbsent(Exception().stackTrace[0].className) { cn -> LoggerFactory.getLogger(Class.forName(cn)) }
}
По получаемому синтаксису вроде идеально получается, здесь только с перформансом уже проблемы скорее всего будут (по крайней мере, для библиотечной функции общего пользование такое явно недопустимо). В принципе, если дождаться JEP-303, то в паре с инлайном это будет идеальное решение. По идее, тогда даже мапа будет не нужна, т.к. можно будет повесить CallSite напрямую на нужный логгер, но в этом я пока не уверен, надо внимательнее посмотреть.
Если честно, то лень. Но это и есть весь код. Суете в либу на топ уровне что я писал ранее, а используется просто как
import org.dou.lib.logger
class Foo {
init {
logger.info("hello") //[main] INFO org.dou.Foo - hello
}
}
На самом деле, я уже обнаружил две большие проблемы у этого варианта:
1. Это публичное экстеншн проперти, так что я «logger» могу написать не только внутри класса, но и снаружи
42.logger.info("msg") //[main] INFO java.lang.Integer - msg
2. Он вообще не работает с топ левел функциями (вне класса/объекта). Т.к. там нет this, у которого logger и отрастает.
В принципе, обе проблемы решаются если убрать «Any.» и сделать просто
val logger: Logger get() { TODO() }
(тоже на рутовом уровне), но здесь я пока не придумал как в теле геттера получать класс, откуда он вызывается. Каждый раз через исключение смотреть стектрейс — явно плохо.
Так наоборот же. Эту штуку надо написать всего один раз (и можно даже в библиотеке) и всё! Вам больше вообще ничего делать не надо. У всех классов уже есть логгер. В отличии от ломбока, в котором как раз надо еще целую аннотацию поставить.
Очень интересно. Я правильно понимаю, что если у меня сорцы на котлине, то теперь хрен мне, а не jni? Спасибо тебе, Оракл, что заставляешь в полностью котлиновском проекте заводить единственный джавовый класс со всеми нативными методами.
Ну так я же не сам придумал. Честно, был безумно удивлен когда это обнаружил. На что именно уходит память не анализировал еще. Получается что QuickSort и Arrays.sort не выделяют ничего. Arrays.parallelSort выделяет 400кк байт — это ровно еще один наш массив интов (и там действительно внутри есть new int[n]). А вот стримы (независимо от параллельности) и ParallelQuickSort выделяют дополнительно 800кк.
Более того, в стримах результат очень стабильный и там 800кк почти ровно.
А вот в ParallelQuickSort какой-то расколбас.
Надо через jmc посмотреть что там происходит.
1. В java-сообществе уже давно публиковать бенчи не на jmh считается неприлично.
2. Ваш вариант с int v = a[lo]; все-таки уж очень плохой. Если запустить повторную сортировку на массиве из примера, то ее результата мы уже никогда не дождемся. Банальная замена на int v = a[ThreadLocalRandom.current().nextInt(lo, hi + 1)]; полностью все исправляет.
3. Сравнение со стримами тоже плохое. Вы сортируете массив на месте, а стримы создают новый не трогая исходный.
4. В связи с последним особенно интересно что памяти ваш вариант выделяет столько же, сколько и стримы.
Но вообще очень показательно получилось. Очень хорошо видно что на таком кол-ве данных в первую очередь важна именно асимптотика, и не смотря на все отличия/оптимизации в решении, итоговый результат зависит от них довольно мало.
Но вообще, мне кажется что все это некорректно. Если за базу мы берем haskell, то с нулами получается интересно. Ведь там их нет, именно за этим и нужен Maybe. Соответственно, логично предположить что если функция принимает какой-то тип, то она принимает именно этот тип, а не null. И тогда получается что (return v) >>= f ≡ f v для случая v = null просто не имеет смысла, т.к. конструкции (return null) и (f null) не существуют. На простой map, я думаю, данное ограничение тоже распространяется.
Так что еще большой вопрос является ли Optional монадой в строгом значении.
Но вообще, по поводу монад у меня есть другая мысль. Рискну быть заминусованным, но предположу что когда про монады говорит кто-нибудь, кто не является профессиональным фп-программистом (что-бы это не значило), он имеет в виду не строгий математический объект, а некую monad-like «контейнерную» абстракцию, позволяющую легко работать напрямую с содержимым в «нормальных» сценариях. А что при этом оно почти всегда разваливается на границах — да кому какое дело?! В конце концов, не надо мешать нулы и опшионалы.
p.s. Сам пост отличный, спасибо!
По получаемому синтаксису вроде идеально получается, здесь только с перформансом уже проблемы скорее всего будут (по крайней мере, для библиотечной функции общего пользование такое явно недопустимо). В принципе, если дождаться JEP-303, то в паре с инлайном это будет идеальное решение. По идее, тогда даже мапа будет не нужна, т.к. можно будет повесить CallSite напрямую на нужный логгер, но в этом я пока не уверен, надо внимательнее посмотреть.
На самом деле, я уже обнаружил две большие проблемы у этого варианта:
1. Это публичное экстеншн проперти, так что я «logger» могу написать не только внутри класса, но и снаружи
2. Он вообще не работает с топ левел функциями (вне класса/объекта). Т.к. там нет this, у которого logger и отрастает.
В принципе, обе проблемы решаются если убрать «Any.» и сделать просто (тоже на рутовом уровне), но здесь я пока не придумал как в теле геттера получать класс, откуда он вызывается. Каждый раз через исключение смотреть стектрейс — явно плохо.
А чем плох такой вариант?
Да, не static final, но неужели не пофиг?
Нет, Ceylon ни разу не является достойной альтернативой Kotlin.
Кстати, еще в общую копилку: This War of Mine. Из той же серии — ни черта не понятно, но эмоции и сильнейшее желание узнать об игре побольше вызывает.
Он на самом деле статический.