Pull to refresh

Comments 17

Неплохое интро в кложур, но хотелось бы больше увидеть его как названии статьи (во flutter'e, со стейт менеджерами, а не просто в сравнении с dart'ом при решении некоторых мат. задач

Подумываю написать вторую статью для тех, кто знаком и с флаттером и Clojure, с примерами того, как будет отличаться подход к работе со стейтом (если делать это идиоматично, но в целом можно так же использовать BLOC).

Пока что могу только сослаться на примеры в самом репозитории (samples). Во многих есть ссылка на cookbook от flutter.

И есть еще пример (правда, старенький), где я набросал экранчик с TEA архитектурой.

С setefull и Bloc виджетами было бы очень интересно увидеть комплексную статью. За примеры благодарен, основные аспекты они показывают что надо)

Дисклеймер

Я не писал на Clojure, но пишу на Dart под Flutter, поэтому напишу то, что увидел прочитав статью и запустив пример кода ClojureDart

Всё что далее, будет рассмотрено в контексте того, что ClojureDart на выходе создаёт Dart код (тут, а так же видно при запуске ClojureDart кода)

Не имею претензий к Clojure, но скорее всего имею к ClojureDart

1. ClojureDart на выходе создаёт Dart код, поэтому, логичнее было бы сравнивать не с чистым Dart, а с Dart с кодогенерацией

2. Аргумент, что код короче не такой однозначный:

  • На выходе имеем 40807 строк неформатированного кода Dart (88141 строк после flutter format)

  • Этот код на Dart, из того же примера с симуляцией физики, занимает 49 строк (и на мой взгляд читается легче) - вопрос форматирования

Тут
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:functional_widget_annotation/functional_widget_annotation.dart';

part 'main.g.dart';

void main() => runApp(const MaterialApp(home: PhysicsCardDragDemo()));

@swidget
Widget physicsCardDragDemo() => Scaffold(
    appBar: AppBar(),
    body: const DraggableCard(child: FlutterLogo(size: 128)));

class DraggableCard extends StatefulWidget {
  const DraggableCard({required this.child, super.key});

  final Widget child;

  @override
  State<DraggableCard> createState() => _DraggableCardState();
}

class _DraggableCardState extends State<DraggableCard> with SingleTickerProviderStateMixin {
  late final _controller = AnimationController(vsync: this)..addListener(() => setState(() => _dragAlignment = _animation.value));
  Alignment _dragAlignment = Alignment.center;
  late Animation<Alignment> _animation;

  void _runAnimation(Offset pixelsPerSecond, Size size) {
    _animation = _controller.drive(AlignmentTween(begin: _dragAlignment, end: Alignment.center));
    final simulation = SpringSimulation(const SpringDescription(mass: 30, stiffness: 1, damping: 1), 0, 1, -Offset(pixelsPerSecond.dx / size.width, pixelsPerSecond.dy / size.height).distance);
    _controller.animateWith(simulation);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return GestureDetector(
        onPanDown: (details) => _controller.stop(),
        onPanUpdate: (details) => setState(() => _dragAlignment += Alignment(details.delta.dx / (size.width / 2), details.delta.dy / (size.height / 2))),
        onPanEnd: (details) => _runAnimation(details.velocity.pixelsPerSecond, size),
        child: Align(alignment: _dragAlignment, child: Card(child: widget.child)));
  }
}

3 Для создания плоского вложенного кода есть пакет nested, но это вопрос немного глубже - имея много вложенности, быстро поймёшь, что нужно разбить виджет на несколько, так как сложно понимать, а так же, с большой вероятностью нужно не обновлять все из вложенных виджетов при изменении состояния - вопрос оптимизации
4. (небольшое отступление) Макросы планирую добавить в Dart (тут)
5. Можно проверить, что отсортирована с помощью isSorted (из достаточно стандартного пакета collection) и даже сравнивать то, что не имеет реализации < > =
6. А можно так переворачивать матрицы:

