Почему стоит полностью переходить на Ceylon или Kotlin (часть 2)
Комментарии (12)
20 июня 2017 в 11:00
0↑
↓
Судя по описанию, кортежи в ceylon ближе к HList, чем к Tuple.
В связи с чем вопрос: можно ли выполнить map по кортежу с сохранением информации о типах элементов?
Примерно так:scala> (1, "str", 'sym) map elemToList res1: (List[Int], List[String], List[Symbol]) = (List(1),List(str),List('sym))
Полный код по ссылке.20 июня 2017 в 11:46
0↑
↓
Нет, информация о типе не сохранится.
{String+} mapped = [1, 1.0, "2.3"].map((Integer|Float|String element) => element.string);
Результат Map — всегда Iterable. Iterable параметризуется одним типом. Здесь интереснее — в map приходит union тип, уже лямбда, передаваемая в map информацию о строгом типе теряет.
20 июня 2017 в 12:42
0↑
↓
Жаль. Возможность возвращать разный тип результата в зависимости от типа аргумента в HList: map позволяет делать очень интересные вещи.
Например глубокое сравнение для case class:Foo(2, "foo") delta Foo(8, "bar") // 6 :: ("foo", "bar") :: HNil
20 июня 2017 в 12:52
0↑
↓
На всякий случай, чтоб мы говорили об одном и том же.
Вот пример полиморфного метода: https://scalafiddle.io/sf/8Sq4LJv/0(1, 3.14, "str") map increase // (Int, Double, String) = (2,4.140000000000001,str + 1)
20 июня 2017 в 13:53
0↑
↓
Я понял. Скорее всего для кортежей скорее всего можно извратиться. Но костылище будет еще то, не факт что будет иметь смысл такие извращения.
Отдельный метод, принимающий кортеж, у которого обязательства вернуть кортеж строго с такими же типами. Внутри обработчик, принимающий union type, обрабатывающий типы и инкрементящий. И при возврате проверка, что тип кортежа не изменился.
В Ceylon нет многого из того, что есть в Scala. В Kotlin аналогично :).
20 июня 2017 в 14:06
0↑
↓
строго с такими же типами
Тут я все-таки опять не достаточно хороший пример привел. Тип результата не обязан совпадать с типом параметра. Совсем синтетический пример: https://scalafiddle.io/sf/apdsGEh/4.
Также есть zip на HList, возвращающий строго типизированный HList и многие другие методы.
Подобное требуется при метапрограммировании.
20 июня 2017 в 11:33
0↑
↓
Блин, выглядит круто!20 июня 2017 в 11:38
+2↑
↓
Только вот в Kotlin есть 2 вещи, которые реально бесят.
1. Функциональные методы на коллекциях (типа map, filter и т. д.) Iterable после каждого использования создают новый массив. Везде приходится прописывать сначала asSequence. Чем им не нравилась концепция «как в Scala» или хотя бы «как в C#» — без понятия. Очень бы хотелось посмотреть в невинные глазки этого «гения», что это придумал.
2. Жутко засорено глобальное пространство имён. Все эти listOf, println и пр. — все доступны всегда и везде без всяких import.20 июня 2017 в 12:31 (комментарий был изменён)
+2↑
↓
Раз уж в статье идет сравнение со scala, я позволю себе развить эту тему:
18# Типы — объединения (union types)В большинстве языков программирования функция может может возвратить значения строго одного типа.
Это если не учитывать наследование и ADT.
Для возвращения ошибок есть Validationval p = Validated.catchNonFatal { Integer.parseInt("56") }
Если же очень хочется именно объединения, то это потребует не намного больше кода:type F = Boolean :+: Double :+: String :+: Null :+: List[Nothing] :+: CNil def f(): F = { val rnd = util.Random.nextInt(5) rnd match { case 0 => Coproduct[F](false) case 1 => Coproduct[F](1.0) case 2 => Coproduct[F]("2") case 3 => Coproduct[F](null) case _ => Coproduct[F](List.empty) } } val v = f() v.select[Double].foreach { d => println(s"Double $d") } v.select[Int].foreach { i => println(s"Int $i") } // не скомпилируется, v не может быть Int
Кстати, в ceylon можно ли обработать весь тип объединения полиморфным методом с сохранением типов?object headOption extends (List ~> Option) { def apply[T](l: List[T]) = l.headOption } val x = Coproduct[List[Int] :+: List[String] :+: CNil]("str" :: Nil) val res: Option[Int] :+: Option[String] :+: CNil = x map headOption res.select[Option[Int]].isEmpty // true res.select[Option[String]].nonEmpty // true
19# Типы — пересечения (Intersection types)
Это не проблема тех пор, как придумали наследование:
trait CanRun { def run() = println("I am running") } trait CanSwim { def swim() = println("I am swimming") } trait CanFly { def fly() = println("I am flying") } case class Duck() extends CanRun with CanSwim with CanFly case class Pigeon() extends CanRun with CanFly {} case class Chicken() extends CanRun {} case class Fish() extends CanSwim {} def f(arg: CanFly with CanSwim) = { arg.fly(); arg.swim(); } f(Duck()); //OK Duck can swim and fly f(Fish()); //ERROR = fish can swim only
20# Типы — перечисления (enumerated types)
Обычно это называется ADT (algebraic data type).
В scala тоже позволяет компилятору проверять полноту сопоставления с образцом.23# Алиасы типов (Type aliases)
Или на класс, причем класс с конструктором:
Интересный синтаксис, но мне кажется удобнее сделать ссылку на весь компаньон, чтоб получить все методы:type MyList[T] = List[T] val MyList = List val ml1: MyList[Int] = MyList.empty val ml2: MyList[Int] = MyList("str")
21# Кортежи
Выше уже отметил, что лучшим аналогом в scala является HList.
22# Конструирование коллекций (for comprehensions)
Многомерное итерирование поддерживатеся?
val res = for { x <- 1 to 15 by 2 if x % 3 == 0 y <- 1 to 10 by 3 } yield x -> y // Vector((3,1), (3,4), (3,7), (3,10), (9,1), (9,4), (9,7), (9,10), (15,1), (15,4), (15,7), (15,10))
Это только для коллекций или можно обобщить на произвольную монаду?24# Улучшенные дженерики
Вообще хорошо, но нельзя забывать, что это влечет некоторые проблемы при взаимодействии с java. Например с java библиотеками для сериализации в JSON.
24# Метамодель
Звучит очень хорошо. Это полноценный механизм макросов как в scala? Можно при помощи него генерировать новые классы во время компиляции?
#25 Общий дизайн языка
Дискуссионный момент. Кому-то (например мне) может больше нравиться подход scala, где сделан упор на расширяемость языка, что позволяет делать такие сторонние библиотеки как shapeless, cats и другие, привносящие в язык новые концепции.
20 июня 2017 в 13:00
+1↑
↓
22# Конструирование коллекций (for comprehensions)
Многомерное итерирование поддерживатеся?Эквивалентный код:
value res = {for (x in (1:15).by(2)) if (x % 3 == 0) for (y in (1:10).by(3)) x -> y}
Да, это только для коллекций, точнее для того, что можно итерировать. Обобщить на произвольную монаду можно, если ее привести к коллекции :).
24# Метамодель
Звучит очень хорошо. Это полноценный механизм макросов как в scala? Можно при помощи него генерировать новые классы во время компиляции?Нет, даже близко не макросы. Ceylon не поддерживает вообще никакого Compile time метапрограммирования. Только рантайм. На самом деле я сделал в одном проекте кодогенерацию на основе метамодели, но это близко к костылю так как метод кодогенерации я должен сначала скомпилировать, потом дернуть, затем скомпилировать результат.
24# Улучшенные дженерики
Вообще хорошо, но нельзя забывать, что это влечет некоторые проблемы при взаимодействии с java. Например с java библиотеками для сериализации в JSON.На самом деле в основном тут с большим потреблением памяти. Когда в коллекции с дженериками потребовалось пихать миллиарды значений, это съело примерно в 2 раза больше памяти чем на Java коллекциях. Потому пришлось быстренько эти места переписать на Java. Кстати, именно с библиотеками для сериализации именно с улучшенными дженериками проблем то и нет :). Там в самом языке есть хинт — ee режим. Если нужно сериализовать классы, и они помечены определенными аннотациями, соответствующая коллекция будет для Java библиотеки выгляжеть как Java коллекция, и сериализация пройдет. С сериализацией есть действительно проблемы, но в принципе жить можно. Самая очевидная — иерархические класслоадеры. Сериализовать без проблем. А десериализовать — проблема, так как класслоадер библиотеки сериализации не видит классы, вызывающие эту библиотеку. Но есть хинт, ключ --flat-classpath, в результате чего класслоадер будет один и поведение как в Java/
25 Общий дизайн языка
Естественно дискуссионный. На самом деле Scala весьма и весьма крута, и это я постараюсь отразить в следующей статье, наброски которой у меня уже есть. По фичам для написания DSL со Scala тягаться весьма проблематично. Но к Scala нужно привыкать. Ceylon вполне читабелен даже без привыкания.
20 июня 2017 в 13:10
0↑
↓
Спасибо, интересно.22. Немножко громоздко, но можно привыкнуть. Я правильно понимаю, что вот для такого понадобятся дополнительные скобки?
val res = for { x <- 1 to 15 by 2 z = x*x -1 if z % 3 == 0 y <- 1 to 10 by 3 } yield z -> y
Future же к коллекции не привести, или я не прав? Да и потеря типа не приятна.
Есть аналог async/await?
Там, на самом деле более сложный вопрос — как в for работать с Future[Validation[…]] — для асинхронной обработки результата. В scala для этого monad transformers.24. Мне кажется больше проблем в десериализации generic типа — если экземпляр создан в java, то у него не будет метаинформации о конкретном типе с которым он создан. И ceylon код, рассчитывающий на эту информацию будет несколько удивлен.
20 июня 2017 в 13:45
0↑
↓
- Немножко громоздко, но можно привыкнуть. Я правильно понимаю, что вот для такого понадобятся дополнительные скобки?
Вот для такого на самом деле в текущей версии языка придется делать кое что покруче чем скобки:
value res = {for (x in (1..15).by(2)) for (z in {x * x - 1}) if (z % 3 == 0) for (y in (1..10).by(3)) z->y};
То есть для z пришлось имитировать итерированием по последовательности из одного элемента. В будущем обещают let разрешить внутри for comprehensions, пока так.
Future по идее можно привести к коллекции из одного элемента. Примерно таким же способом, как я с z извратился.
async await планируют в будущем, но не знаю сколько лет еще ждать. Я пока вместо asinc await просто rxJava использую. Плюс у меня есть библиотечный async метод, который стартует Java поток. Возвращающий Observable. Соответственно внутри потока могу стартовать еще с помощью async другие потоки, и далее у Observable вызываю blockingFirst. Пока не появится async await в языке — живу вот так.
24
На самом деле Ceylon достаточно неплохо работает с Java коллекциями и в принчипе взаимодействие для программиста незаметное. Правда пока можно нарваться на багу взаимодействия с Java, в результате чего может не скомпилиться и придется искать обходные пути, фича новая и не все крайние условия пойманы. Касается баги в основном преобразования цейлоновских лямбд в Java лямбды — часто это работает, а если не работает приходится по старинке генерировать анонимный класс что громоздко.