Почему стоит полностью переходить на 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.
    Для возвращения ошибок есть Validation
    val 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

        1. Немножко громоздко, но можно привыкнуть. Я правильно понимаю, что вот для такого понадобятся дополнительные скобки?

        Вот для такого на самом деле в текущей версии языка придется делать кое что покруче чем скобки:


            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 лямбды — часто это работает, а если не работает приходится по старинке генерировать анонимный класс что громоздко.

© Habrahabr.ru