List<List<int>> transposeList<R>(List<List<int>> input) {
  return List.generate(
    input[0].length,
    (i) => List.generate(input.length, (j) => input[j][i]),
  );
}

7.

Core коллекции в Clojure — иммутабельные и персистентные, то есть позволяют создавать копии себя за практически константное время. 

Не уверен, что это применимо для ClojureDart, так как добавление элемента в список (как это сделать гуглил)

(conj [1 2 3] 4)

генерит Dart код, который создаёт новый List размера 4 и заполняет его новыми элементами - не похоже на константу

8.

thread-safe код.

Тоже не про Dart

9.

Не нужно описывать, читать и разбираться со всем бойлерплейтом, сопряженным с созданием класса (hash, ==, toMap, fromMap, toString, copyWith), он доступен из коробки.

Имея кодогенерацию - не имеем проблем с этим (например, freezed)

Итого: Если вы знаете только Clojure - то ClojureDart скорее всего ваш выбор. По факту же, генерируется код с кучей dynamic, что сводит на нет оптимизации, которые проводит компилятор Dart, а так же генерируется куча лишнего кода, который заставляет работать Dart как Clojure, при этом заставляя компилятор изрядно попотеть при компиляции (например, hello world занимает примерно 35500 строк до форматирования и 74115 после)

Спасибо за комментарий! Многие тезисы справедливы. Позволю себе прокомментировать каждый.

  1. Смотря для каких целей (см. пункт 2). Пока еще ClojureDart в альфе, так что я ожидаю, что сгенерированный код будет значительно(!) лучше.

  2. Я сравнивал тот код, с которым разработчик работает большую часть времени. Когда я пишу на Clojure (с jvm хостом), я смотрю на сгенерированный код редко — когда требуется производительность или отладка. А пример с физикой — вот тут же 108 сток кода без комментариев, а кложа-вариант — 50.

  3. Кажется, что насчет nest я не нагрешил в статье. Посмотрел реализацию, в рантайме будет дополнительная работа. И чтобы написать такое решение на Dart, потребовалась библиотека, где реализация на 400 строк. Мой же тезис был про то, что когда подобное нужно на Clojure, кода на реализацию получается сильно меньше (говоря о коде, с которым работает разработчик, а не который генерируется).

  4. Макросы не смогут быть такими же как в Clojure, потому что Dart не гомоиконичный язык.

  5. Мой пример про проверку сортировки был только для демонстрации того, что умеет делать apply.

  6. Да, было бы честнее, если б я использовал такой пример для сравнения.

  7. Это временно, целевая версия будет работать на персистентных коллекциях. Думаю, это даже можно будет сделать, переиспользуя fast_immutable_collections, спрошу авторов про это, и может подробнее напишу в следующей статье.

  8. Я тут имел в виду немного другой. Одна из причин, почему любят иммутабельность, — потому что не нужно думать, что разные потоки будут менять один стейт. И хорошо, если создавать копию коллекции можно за О(Log32), а не линейно. И именно это позволяют персистентные коллекции. Плюс Clojure тут в том, что созданы сотни функций для работы с этими коллекциями, и их же используют для представления данных (вместо классов).

  9. Да, можно генерировать, но мне не нравится, что кодовая база содержит этот бойлерплейт. Например, отойдя в сторону от Clojure, на том же Kotlin можно хотя бы так описать: data class Human(val age: Int). Так можно описать с десяток моделей в одном файле. И тут вся информация, которая мне нужна. Я не хочу читать кучу методов, если в них предсказуемый бойлерплейт, а не что-то, что мне нужно учесть. Т.е. это просто отвлекает, т.к. в каком-то месте может быть действительно уникальный код, а не типичная реализация.

P.S. Я немного поправлю статью, допишу про коллекции и приведу сокращенный дарт-код для транспанирования матрицы.

Неправильную ссылку привел. Пункт 2. Физика на 108 строк без комментариев.

  1. Там не сохраняются ваши изменения

  2. Я привёл тот-же пример, что и по вашей ссылке, но убрал красивость форматирования

По поводу пункта 7.

Посмотрел на внутренности ClojureDart — там есть свои реализации PersistentVector и PersistentMap.

Я запустил Hello world на коммите 0b83ba8dd0bae639f5d457f275d6800eba57ffe4 с таким кодом:

(ns quickstart.helloworld)

(defn main []
  (let [v1 [1 2 3]
        v2 (conj v1 4)]
    (print (str :v1 v1))
    (print (str :v2 v2))))

Вот что сгенерировалось:

import "dart:core" as dc;
import "helloworld.dart" as lcoq_helloworld;
import "../cljd/core.dart" as lcoc_core;

// BEGIN main
dc.dynamic main(){
final dc.List<dc.dynamic> fl$1=(dc.List<dc.dynamic>.filled(3, 1, ));
fl$1[1]=2;
fl$1[2]=3;
final lcoc_core.PersistentVector v1$1=lcoc_core.$_vec_owning(fl$1, );
final lcoc_core.PersistentVector coll7498$1=v1$1;
late final dc.dynamic v2$1;
if((coll7498$1 is lcoc_core.ICollection$iface)){
v2$1=((coll7498$1 as lcoc_core.ICollection$iface).$_conj$1(4, ));
}else{
v2$1=((lcoc_core.ICollection.extensions((coll7498$1 as dc.dynamic), ) as lcoc_core.ICollection$ext).$_conj$1((coll7498$1 as dc.dynamic), 4, ));
}
lcoc_core.print.$_invoke$1((lcoc_core.str.$_invoke$2(const lcoc_core.Keyword(null, "v1", 2915138964, ), v1$1, )), );
return (lcoc_core.print.$_invoke$1((lcoc_core.str.$_invoke$2(const lcoc_core.Keyword(null, "v2", 1579994680, ), v2$1, )), ));
}

// END main

Создание персистентного вектора v1:

final lcoc_core.PersistentVector coll7498$1=v1$1;

Создание персистентного вектора v2:

v2$1=((coll7498$1 as lcoc_core.ICollection$iface).$_conj$1(4, ));

Т.е. добавляем в первый вектор цифру 4, и это не изменяет первый вектор, а создает новый за O(Log32n), а не линейно.

Вызваем dart run и видим, что выводится:

:v1[1 2 3]:v2[1 2 3 4]

Т.е. добавление в v1 не изменило его.

Я предположу, что ваш случай c созданием списка тут

(conj [1 2 3] 4)

объясняется тем, что компилятор понимает, что не зачем тут сперва создавать вектор из трех элементов, а затем добавлять в него элемент, а проще сразу создать вектор из 4 элементов.

Собраю на коммите 1c519109b5dda0c24badc7bea3b89b52f3d3db8f

Этот код создаёт новый вектор:

    v2$1 = ((coll7502$1 as lcoc_core.ICollection$iface).$_conj$1(
      4,
    ));

И если пройтись дебаггером, то мы попадём в создание списка и заполнение его

Скриншот дебаггера

Похоже, тут дело в том, что переиспользоваться массивы внутри вектора будут, только если элементов в векторе больше 32 (статья). Спросил разраба ClojureDart — у них такая реализация.

Вижу изменения в работе - оно разделяет на куски по 32

Решил ещё глянуть как выглядит удаление последнего элемента - это чудо создаётся если в списке на 37 элементов вызвать drop-last

Дебаггер
v2$1 = {LazySeq} (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)
 meta = null
 fn = null
 s = {Cons} (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)
  meta = null
  $UNDERSCORE_first = 1
  rest = {LazySeq} (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)
   meta = null
   fn = null
   s = {Cons} (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)
    meta = null
    $UNDERSCORE_first = 2
    rest = {LazySeq} (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)
     meta = null
     fn = null
     s = {Cons} (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)
      meta = null
      $UNDERSCORE_first = 3
      rest = {LazySeq} (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)
       meta = null
       fn = null
       s = {Cons} (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)
        meta = null
        $UNDERSCORE_first = 4
        rest = {LazySeq} (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)
         meta = null
         fn = null
         s = {Cons} (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)
          meta = null
          $UNDERSCORE_first = 5
          rest = {LazySeq} (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)
           meta = null
           fn = null
           s = {Cons} (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)
            meta = null
            $UNDERSCORE_first = 6
            rest = {LazySeq} (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)
             meta = null
             fn = null
             s = {Cons} (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)
              meta = null
              $UNDERSCORE_first = 7
              rest = {LazySeq} (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)
               meta = null
               fn = null
               s = {Cons} (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)
                meta = null
                $UNDERSCORE_first = 8
                rest = {LazySeq} (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)
                 meta = null
                 fn = null
                 s = {Cons} (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)
                  meta = null
                  $UNDERSCORE_first = 9
                  rest = {LazySeq} (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)
                   meta = null
                   fn = null
                   s = {Cons} (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)
                    meta = null
                    $UNDERSCORE_first = 10
                    rest = {LazySeq} (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)
                     meta = null
                     fn = null
                     s = {Cons} (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)
                      meta = null
                      $UNDERSCORE_first = 11
                      rest = {LazySeq} (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)
                       meta = null
                       fn = null
                       s = {Cons} (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)
                        meta = null
                        $UNDERSCORE_first = 12
                        rest = {LazySeq} (13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                         meta = null
                         fn = null
                         s = {Cons} (13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                          meta = null
                          $UNDERSCORE_first = 13
                          rest = {LazySeq} (14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                           meta = null
                           fn = null
                           s = {Cons} (14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                            meta = null
                            $UNDERSCORE_first = 14
                            rest = {LazySeq} (15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                             meta = null
                             fn = null
                             s = {Cons} (15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                              meta = null
                              $UNDERSCORE_first = 15
                              rest = {LazySeq} (16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                               meta = null
                               fn = null
                               s = {Cons} (16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                meta = null
                                $UNDERSCORE_first = 16
                                rest = {LazySeq} (17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                 meta = null
                                 fn = null
                                 s = {Cons} (17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                  meta = null
                                  $UNDERSCORE_first = 17
                                  rest = {LazySeq} (18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                   meta = null
                                   fn = null
                                   s = {Cons} (18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                    meta = null
                                    $UNDERSCORE_first = 18
                                    rest = {LazySeq} (19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                     meta = null
                                     fn = null
                                     s = {Cons} (19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                      meta = null
                                      $UNDERSCORE_first = 19
                                      rest = {LazySeq} (20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                       meta = null
                                       fn = null
                                       s = {Cons} (20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                        meta = null
                                        $UNDERSCORE_first = 20
                                        rest = {LazySeq} (21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                         meta = null
                                         fn = null
                                         s = {Cons} (21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                          meta = null
                                          $UNDERSCORE_first = 21
                                          rest = {LazySeq} (22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                           meta = null
                                           fn = null
                                           s = {Cons} (22 23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                            meta = null
                                            $UNDERSCORE_first = 22
                                            rest = {LazySeq} (23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                             meta = null
                                             fn = null
                                             s = {Cons} (23 24 25 26 27 28 29 30 31 32 33 34 35 36)
                                              meta = null
                                              $UNDERSCORE_first = 23
                                              rest = {LazySeq} (24 25 26 27 28 29 30 31 32 33 34 35 36)
                                               meta = null
                                               fn = null
                                               s = {Cons} (24 25 26 27 28 29 30 31 32 33 34 35 36)
                                                meta = null
                                                $UNDERSCORE_first = 24
                                                rest = {LazySeq} (25 26 27 28 29 30 31 32 33 34 35 36)
                                                 meta = null
                                                 fn = null
                                                 s = {Cons} (25 26 27 28 29 30 31 32 33 34 35 36)
                                                  meta = null
                                                  $UNDERSCORE_first = 25
                                                  rest = {LazySeq} (26 27 28 29 30 31 32 33 34 35 36)
                                                   meta = null
                                                   fn = null
                                                   s = {Cons} (26 27 28 29 30 31 32 33 34 35 36)
                                                    meta = null
                                                    $UNDERSCORE_first = 26
                                                    rest = {LazySeq} (27 28 29 30 31 32 33 34 35 36)
                                                     meta = null
                                                     fn = null
                                                     s = {Cons} (27 28 29 30 31 32 33 34 35 36)
                                                      meta = null
                                                      $UNDERSCORE_first = 27
                                                      rest = {LazySeq} (28 29 30 31 32 33 34 35 36)
                                                       meta = null
                                                       fn = null
                                                       s = {Cons} (28 29 30 31 32 33 34 35 36)
                                                        meta = null
                                                        $UNDERSCORE_first = 28
                                                        rest = {LazySeq} (29 30 31 32 33 34 35 36)
                                                         meta = null
                                                         fn = null
                                                         s = {Cons} (29 30 31 32 33 34 35 36)
                                                          meta = null
                                                          $UNDERSCORE_first = 29
                                                          rest = {LazySeq} (30 31 32 33 34 35 36)
                                                           meta = null
                                                           fn = null
                                                           s = {Cons} (30 31 32 33 34 35 36)
                                                            meta = null
                                                            $UNDERSCORE_first = 30
                                                            rest = {LazySeq} (31 32 33 34 35 36)
                                                             meta = null
                                                             fn = null
                                                             s = {Cons} (31 32 33 34 35 36)
                                                              meta = null
                                                              $UNDERSCORE_first = 31
                                                              rest = {LazySeq} (32 33 34 35 36)
                                                               meta = null
                                                               fn = null
                                                               s = {Cons} (32 33 34 35 36)
                                                                meta = null
                                                                $UNDERSCORE_first = 32
                                                                rest = {LazySeq} (33 34 35 36)
                                                                 meta = null
                                                                 fn = null
                                                                 s = {Cons} (33 34 35 36)
                                                                  meta = null
                                                                  $UNDERSCORE_first = 33
                                                                  rest = {LazySeq} (34 35 36)
                                                                   meta = null
                                                                   fn = null
                                                                   s = {Cons} (34 35 36)
                                                                    meta = null
                                                                    $UNDERSCORE_first = 34
                                                                    rest = {LazySeq} (35 36)
                                                                     meta = null
                                                                     fn = null
                                                                     s = {Cons} (35 36)
                                                                      meta = null
                                                                      $UNDERSCORE_first = 35
                                                                      rest = {LazySeq} (36)
                                                                       meta = null
                                                                       fn = null
                                                                       s = {Cons} (36)
                                                                        meta = null
                                                                        $UNDERSCORE_first = 36
                                                                        rest = {LazySeq} ()
                                                                         meta = null
                                                                         fn = null
                                                                         s = null
                                                                         $UNDERSCORE_$UNDERSCORE_hash = -1
                                                                        $UNDERSCORE_$UNDERSCORE_hash = -1
                                                                       $UNDERSCORE_$UNDERSCORE_hash = -1
                                                                      $UNDERSCORE_$UNDERSCORE_hash = -1
                                                                     $UNDERSCORE_$UNDERSCORE_hash = -1
                                                                    $UNDERSCORE_$UNDERSCORE_hash = -1
                                                                   $UNDERSCORE_$UNDERSCORE_hash = -1
                                                                  $UNDERSCORE_$UNDERSCORE_hash = -1
                                                                 $UNDERSCORE_$UNDERSCORE_hash = -1
                                                                $UNDERSCORE_$UNDERSCORE_hash = -1
                                                               $UNDERSCORE_$UNDERSCORE_hash = -1
                                                              $UNDERSCORE_$UNDERSCORE_hash = -1
                                                             $UNDERSCORE_$UNDERSCORE_hash = -1
                                                            $UNDERSCORE_$UNDERSCORE_hash = -1
                                                           $UNDERSCORE_$UNDERSCORE_hash = -1
                                                          $UNDERSCORE_$UNDERSCORE_hash = -1
                                                         $UNDERSCORE_$UNDERSCORE_hash = -1
                                                        $UNDERSCORE_$UNDERSCORE_hash = -1
                                                       $UNDERSCORE_$UNDERSCORE_hash = -1
                                                      $UNDERSCORE_$UNDERSCORE_hash = -1
                                                     $UNDERSCORE_$UNDERSCORE_hash = -1
                                                    $UNDERSCORE_$UNDERSCORE_hash = -1
                                                   $UNDERSCORE_$UNDERSCORE_hash = -1
                                                  $UNDERSCORE_$UNDERSCORE_hash = -1
                                                 $UNDERSCORE_$UNDERSCORE_hash = -1
                                                $UNDERSCORE_$UNDERSCORE_hash = -1
                                               $UNDERSCORE_$UNDERSCORE_hash = -1
                                              $UNDERSCORE_$UNDERSCORE_hash = -1
                                             $UNDERSCORE_$UNDERSCORE_hash = -1
                                            $UNDERSCORE_$UNDERSCORE_hash = -1
                                           $UNDERSCORE_$UNDERSCORE_hash = -1
                                          $UNDERSCORE_$UNDERSCORE_hash = -1
                                         $UNDERSCORE_$UNDERSCORE_hash = -1
                                        $UNDERSCORE_$UNDERSCORE_hash = -1
                                       $UNDERSCORE_$UNDERSCORE_hash = -1
                                      $UNDERSCORE_$UNDERSCORE_hash = -1
                                     $UNDERSCORE_$UNDERSCORE_hash = -1
                                    $UNDERSCORE_$UNDERSCORE_hash = -1
                                   $UNDERSCORE_$UNDERSCORE_hash = -1
                                  $UNDERSCORE_$UNDERSCORE_hash = -1
                                 $UNDERSCORE_$UNDERSCORE_hash = -1
                                $UNDERSCORE_$UNDERSCORE_hash = -1
                               $UNDERSCORE_$UNDERSCORE_hash = -1
                              $UNDERSCORE_$UNDERSCORE_hash = -1
                             $UNDERSCORE_$UNDERSCORE_hash = -1
                            $UNDERSCORE_$UNDERSCORE_hash = -1
                           $UNDERSCORE_$UNDERSCORE_hash = -1
                          $UNDERSCORE_$UNDERSCORE_hash = -1
                         $UNDERSCORE_$UNDERSCORE_hash = -1
                        $UNDERSCORE_$UNDERSCORE_hash = -1
                       $UNDERSCORE_$UNDERSCORE_hash = -1
                      $UNDERSCORE_$UNDERSCORE_hash = -1
                     $UNDERSCORE_$UNDERSCORE_hash = -1
                    $UNDERSCORE_$UNDERSCORE_hash = -1
                   $UNDERSCORE_$UNDERSCORE_hash = -1
                  $UNDERSCORE_$UNDERSCORE_hash = -1
                 $UNDERSCORE_$UNDERSCORE_hash = -1
                $UNDERSCORE_$UNDERSCORE_hash = -1
               $UNDERSCORE_$UNDERSCORE_hash = -1
              $UNDERSCORE_$UNDERSCORE_hash = -1
             $UNDERSCORE_$UNDERSCORE_hash = -1
            $UNDERSCORE_$UNDERSCORE_hash = -1
           $UNDERSCORE_$UNDERSCORE_hash = -1
          $UNDERSCORE_$UNDERSCORE_hash = -1
         $UNDERSCORE_$UNDERSCORE_hash = -1
        $UNDERSCORE_$UNDERSCORE_hash = -1
       $UNDERSCORE_$UNDERSCORE_hash = -1
      $UNDERSCORE_$UNDERSCORE_hash = -1
     $UNDERSCORE_$UNDERSCORE_hash = -1
    $UNDERSCORE_$UNDERSCORE_hash = -1
   $UNDERSCORE_$UNDERSCORE_hash = -1
  $UNDERSCORE_$UNDERSCORE_hash = -1
 $UNDERSCORE_$UNDERSCORE_hash = -1

Итого:

  1. Пока ещё очень сыро.

  2. Компилятор Dart не доволен

  3. Оптимизатор тоже не доволен

  4. Дебаггер в шоке

  5. Стоит подумать над вариантом, когда для web версии компилируется сразу в JS, так как Dart сам оставляет много чего для себя, а ещё будет огромное количество ненужного от ClojureDart

А как решается проблема с null safety?

Точно нет проблемы, как в Java, где эти проверки на null на каждом шагу пишут (говорю по опыту и по тому, что читал).

Сложно сравнить с языком со статической типизацией и поддержкой null safety, как Dart или Kotlin. С одной стороны, нельзя объявить тип и тут же пометить его как non-nullable. С другой — можно описать spec, которым на границах проверять значения.

С третьей, в Clojure null (nil) естественно вписывается как first class value. На нем можно вызывать методы (на самом деле, не совсем так, т.к. в фп это не у объектов есть методы, а функции принимают разные типы), расширять протоколы (интерфейсы) — это как extension в Dart или Kotlin.

Есть концепция nil punnling — это когда nil ведет себя по обстоятельствам.

Если в контектсе ожидается boolean, nil означает false.
Если “мапа” — пустая “мапа”.
Если вектор — пустой вектор.

Примеры:

(if nil 1 2) ;=> 2

;; vector, list
(conj [] 1) ;=> [1]
(conj nil 1) ;=> (1)
(concat nil [1 2] nil [3 4]) ;=> (1 2 d3 4)
(first [1 2]) ;=> 1
(first []) ;=> nil
(first nil) ; nil
(count [1 2]) ;=> 2
(count nil) ;=> 0

;; map
(assoc {} :a 1 :b 2) ;=> {:a 1, :b 2}
(assoc nil :a 1 :b 2) ;=> {:a 1, :b 2}

Eсли вместо функции пеердать nil, то упадем с "can't call nil":
(nil 1 2)

Ну и конечно не всегда nil pinning на руку. Например, (str nil) вернет пустую строку, хотя в других случаях nil не ведет себя как пустая строка.

Примеры:

(str nil) ;=> ""

(str/join ", " [1 2 3]) ;=> "1, 2, 3"
(str/join nil [1 2 3]) ;=> "123"

(str/trim nil) ;=> NullPointerException

С четвертой стороны, процесс разработки на Clojure очень сильно отличается. Например, когда я работаю с Kotlin, я могу писать код весь деть и запустить его только 1 раз вечером, и иногда все заведется с первого раза.

С Clojure у меня под рукой REPL и какая-нибудь тулза для tracing “дебагинга” (это когда записываются значения всего везде), так что я постоянно вижу, как работает код и какие данные находятся под каждым символом.

Будет нетривиальнейшее описание вакансии на такой проект: знание Clojure и Flutter.

Middle Flutter разработчик

Задачи:

  • Разрабатывать новую и улучшать существующую функциональность кросплатформенного приложения web, ios, android;

Мы ожидаем:

  • Уверенное владение Clojure, Dart, ClojureDart, DartClojure;

  • SOLID, паттерны проектирования, знание паттернов MVP, MVVM, MVI;

  • Понимание основ функционального программирования.

  • Понимание основ датаориентированного программирования.

Будет плюсом:

  • Опыт нативной разработки iOS/Android;

  • Знание Objective-C/Swift, Java/Kotlin;

  • Опыт веб-разработки;

  • Знание Javascript/Typescript;

Sign up to leave a comment.

Articles

Change theme settings