Страницы

Поиск по вопросам

Показаны сообщения с ярлыком scala. Показать все сообщения
Показаны сообщения с ярлыком scala. Показать все сообщения

вторник, 25 февраля 2020 г.

Java, Scala, Groovy и проч. что выбрать для разработки десктопа? [закрыт]

#groovy #desktop #java #scala


        
             
                
                    
                        
                            Закрыт. Этот вопрос не по теме. Ответы на него в данный
момент не принимаются.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Переформулируйте вопрос,
чтобы он соответствовал тематике «Stack Overflow на русском».
                        
                        Закрыт 4 года назад.
                                                                                
           
                
        
Есть запрос на написание десктопного мультиплатформенного приложения. Условие язык
должен быть на платформе Java. 
Писать надо быстро, так что надо чтобы язык имел приличную гуйную библиотеку.
Что посоветуете?
По C/C++/Perl и проч. просьба не умничать. У приложения есть довольно большой набор
Java библиотек с бизнес-логикой, так что платформа строго Java - вариантов нет.    


Ответы

Ответ 1



Сами по себе Scala и Groovy пока не содержат в себе отдельных production-ready решений для GUI, кроме оберток над swing. При этом вариантов, вообще говоря, два - SwingBuilder для Groovy и scala.swing для Scala. Если вы хорошо знакомы с Groovy / Scala (и если вы не единственный из команды, кто может этим похвастаться), то можете воспользоваться одним из этих подходов. При этом, очевидно, стоит учитывать время на необходимость разобраться в деталях соответствующих оберток и возможные риски из-за их непродуманности или недоделанности. Очень обидно будет нарваться на UnsupportedOperationException("Not yet implemented") в конце спринта. Если нет, а выбор между vanilla Java / Scala / Groovy появился только благодаря "модности" последних, то работайте через них с java.swing, либо вообще остановитесь на просто Java. Лично я, не будь у меня как минимум года работы со Scala / Groovy / Clojure / Kotlin, не стал браться за разработку бизнес-решения на их базе.

Ответ 2



Наткнулся сегодня на dzone на номер журнала Java Tech Journal, посвященный Groovy, а уже в нем на статью о Grails-подобной платформе для десктопных приложений Griffon. Что дают (если верить статье): Набор утилит командной строки для создания проекта, сборки, упаковки и деплоя MVC Bindings для свойств бинов различные реализации непосредственно GUI (Swing, SWT, JavaFX) расширяемость плагинами Convention over Configuration, управление структурой и жизненным циклом приложения В общем, если есть тяга к экспериментам, я бы взглянул.

Ответ 3



Если надо быстро написать десктопное мультиплатформенное приложение, сегодня это возможно только на Lazarus+freepascal, который к тому же очищает совесть своим openSource. Слова "быстро написать", "java" и "мультиплатформенное приложение" не вяжутся. Сказки про java просто хорошо раскручены.

воскресенье, 9 февраля 2020 г.

Scala. Список футур. Выполнение шаг за шагом

#scala #future


Хола, коллеги!
У меня есть список некоторых сущностей. Они могут быть вложены друг в друга. Например:

case class Entity(id: Long, parentId: Long)

val list = List(
    Entity(1,0),
    Entity(2,1),
    Entity(3,2),
    Entity(4,1),
    Entity(5,0)
)


Мне нужно добавить их в БД. Мой сервис:

val listOfFutures = list map createEntity // createEntity - мой метод,
                                          // который добавляет сущность в БД.
                                          // Возвращает Future[Entity]
Future.sequence( listOfFutures ) map { _ => println("Ok!") }


Проблема в constraints(Как их по-русски назвать?). Получаю ошибку:


  Create failed. No such parent 1 entity exists.


Я думаю, что проблема в том, что метод createEntity выполняется параллельно. Как
заставить этот код выполняться последовательно, сущность за сущностью?
    


Ответы

Ответ 1



Future считается монадой, так как у неё есть последовательный flatMap: list.foldLeft(Future.successful(()))(( prevF, entity) => prevF .flatMap( _ => createEntity(entity)) .map( _ => ()) ) Аналогичный код, используя for-comprehension: list.foldLeft(Future.successful(()))(( prevF, entity) => for { _ <- prevF _ <- createEntity(entity) } yield () ) Объяснение: val entity = Entity(0, 1) createEntity(entity).map( _ => println("Ok!")) Метод map запоминает полученную функцию и выполняет её после того как получит успешный результат Future. Meтод flatMap работает также, только ожидает что полученная функция тоже будет возвращать Future. createEntity(entityOne).flatMap( _ => createEntity(entityTwo)) Таким образом мы соединяем две Future последовательно, т.е. вторая вызовется только после успешного выполнения первой. Отсюда очевидно что мы можем выстраивать длинные последовательные цепочки. createEntity(entityOne) .flatMap( _ => createEntity(entityTwo)) .flatMap( _ => createEntity(entityThree)) .flatMap( _ => createEntity(entityFour)) А результатом будет новая Future. Ок, а как поступить если у нас есть список Future? Так же как мы бы поступили бы например с числами. val listNumbers = List(1, 2, 3, 4, 5) val zeroNumber = 0 def joinNumbers(a: Int, b: Int): Int = a + b listNumbers.foldLeft(zeroNumber)(joinNumbers) Почему foldLeft, а не map? Потому что прибавляем по очереди, и нам нужна сумма предыдущих чисел. Зачем нужен zeroNumber? Ну а вдруг список пустой - вернем 0. Теперь с Future: val listEntities: List[Entity] = ??? val zeroFuture = Future.successful(()) def joinFuture(prevF: Future[Unit], entity: Entity): Future[Unit] = prevF .flatMap(_ => createEntity(entity)) // тут вторая Future .map( _ => ()) listEntities.foldLeft(zeroFuture)(joinFuture) Следующий createEntity выполняется после предыдущих. Зачем нужен zeroFuture - ну а вдруг список пустой. UPD: Попробую подробней расписать почему foldLeft, а не map. Сначала взглянем на map: val listOfFutures = list.map( entity => createEntity(entity)) map просто проходит по листу и вызывает создание Энтити. Он не ждёт пока создание завершится, он просто создаёт новую Future и идёт дальше к следующему элементу листа. Т.е. на каждой итерации нам нужно "нечто" что будет знать о том что на предыдущем шаге Энтити создался: val listOfFutures = list.map( entity => val prevEntityWasCreated = ??? // что-то, что знает о предыдущем Энтити // flatMap заставляет выполнятся ПОСЛЕ prevEntityWasCreated.flatMap(_ => createEntity(entity)) ) Т.е. проходимся по списку и на каждом этапе ждем пока завершиться предыдущий. Что-то подобное нам надо, верно? Точнее такое: val listOfFutures = list.map( (prevEntityWasCreated, entity) => prevEntityWasCreated.flatMap(_ => createEntity(entity)) ) Супер. Второй энтити ждет когда создастся первый, третий ждёт когда создастся второй. А чего ждёт первый? Надо создать пустую Future специально для первой этнити: val zeroFuture = Future.successful(()) val listOfFutures = list.map(zeroFuture)( (prevEntityWasCreated, entity) => prevEntityWasCreated.flatMap(_ => createEntity(entity)) ) Всё отлично, только тип не совпадает: val zeroFuture: Future[Unit] = Future.successful(()) val foo: Future[Entity] = prevEntityWasCreated.flatMap(_ => createEntity(entity)) Просто добавим map и изменим тип на одинаковый val zeroFuture: Future[Unit] = Future.successful(()) val foo: Future[Unit] = prevEntityWasCreated.flatMap(_ => createEntity(entity)).map( _ => ()) Последнии штрих - переименовать новую функцию в foldLeft. list.foldLeft(Future.successful(()))(( prevF, entity) => val foo: Future[Unit] = prevF .flatMap( _ => createEntity(entity)) .map( _ => ()) // возвращаем Future для следующего элемента foo )

пятница, 10 января 2020 г.

saveToCassandra() : IllegalArgumentException: Multiple constructors with the same number of parameters not allowed

#scala #hadoop #cassandra #apache_spark


Что я делаю не так? Здесь выдаёт ошибку при запуске: 


  textfile.saveToCassandra("logs", "logstable")


Весь код:

object App extends java.io.Serializable {

  final val APP_NAME = "SparkBatchTest"

  def main (args: Array[String]) {

    val conf = new SparkConf().setAppName(APP_NAME).setMaster("local[2]")
      .set("spark.cassandra.connection.host", "localhost")
      .set("spark.cassandra.connection.native.port", "9042")
    val sc = new SparkContext(conf)

    val textfile = sc.textFile("hdfs:////tmp/kafka/test/15-12-10/FlumeDat*")

    textfile.foreach(record => seperateFields(record))
    textfile.saveToCassandra("logs", "logstable") //здесь ругается

  }

  def seperateFields(line: String): Tuple5[Object, Object, Object, Object, Object] = {
    println("Waiting...")
    Thread sleep 3000
    println("Saving...")
    val split = line.split(" ").toArray[Object]
    println(line)
    return (split(0) + " " + split(1), if (line contains "Down") "0" else "1", split(5),
split(6), split(4))
  }
}


Перед форматированием содержание файла следующее:

2015-12-10 12:04:48.299 AMP (amp-management-5-sa)[6953]: Aruba RAP-109 a63253686jypmO68380
Down      System Device  ID: 2490    Top > mariscos puerto vallarta   2-Standard


таблица в Cassandra выглядит следующим образом:

datetime            | location | logid   | status              | systemid
---------------------+----------+---------+---------------------+----------
 2015-12-23 15:10:01 |        1 | RAP-109 | a73225704jypmO54359 |    Aruba
 2015-12-23 15:55:54 |        0 | RAP-109 | a62710684jypmO66318 |    Aruba
 2015-12-23 15:10:02 |        1 | RAP-109 |  a2222705jypmO69412 |    Aruba
 2015-12-23 15:09:45 |        1 | RAP-109 | a80296226jypmO21003 |    Aruba
 2015-12-23 15:25:29 |        1 | RAP-109 |  a11170884jypmO9634 |    Aruba
 2015-12-23 15:55:53 |        1 | RAP-109 | a18255961jypmO91299 |    Aruba
 2015-12-23 16:17:27 |        1 | RAP-109 | a41956492jypmO85560 |    Aruba

    


Ответы

Ответ 1



Решено, переписал следующие строки: val textfile = sc.textFile("hdfs:////tmp/kafka/test/15-12-10/FlumeDat*") val res = sc.parallelize(Seq(seperateFields(textfile.first()))) res.saveToCassandra("logs", "logstable", SomeColumns("datetime","location","logid","status","systemid")) и ещё тут чуть-чуть: def seperateFields(line: String): Tuple5[String, String, String, String, String] = {... и всё заработало!

понедельник, 6 января 2020 г.

ignoring option MaxPermSize=512m при запуске Scala

#java #jvm #scala


Установил Play фрэймворк, и при запуске проекта выдается ошибка:


  Java HotSpot(TM) 64-Bit Server VM warning:
  ignoring option MaxPermSize=512m; support was removed in 8.0


Как исправить?


  [error]Server access Error: Connection timed out: connect url=http://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/com.typesafe.sbt/sbt-native-packager/scala_2.10/sbt_0.13/0.6.4/ivys/ivy.xml


Я так понял, он пытается что-то скачать, но доступа нет из-за этой ошибки?

ОС: Windows 8
Версия Play: play-2.2.6
Версия Java: 1.8.

Нельзя ли по-другому это обойти?
    


Ответы

Ответ 1



Первое - это не ошибка, а предупреждение. Второе. Установить прокси в браузере недостаточно, его нужно установить на уровне операционной системы. Вы не указали какую используете ОС и версию Play, поэтому могу предложить воспользоваться общим решением через activator. Запустите его так: activator -Dhttp.proxyHost="your proxyname" -Dhttp.proxyPort="your port" -Dhttps.proxyHost="your proxyname" -Dhttps.proxyPort="your port" Дополнительно может потребоваться установить параметры proxyUser и proxyPassword Обновление Для Windows 8 и Play 2.2.6 можно решить проблему воспользовавшись этой инструкцией в документации, а именно: Еслы вы находитесь за прокси, убедитеcь что установлено для Windows set HTTP_PROXY=http://<логин>:<пароль>@<хост>:<порт> От себя отмечу, что логин и пароль может и не нужен, если прокси его не требует.

Ответ 2



Вы можете попробовать установить offline := true в файле build.sbt. Во многих случаях так работает.

Ответ 3



Все просто, хоть я и сам потратил какое-то время на решение этой задачи: 1-перейти в каталог cd opt/glassfish4/bin/ 2-запустить с ROOT правами sudo ./asadmin start-domain 3-остановка сервера тоже с ROOT правами sudo ./asadmin stop-domain В вашем случае все должно заработать.

Логика и назначение Product trait

#scala


Столкнулся с проблемой непонимания как работает trait Product.
Пример:

List(1,2).productIterator.toList


Возвращает 

List[Any] = List(1, List(2))


Покопавшись в документации пока не смог найти ответа на вопрос почему 2-ой элемент
возвращается как List(2) а не просто 2.
Спасибо.
    


Ответы

Ответ 1



Для начала объясню что такое List, потом объясню как работает productIterator. И так List - это абстрактный класс, а значит создать его экземпляр нельзя. То что выглядит как List - на самом деле один из его наследников. У класса List два наследника - кейс-класс :: и кейс-объект Nil. Давай посмотрим на их реализацию: final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] { override def tail : List[B] = tl override def isEmpty: Boolean = false } case object Nil extends List[Nothing] { override def isEmpty = true override def head: Nothing = throw new NoSuchElementException("head of empty list") override def tail: List[Nothing] = throw new UnsupportedOperationException("tail of empty list") // Removal of equals method here might lead to an infinite recursion similar to IntMap.equals. override def equals(that: Any) = that match { case that1: scala.collection.GenSeq[_] => that1.isEmpty case _ => false } } То, что Nil - это пустой список, знают многие, поэтому рассмотрим кейс-класс ::. Как видишь у него два поля - head и tl. Заметь что head- это один элемент, а tl - коллекция(List). Т.е. когда мы пишем List(1) на самом деле создается такой кейс-класс: ::(1, Nil) ::(head = 1, tl = Nil)//тоже самое с именованными параметрами А когда ты создаешь List(1, 2) на самом деле создается такое: ::(1, ::(2, Nil)) ::(head = 1, tl = ::(2, Nil)) //тоже самое с именованными параметрами Обрати внимание на то, что второй параметр имеет тип List, а значит какого бы ты наследника не передал (:: или Nil) - виден он будет как List. Теперь про productIterator, этот метод возвращает итератор на аргументы класса. В нашем случае у объекта Nil - нету аргументов, а у класса :: есть всего два аргумента - head и tl. Их значения ты и видишь. P.S. А метод productIterator у них есть - благодаря тому, что они кейс-класс и кейс-объект. Полезные материалы: http://www.alessandrolacava.com/blog/scala-case-classes-in-depth/ http://www.scala-lang.org/api/current/scala/collection/immutable/List.html исходники Scala

четверг, 26 декабря 2019 г.

Об алгоритме вывода типов рекурсивных функций

#рекурсия #scala #f# #ocaml #теория_типов


В Scala не реализован вывод типов для рекурсивных функций, в качестве аргумента Кей
Хорстман в своей книжки пишет что "Алгоритм Хиндли-Милнера не стабильно себя ведёт
в языках с ООП". Но, например, в F# и OCaml реализован вывод типов для рекурсивных
функций и возникают вопросы: что же тогда имел ввиду Кей Хорстман? Есть ли примеры
программ на ООП языке в которых алгоритм даёт сбой(ошибка согласованности типов в рантайме
при успешно пройденной проверке типов на этапе компиляции)? Или косяк именно в каких-то
особенностях Scala?
    


Ответы

Ответ 1



Система типов Scala однозначно содержит F \sub. Вероятно содержит намного больше (это сейчас активное направление исследований у нас) но уже в F \sub многие вопросы, и в том числе этот - неразрешимая задача. Ссылка на статью Если честно, я не знаю как это работает в F# и OCaml. Думаю они делают best-effort.

среда, 25 декабря 2019 г.

scala x = y = 1

#scala


Как сделать такое присвоение возможным. = возвращает Unit.
    


Ответы

Ответ 1



val x, y = 1 Вывод: >>x: Int = 1 >>y: Int = 1

Ответ 2



Фактически, x является Unit в этом случае:: var y = 2 var x = y = 1 Может быть прочитан как: var y = 2 var x = (y = 1) и наконец: var x: Unit = () Вы можете перейти к типу x = y = 1 в оболочке REPL без ошибок: var x:Unit = {} var y = 0 x = y = 1

четверг, 12 декабря 2019 г.

Практика в Scala [закрыт]

#scala


        
             
                
                    
                        
                            Closed. This question is opinion-based. It is not currently
accepting answers.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Want to improve this question? Update the question so
it can be answered with facts and citations by editing this post.
                        
                        Closed 4 года назад.
                                                                                
           
                
        
Подскажите, есть ли ресурсы, где можно попрактиковаться в написании кода на Scala?
Можно просто сборники задач по основному функционалу языка. Желательно, чтобы было
освещено побольше проблем, связанных с функциональным программированием. Ссылки на
ресурсы по спортивному программированию, где принимают решения на Scala, тоже категорически
приветствуются.    


Ответы

Ответ 1



Для разгона 99 задачек для Scala - подборка 99 небольших упражнений на выработку идиоматического стиля написания кода, знакомства с классами и методами стандартной библиотеки, короче, чтобы освоиться со Scala. Упражнения для начинающих программистов на Scala от знаковой фигуры в мире функционального программирования, Тони Морриса. Если не знаете, кто это такой - марш читать рассылки scala-user и scala-debate. :) 20 упражнений умеренной сложности для Scala от все того же Тони Морриса. CodeCata - сборник практических, приближенных к реальности, задач по программированию, часто с перечнем тестовых данных. Не привязаны к конкретному языку, хотя часто для демонстрации используется Ruby. В комментариях пишут решения на самых разных языках, от C/C++ до Lisp. Для обретения уверенности Codechef - отличный учебный и олимпиадный ресурс с кучей задач самого различного толка, автоматической валидацией решений (поддерживаются Scala и Haskell), и прочими вкусностями. Sphere Online Judge - аналогичная площадка, с бОльшим уклоном на соревновательную составляющую. Поддерживает Scala и Haskell. Список задачек, которые в небезызвестной в узких гругах нидерландской компании Streamtech предлагают на собеседованиях при приеме на работу. Programming Praxis изначально вышел из сообщества программистов на языке Scheme, но сейчас на ресурсе активно решают задачи на самых различных языках. Особый упор делается на функциональное программирование. Для постижения дзена Все, что перечислено выше - херня. В определенном смысле, конечно :). Эти ресурсы помогут научиться алгоритмическому мышлению, различным алгоритмам, процедурному программированию, может быть, даже, объектно-ориентированному, хотя это вряд ли. Но это не научит вас функциональному программированию (ФП) - ни на йоту. До того, как взяться за Scala, я изучал много языков, в разной мере, конечно: Pascal, C/C++, Perl, Python, Smalltalk, Scheme. И даже начатки функционального программирования по небезызвестному SICP. Но приходится признать: все это мне мало помогло схватить суть функционального программирования. Ну разве что чуть-чуть. Методология ФП мне понравилась сразу, но до последнего я плевался: "Дурацкий Haskell со своим уродливым синтаксисом, идиотской математической терминологией для профессоров - к черту!". И лишь однажды, уже после того, как я начал заниматься Scala, от делать нечего я открыл главу из Душкина, посвященную спискам и еще чему-то там, и... что-то сдвинулось в моей голове - прочитав всю главу с увлечением, я понял, что настало, наконец, время для Haskell. И приходится признать: одни лишь первые 5 глав "Real World Haskell" дали мне НАМНОГО больше, чем все те многочисленные книги по языкам, которые я изучал до этого. Haskell - это реально посвящение в дзен ФП. Без него это ОЧЕНЬ и ОЧЕНЬ трудно сделать, хотя, возможно, вы и будете думать что все знаете и понимаете. Проверено на собственном опыте. Возможно, ваш путь будет более успешен, но вы можете не повторять моих ошибок, и на досуге изучать Haskell. Но это уже другая история :)

Ответ 2



Очень скоро начнется онлайн курс от создателя языка -- Мартина Одерски, насколько мне известно там будут не только видео лекции но и задачки, поэтому очень рекомендую хотя бы взглянуть на него.

вторник, 10 декабря 2019 г.

Зачем нужны Nothing, Null, Nil и None

#scala


Судя по именам все типы означают одно и тоже.
В каких случаях в scala нужно применять: Nothing, Null, Nil и None, чем они отличаются?
    


Ответы

Ответ 1



Хорошо расписано тут. Кое-что добавлю, кое-что подрезюмирую. Тип Nothing - это самый нижний тип. Это значит что переменные с таким типом можно присвоить к абсолютно любому другому типу. Пример 1: def isTen(number: Int): Boolean = if (10 == number) true else throw new Exception("Number is not ten") true имеет тип Boolean, кидание исключения имеет тип Nothing и так как Nothing в иерархии типов наследник Boolean, то в результате получается тип Boolean. Пример 2: def genericIsTen[T](value: T): T = if (10 == value) value else throw new Exception("Generic is not ten") Так как Nothing самый нижний тип, значит в иерархии типов наследник любого типа - вместо Boolean может быть и дженерик. Пример 3: trait Box[+T] case class Full[T](value: T) extends Box[T] object Empty extends Box[Nothing] def boxedIsTen(value: Int): Box[Int] = if (10 == value) Full(value) else Empty Аналогично примерам выше объект с типом Box[Nothing] спокойно присваивается к типу Box[Int] (благодаря ковариантности, т.е. тому плюсику у трейта). Вывод, как видишь тип Nothing используется тогда, когда нужно чтоб тип был принят другим типом. "Перетёрт" другим типом. Тип Null - это почти самый нижний тип. В иерархии типов - он наследник всех объектов, но не наследник всех примитивов. А значит он будет работать для объектов точно также как и Nothing. isTen(null) // НЕ будет работать так как функция хочет примитив genericIsTen((null) // будет работать boxedIsTen(null) // НЕ будет работать так как функция хочет Int Nil - это не тип, это объект Nil. Тип у этого объекта Nil.type. Объект Nil - один из двух наследников класса List и используется он там, где нужен пустой список типа List. Ну и так как он наследник List-а, у него доступны все его методы. sealed abstract class List[+A] { /* ... */ } case object Nil extends List[Nothing] { /* ... */ } final case class ::[B]( /* ... */ ) extends List[B] { /* ... */} def listIsTen(number: Int): List[Int] = if (10 == number) List(number) else Nil None - это тоже не тип, а объект None. Тип у этого объекта None.type. Объект None - один из двух наследников класса Option и используется как пустой вариант Option. sealed abstract class Option[+A] case object None extends Option[Nothing] { /* ... */ } final case class Some[+A](x: A) extends Option[A] { /* ... */ } def optionIsTen(number: Int): Option[Int] = if (10 == number) Some(number) else None

понедельник, 9 декабря 2019 г.

Что такое фантомный тип и как это связано с traits?

#scala #traits #дизайн_языка #теория_типов


Много раз натыкался на термин «фантомный тип», особенно в контексте обсуждения traits
в языке Scala.

Что это такое? При чём здесь traits?
    


Ответы

Ответ 1



Заранее прошу извинить за длину ответа, просто иначе понять, что такое фантомные типы и как они используются, будет трудно, а тема и правда интересная. Предположим, "Роскосмос" попросил написать нас систему управления запуском ракет. "Ракета должна быть заправлена топливом и кислородом, и только после этого можно запускать", - лаконично констатирует техническое задание. Не долго думая, мы пишем следующий код на языке Scala (в функциональном стиле, не в объектно-ориентированном, почему - будет видно впоследствии): object UnsafeRocketModule { case class Rocket private[UnsafeRocketModule] (hasFuel: Boolean, hasO2: Boolean) def createRocket() = Rocket(false, false) def addFuel(r: Rocket) = Rocket(true, r.hasO2) def addO2(r: Rocket) = Rocket(r.hasFuel, true) def launch(r: Rocket) = if (!r.hasFuel || !r.hasO2) throw new Exception("Попытка запустить неподготовленную ракету!") else println("3-2-1... Пуск!") } Модуль UnsafeRocketModule определяет тип Rocket, функцию-конструктор ракет (т.к. конструктор типа Rocket не экспортируется модулем в целях поддержания инкапсуляции), а также функции заправки ракеты топливом, кислородом, и функцию запуска ракеты. Мы также отслеживаем состояние ракеты (заправленность топливом и кислородом) и генерируем исключение при попытке запустить неподготовленную ракету. Загрузим этот модуль в Scala REPL, а затем попробуем ввести определение функции, которая подготавливает ракету (заправляет топливом и кислородом), и запускает ее: scala> def prepareAndLaunchRocket() { | import UnsafeRocketModule._ | launch(addFuel(createRocket())) | } prepareAndLaunchRocket: ()Unit Все скомпилировалось без ошибок, можно работать: scala> prepareAndLaunchRocket() java.lang.Exception: Попытка запустить неподготовленную ракету! at UnsafeRocketModule$.launch(:13) at $anonfun$prepareAndLaunchRocket$2.apply(:8) at $anonfun$prepareAndLaunchRocket$2.apply(:8) ... пропущено ... Вот незадача: в тексте функции prepareAndLaunchRocket мы заправили ракету топливом, но забыли заправить кислородом! Эта типичная для программ ошибка: попытка выполнить операцию над объектом, который находится в неподходящем для этого состоянии. Этот класс ошибок выявляется во время выполнения программы (run time), и очень часто, уже в процессе эксплуатации. Но мы ведь не хотим, чтобы из-за нашей программы взрывались ракеты, только потому, что в коде мы забыли подготовить ее должным образом, а из-за нехватки времени и внимания написали тесты, не покрывающие этот случай, что не позволило выявить проблему до сдачи в эксплуатацию. Поэтому, исправив функцию prepareAndLaunchRocket, мы понимаем, что пришло время что-то кардинально менять. А именно: мы должны придумать способ гарантировать, что перед запуском (то есть, вызовом функции launch()) ракета будет подготовлена должным образом, то есть будут вызваны обе функции addFuel И addO2. Такая гарантия означает, что попытка запуска неподготовленной ракеты должна теперь выявляться во время компиляции (compile time) программы! Иными словами, компилятор не должен нам позволить скомпилировать ошибочное определение prepareAndLaunchRocket, приведенное выше. А это означает, что это определение не должно пройти проверку типов. То есть, мы должны расширить систему типов, принятую в нашем языке программирования. С этого момента начинается магия. Первое, что мы делаем, это переписываем определение типа Rocket следующим образом: object SafeRocketModule { case class Rocket[Fuel, O2] private[SafeRocketModule] () // ... } Что мы поменяли? Тип Rocket приобрел два параметра типа (Fuel и O2), а атрибуты hasFuel и hasO2 были изъяты. Мы перенесли отслеживание состояния заправленности ракеты топливом и кислородом из системы времени выполнения в систему типов, то есть в систему времени компиляции. То, что отслеживалось атрибутами класса отныне отслеживается параметрами этого типа. Теперь добавим следующие вспомогательные типы: sealed trait NoFuel sealed trait HasFuel sealed trait NoO2 sealed trait HasO2 Эти типы будут использоваться нами как значения времени компиляции вместо значений времени выполнения (true и false для атрибутов hasFuel и hasO2) для индикации состояния заправленности ракеты. Это типы-маркеры, существующие только для подстройки системы типов, они не имеют атрибутов, а их значения (new NoFuel { ... }) нами никогда не будут использованы. Такие типы называются фантомными. А traits являются удобным механизмом их определения. С их помощью мы можем переписать оставшиеся функции модуля: def createRocket() = Rocket[NoFuel, NoO2]() def addFuel[O2](r: Rocket[NoFuel,O2]) = Rocket[HasFuel,O2]() def addO2[Fuel](r: Rocket[Fuel,NoO2]) = Rocket[Fuel,HasO2]() def launch(r: Rocket[HasFuel,HasO2]) = println("3-2-1... Пуск!") Обратим внимание: потребности в проверке заправленности ракеты функцией launch() больше нет. Система типов гарантирует, что launch() будет вызвана только для корректно подготовленной ракеты. Кроме того, становится понятно, почему нам следует использовать функциональный стиль, а не объектно-ориентированный. В последнем случае объект Rocket был бы создан единственный раз и функции addFuel, addO2 и launch вызывались бы впоследствии как его методы. Нам же необходимо менять тип ракеты при вызове соответствующих операций, и создавать значения этих типов с нуля, что и предполагает функциональный стиль. Загрузим модуль SafeRocketModule в Scala REPL, а затем снова попробуем ввести ошибочное определение функции prepareAndLaunchRocket: scala> def prepareAndLaunchRocket() { | import SafeRocketModule._ | launch(addFuel(createRocket())) | } :8: error: type mismatch; found : SafeRocketModule.Rocket[SafeRocketModule.NoFuel,SafeRocketModule.NoO2] required: SafeRocketModule.Rocket[SafeRocketModule.NoFuel,SafeRocketModule.HasO2] launch(addFuel(createRocket())) ^ Компилятор не дал ошибочному определению попасть в код программы из-за ошибки во время проверки типов. Исправим определение функции: scala> def prepareAndLaunchRocket() { | import SafeRocketModule._ | launch(addFuel(addO2(createRocket()))) | } prepareAndLaunchRocket: ()Unit scala> prepareAndLaunchRocket() 3-2-1... Пуск! Компиляция прошла успешно, пробный запуск осуществлен без ошибок. Теперь в эксплуатацию попадет гарантированно корректная версия этой функции, даже при отсутствии тестов - верификацию выполнила за нас система типов. Наша новая реализация имеет также и другие преимущества, по сравнению со старой: расширенная нами система типов гарантирует, что ракета будет заправлена топливом и кислородом только один раз, не допуская повторных вызовов функций заправки для ракеты, которая уже была заправлена: scala> addFuel(addFuel(createRocket)) :10: error: type mismatch; found : SafeRocketModule.Rocket[SafeRocketModule.HasFuel,SafeRocketModule.NoO2] required: SafeRocketModule.Rocket[SafeRocketModule.NoFuel,?] addFuel(addFuel(createRocket)) ^ scala> addO2(addO2(createRocket)) :10: error: type mismatch; found : SafeRocketModule.Rocket[SafeRocketModule.NoFuel,SafeRocketModule.HasO2] required: SafeRocketModule.Rocket[?,SafeRocketModule.NoO2] addO2(addO2(createRocket)) ^ Рассмотренный способ применения фантомных типов часто используется при проектировании публичных интерфейсов (API) в таких языках, как Haskell, особенно для сложных интерфейсов. В других языках, с менее развитой системой типов, эти приемы не используются вовсе. К примеру, в Java, насколько мне известно, все это не работает "из коробки", и надо делать уродливую "мумбу-юмбу", чтобы получить похожий результат. Также следует заметить, что это не единственная область применения фантомных типов. Более подробно о последних и о возможных областях применения можно почитать здесь (все источники англоязычные): Phantom Types In Haskell and Scala. Пример с ракетами был адаптирован из этой статьи, где параллельно приводится версия на Haskell. Phantom type (HaskellWiki) A Foundation for Embedded Languages by Morten Rhiger - основательная академическая публикация о фантомных типах и их применениях в Haskell

воскресенье, 8 декабря 2019 г.

Scala с Android - есть у кого-нибудь опыт?

#android #java #scala


Ау? Есть у кого-нибудь опыт со Scala в Android? 
Синтаксис дюже нравится, а по сути все равно внутри все та же вроде Java - так что
наверняка подойдет к Android    


Ответы

Ответ 1



Здесь пара плагинов для сборки приложений на Scala под Android: Build Scala Android apps using Scala. Android Plugin for Gradle, featuring ProGuard and Scala support

Ответ 2



Опыт есть. Основная проблема -- андроид имеет свой формат архивов (вместо jar -- dex). В формате есть ограничение в 65,535 методов/на файл и scala в это ограничение упирается. С этим можно бороться с помощью таких инструментов как proguard, которые вырезают весь неиспользуемый код, и делает еще ряд оптимизаций, но он работает отнюдь не быстро и каждый и так не быстрый цикл "собрал приложение/залил на телефон-эмулятор/протестировал" еще больше затягивается. Раньше (до версии 2.2 когда вроде бы сделали JIT в андроиде) код написанный на scala был медленней чем plain java, теперь это разница не заметна. Заметна разница в потреблении памяти (но трудные места можно написать в императивном стиле -- scala позволяет, или же написать на java -- как тут упоминали, можно в одном проекте замиксовать любые jvm языки, которые компилируются в class файлы, это например Java, Scala, Clojure, Kotlin и другие менее популярные). В остальном разработка довольно приятная (например, плагин про который упоминал @мурмурмур позволяет использовать типизированные ресурсы, а не кастовать каждый ресурс который выбираешь по id).

Ответ 3



У нас в команде пишут. Я правда сам не мобильный разработчик, бэкендом занимаюсь, но с отзывами самих разработчиков можете здесь ознакомиться. Пишут в Android Studio насколько я знаю. С точки зрения пользователя проблем с мобильным приложением замечено особых не было.

Ответ 4



Должно работать. В принципе, можно брать любой язык, который генерит class файлы для java 1.5 - 1.6. А андроид компилятор просто перегоняет их в dex файлы и пакует в архив apk. Более того, можно в одном проекте комбинировать в различных пропорциях разные языки.

Ответ 5



Вот здесь уже это обсуждалось: Добавление поддержки Scala за несколько кликов ... Может поможет :)

Ответ 6



Могу предложить посмотреть вот такую статейку "Hello, World!" на Scala под Android

суббота, 30 ноября 2019 г.

Правило контравариантности в Scala

#scala


Скомпилируется ли следующий код? 

class GenericCellMut[-T](var x:T)


Если да, то почему?
Если нет, то почему, и как сделать так, чтобы скомпилировался? 

Он, естествено, не компилируется. Остается два вопроса - почему и что изменить, чтобы
скомпилировался. 

Пробовал читать статьи про ко/контравариантности, но так для себя ничего и не вынес.
    


Ответы

Ответ 1



Насколько мне известно, Scala проверяет, что все контравариантные типы встречаются только на позициях аргументов, а все ковариантные типы -- на позициях возвращаемых значений. Ваше выражение не проходит проверку из-за того, что наличие поля x подразумевает то, что его можно получить из этого класса, т.е. использовать в ковариантной позиции. Из-за этого не поможет и сделать поле x константным. Насколько я понимаю, вы хотите сделать изменяемую ячейку. Мне не очень понятно, для чего может потребоваться изменяемая ячейка со свойством контравариантности. Единственное, что я могу предложить для того, чтобы этот пример компилировался -- это написать следующий код: class GenericCellMut[+T](val x:T) Это будет неизменяемая ячейка со свойством ковариантности (замечу, что у неё теперь не очень подходящее название). Сделать поле изменяемым нельзя, так как это будет требовать наличия метода установки значения x, что, в свою очередь, требует поставить тип T в контравариантную позицию. Если вы хотите иметь изменяемую ячейку, то придётся сделать её инвариантной: class GenericCellMut[T](val x:T)

среда, 27 ноября 2019 г.

Как же можно программировать без переменных? Вопрос о функциональщине)

#scala #haskell #f#


Пока занимаюсь повторением Python и С, и все никак не дойдут руки до Haskell. А вопрос
не покидает голову уже долгое время) Может как то вкратце можно на него ответить?
    


Ответы

Ответ 1



Трагедия современных программистов заключается в том, что, изучив школьную математику, и приступая к программированию, они пытаются понять, почему запись x = x + 1 имеет смысл. Когда они это понимают, им сложно научиться мыслить «как раньше». Дмитрий Сошников говорит, что в это время в них умирает функциональный программист. Тем не менее, вспомнить можно, и это не так трудно. Попробуйте решить любое уравнение на бумаге и вы сделаете это в функциональном стиле. Попробуйте вычислить факториал. Вы увидите, что вам не нужна переменная-аккумулятор. Примером переменной-аккумулятора являются классические счёты, и там действительно возникает такое понятие, как состояние. Функциональная запись настолько привычна, что она присутствует во всех императивных языках, кроме некоторых эзотерических. Посмотрите на присваивание g = a * b + c * d + e * f; и скажите, в каком порядке будут выполняться умножения в правой части? Сначала a * b, в конце e * f, или наоборот? a * b и c * d это операнды оператора +, и в таких языках, как C и C++ порядок вычисления операндов не определён. Благодаря этому, компилятор может оптимизировать код. Например, если где-то выше c * d уже вычислили, и значение переменных не поменялось, можно подставить готовое произведение. То, что порядок вычисления не определён, свидетельствует о функциональном стиле. Это он и есть. Если вы захотите вычислить выражение императивно, вам придётся разбить его шаги, или писать на языках Fort или PostScript. Наконец, ещё один способ, «почуствовать» вкус функционального стиля, это записывать вычисления в Excel. Например, не пользуясь функцией SIN сделайте лист для расчёта синусов. Это занимательное задание. Здесь тоже вы определяете только зависимости между ячейками, но не определяете, в каком порядке они вычисляются. Excel, конечно, не Тьюринг-полный вычислитель, тем не менее, он позволяет решить многие практические задачи.

Ответ 2



Циклы Для реализации циклических алгоритмов, как вы совершенно верно заметили, в функциональном программировании используется рекурсия. Например: map f [] = [] map f (x:xs) = f x : map f xs Функция map проходит по всему списку и преобразовывает каждый элемент с помощью функции f. Довольно просто. На практике, однако, рекурсия непосредственно используется редко. Как правило функциональные программы выражают циклические операции через уже существующие библиотечные примитивы, такие как функция map приведённая выше. Что-то в таком роде: numbers = [1,2,3,4,5] strings = show <$> numbers Здесь оператор <$> - это псевдоним функции map (стандартный в Haskell), так что выражение show <$> numbers читается как "применить функцию show к каждому элементу списка numbers". Для более сложных случаев существуют более общие аналоги, такие как fold, for, mapM и т.п. Самые простейшие реализованы с помощью рекурсии, а более сложные строятся на простейших. Мутация "Мутацией" в функциональном программировании (и вообще в программировании) называется изменение содержимого ячейки памяти. В языке Haskell мутация в принципе возможна (см. ниже), но на практике её стараются избегать, прибегая к ней лишь в самых крайних случаях. Вместо непосредственного изменения данных, в функциональном программировании принято создавать новые структуры на основе старых. Например: replaceFirst (x:xs) newX = newX : xs Функция replaceFirst "заменяет" первый элемент списка на новый: xs = [1,2,3,4] ys = replaceFirst xs 42 -- Теперь ys = [42,2,3,4] (внимательный читатель заметит, что функция replaceFirst неприменима к пустому списку, но это не относится к текущей дискуссии) Логически операцию replaceFirst xs 42 можно понимать как "заменить первый элемент списка xs на 42", но на практике происходит не совсем это. На самом деле старый список xs жив и здоров, а список ys - это совершенно новый список. Теперь у нас есть выбор: мы можем выкинуть список xs за ненадобностью, чтобы сборщик мусора его потом удалил, или мы можем оставить его "про запас", на будущее. Такой подход открывает очень широкие возможности. Например, нам ничего не стоит хранить историю изменений - или просто для учёта, или с целью вернуться назад во времени, или ещё для чего. Более интересная возможность - распараллеливание программы. Если мы уверены, что старые данные никогда не изменятся, то нет смысла городить семафоры и прочую синхронизацию - можно просто запустить программы параллельно, и всё! - компилятор гарантирует, что они никогда не будут мешать друг другу модифицируя общие данные. Синхронизация данных - краеугольный камень параллельного программирования и основной источник багов. Если вы в этом сомневаетесь, поищите "defensive copy". Q: Вы что же, хотите сказать, что при каждом изменении надо выделять целую новую структуру данных?! Эдак же никакой памяти не напасёшься! Нет, не нужно. Для того, чтобы этого не происходило, в функциональном программировании используются специальные структуры данных, называемые по-английски "persistent data structures". Это такие структуры данных, в которых каждое следующее обновление построено на основе предыдущей версии. Самый простой пример - односвязный список. Представьте себе список: каждый элемент содержит ссылку (указатель) на предыдущий, а самый последний - ссылку на "конец". Как-то так: (1) --> (2) --> (3) --> (4) --> (end) В примере с replaceFirst (см. выше) этот список у меня назывался xs: (1) --> (2) --> (3) --> (4) --> (end) xs _/ Когда я вызвал функцию replaceFirst, она взяла "хвост" списка (начиная с двойки) и присоединила к нему новую "голову" 42. Этот "новый" список я назвал ys: ys _ \ (42)_ \ (1) --> (2) --> (3) --> (4) --> (end) xs _/ Отсюда видно, что для того, чтобы "заменить" первый элемент списка, вовсе не нужно выделять новый блок памяти. Единственная новая ячейка - это сам новый первый элемент, и всё. Все остальные элементы остались на месте. Более того: если я теперь решу, что список xs мне больше не нужен, то сборщику памяти придётся убирать только число 1, поскольку все остальные элементы списка всё ещё задействованы. Также обратите внимание на то, что такой подход возможен только тогда, когда точно известно, что элементы списка никогда не будут изменены. Только при этом условии я могу рассматривать списки xs и ys как два разных списка, хотя они и занимают частично одну и ту же память. Конечно, односвязный список - это самая простая структура данных. В более сложных случаях используются более сложные структуры - в основном деревья всех видов и размеров. И разумеется, есть ситуации, при которых такой подход всё же менее эффективен, чем непрерывный массив в памяти. Однако в 99% случаев разница в быстродействии совершенно ничтожна и не идёт ни в какое сравнение с повышенной стабильностью и простотой разработки. Для оставшегося 1% случаев, однако, даже Haskell позволяет работать с непрерывными массивами - только делает он это гораздо более безопасным образом (см. ниже) Императивные программы "Императивными" называются программы, выраженные в виде последовательности шагов. Это в противовес "декларативным", которые выражены в виде отношений между частями - функциями и данными. Все примеры, приведённые мной выше - "декларативные". Они все написаны в стиле "xs - это ys, где первый элемент заменён на 42" и т.п. На первый взгляд может показаться, что разницы в принципе нет, но это не так. Главная разница в том, что в императивной программе есть порядок действий, а в декларативной - нет. Я указал, как значение xs связано с ys, но не указал, в каком порядке мне хотелось бы, чтобы эта связь вычислялась. Компилятор разберётся за меня сам. Но это не всегда целесообразно. Хотя "в принципе" любую программу можно выразить и тем и другим способом, иногда чисто с человечксой точки зрения удобнее думать о программе как о функциональных отношениях, а иногда - как о последовательности шагов. Например, рассмотрим ту же игру. Представьте, что у меня есть некий тип данных Game и набор операций к нему: type Game = ... lookForMonsters :: Game -> (Game, Monster) shoot :: Monster -> Game -> (Game, Damage) Обратите внимание, что каждая функция, в дополнение к своему "основному" результату (Monster или Damage), возвращает ещё и "новое", изменённое состояние игры. Это "новое" состояние должно быть передано в следующую функцию, за счёт чего и возникает определенный порядок исполнения этих функций, как-то так: game0 = createGame (game1, monster) = lookForMonsters game0 (game2, damage) = shoot monster game1 message = "Inflicted " ++ show damage ++ " points of damage Поскольку значение game1 необходимо для вызова shoot, но является результатом lookForMonsters, выходит, что lookForMonsters нужно вызвать сначала, а shoot - потом. В этом и есть определение порядка вызовов. Такой стиль, однако, весьма утомителен и создаёт опасность опечаток. Кроме того, обратите внимание на закономерность: все строчки очень похожи друг на друга по форме. Функциональный программист живёт закономерностями. Он их находит и безжалостно обобщает, чтобы сделать свой код более простым и понятным. В данном случае давайте завернём эту закономерность в пару функций, которые я назову bind и return: bind x f = \game -> let (game1, y) = x game in f y game1 return x = \game -> x Не вдавайтесь слишком глубоко в подробности, если это непонятно. Достаточно знать, что функция bind "связывает вместе" две операции над игрой, передавая состояние игры из результата первой операции в аргумент второй; а функция return создаёт операцию, которая игнорирует состояние игры и возвращает какое-то конкретное значение. С помощью этих функций я могу записать мою программу так: program = bind lookForMonsters (\monster -> bind (shoot monster) (\damage -> return ("Inflicted " ++ show damage ++ " points of damage) ) ) (finalGameState, message) = program game0 Можно сделать чуть красивее, поиграв с переводами строк и отступами: program = bind lookForMonsters (\monster -> bind (shoot monster) (\damage -> return ("Inflicted " ++ show damage ++ " points of damage))) Или ещё чуть красивее, если завести оператор >>=, который будет синонимом для bind: x >>= f = bind x f program = lookForMonsters >>= \monster -> shoot monster >>= \damage -> return ("Inflicted " ++ show damage ++ " points of damage) Такая схема определения порядка исполнения называется "монад", и она настолько широко применяется в функциональных языках, что большинство компиляторов предоставляет для неё специальный синтаксический сахар. В Haskell эту программу можно записать так: program = do monster <- lookForMonsters damage <- shoot monster return ("Inflicted " ++ show damage ++ " points of damage) Слово do и стрелки влево <- преобразуются компилятором в последовательные вызовы функции bind. После всех этих преобразований, посмотрите, что у нас получилось: программа выглядит как "обычная" императивная программа на каком-то языке вроде C или Python, и смотрится так, как будто мы "изменяем" состояние игры с каждым шагом. Но на самом деле программа целиком состоит из "чистых" функций без мутации и побочных эффектов. Наша программа принимает изначальное сотояние игры на вход и выдаёт конечное состояние на выходе, и не зависит больше ни от чего. Таким образом мы получили возможность писать программу в виде последовательности шагов, но при этом сохранили все преимущества отсутствия мутации. Обобщение Взгляните ещё раз на программу, записанную мной выше в "хитром" виде: program = do monster <- lookForMonsters damage <- shoot monster return ("Inflicted " ++ show damage ++ " points of damage) Обратите внимание, что в этой программе нигде не упоминается сам тип Game - то есть сама программа в общем-то не в курсе, что она работает с игрой. Всё знание об игре "спрятано" в функции bind и в примитивных операциях lookForMonsters и shoot. Кстати, сами эти операции тоже могут и не быть примитивными - они сами по себе могут быть записаны в таком же стиле, как и моя программа, и основаны на ещё более примитивных операциях. Но это просто к слову. А следующая мысль вот какая: если сама по себе программа не зависит от того, как именно реализована идея "порядка операций" (а именно эту идею реализует функция bind), значит мы можем изменить эту реализацию не трогая при этом саму программу. Наша реализация может быть простой и "игрушечной", как я привёл выше, или, если нам понядобятся какие-то дополнительные возможности, например прерывание при ошибках или журналирование, - мы можем добавить эти возможности в функцию bind, и программа этого не заметит. Эта мысль приводит к идее, что "монады" могут быть разные. Более того, монады можно собирать из блоков с нужной функциональностью, как из кубиков, и при этом писать программы, в которых каждая операция пользуется только теми кубиками, которые ей важны, а остальная программа о них не знает. Ввод-вывод И вот теперь, когда у нас есть понятие "монад", который есть абстрактное выражение порядка операций, приходит следующая мысль: а что если изобрести такой монад, который написан не на самом языке Haskell, а реализован внутри компилятора? И что если организовать этот монад таким образом, что функция bind может творить всякие безобразия, нарушать правила, и вообще быть написана на языке C? Программы, написанные в таком монаде, будут выглядеть совершенно невинно - как последовательность функций, пропущенных через bind, так же, как и примеры выше. Но зато примитивные операции будут для реального ввода-вывода. Вот, например, операция getLine: getLine :: IO String Она производит "побочный эффект" в реальном мире - читает текстовую строку с консоли. Но эта строка "завёрнута" в тип IO. Логически можно это рассматривать как ячейку, в которой находится строка, но "достать" эту строку оттуда никак нельзя. Единственное, что можно с ней сделать - это направить её в качестве параметра в другую функцию с помощью вызова bind. Но вот незадача: в результате этого вызова опять обязательно получится тип IO (возможно не со строкой внутри, а с чем-то другим), и оттуда достать значение опять нельзя. Можно только направить его в третью функцию опять с помощью bind - и так далее. В результате выходит, что как только мы произвели ввод-вывод (т.е. вызвали одну из IO-функций), единственное, что можно сделать с результатом - это произвести ещё ввод-вывод, и ещё, и ещё. Но это как раз ничего страшного, потому что точка входа программы на Haskell - это как раз и есть цепь операций ввода-вывода, скреплённых через bind. Например: main = bind getLine (\name -> bind (putStrLn ("Hello, " ++ name ++ "!")) (\_ -> return 0 ) ) Или то же самое с использованием синтаксиса do: main = do name <- getLine putStrLn ("Hello, " ++ name ++ "!") return 0 Вот таким образом и происходит ввод-вывод в Haskell. Самые примитивные операции, такие как getLine и putStrLn, написаны не на самом Haskell, а на C. Это примерно так же, как вызов CreateProcess в Windows или fork в UNIX реализованы не самой программой, а операционной системой. Самые примитивные примитивы всегда находятся на один уровень ниже, без этого никак. Альтернативный взгляд на эту структуру такой: можно рассматривать программу на Haskell как программу, которая сама по себе ввода-вывода не делает, но зато генерирует программу на другом языке, которая уже делает ввод-вывод. Я не большой фанат такого подхода, но его часто используют при объяснениях. Обратите внимание, что, в принципе, можно абсолютно всю программу на Haskell написать непосредственно в IO, и таким образом пользоваться всеми теми же "благами", что и программа на C или Python. Однако это совершенно убьёт всю идею. Смысл языка Haskell в том, что программы можно писать гораздо более безопасные, выразительные и быстрые, если только отказаться от всяких безобразий типа IO и дать компилятору возможность оптимизировать программу. Поверьте, компилятор (особенно компилятор Haskell) справляется с этим на много порядков лучше нас с вами. Поэтому реальные программы обычно написаны в два слоя: самое ядро программы, где вся логика и все вычисления, написано на "чистых" функциях, и только самая внешняя, очень тонкая оболочка имеет дело с IO. В настоящее время этот принцип, называемый по-английски "functional core, imperative shell", стал широко известен и за пределами функционального программирования - как, впрочем, и многие другие функциональные идеи (например, LINQ, const, then, async, auto и т.п.) "Настоящая" мутация Теперь, когда мы познакомились с монадом IO и узнали, что его примитивы могут творить всякие безобразия, следующее что приходит в голову - что эту лазейку можно использовать в случаях, когда Haskell таки недостаточно быстр, требует слишком много памяти, или не устраивает по каким-то другим причинам. В этих случаях можно написать библиотеку на C и импортировать её в Haskell через IO. Один пример такого подхода - массивы с поддержкой "настоящей" мутации. Эта библиотека предоставляет IO-примитивы для создания, изменения и прочих операций над массивами: main = do arr <- newArray (1,10) 42 writeArray arr 8 5 a <- readArray arr 1 print a Как я уже отметил выше, такие возможности используются только в самых крайних случаях - когда Haskell сам по себе "не дотягивает". Я бы сравнил это с написанием программы на C, но с вкраплениями ассемблера в самых чувствительных местах. Программа получается больше, непонятнее, более хрупкой, и ограничивает возможность оптимизации, так как компилятор не знает, что именно происходит внутри этих примитивов, и потому боится их трогать.

Ответ 3



Программировать без переменных - невозможно. Во первых, если мы хотим изменить во всём скрипте одно значение, то нам придётся во всей программе изменять это значение (а в скрипте могут быть тысячи, десятки тысяч строк). Во вторых, если нам нужно использовать знаки действий (+, -, *, :), то без переменных не обойтись.

понедельник, 8 июля 2019 г.

Scala параметры с двоеточием при вызове метода

final def apply(block: => Result): Action[AnyContent] = apply(BodyParsers.utils.ignore(AnyContentAsEmpty: AnyContent))(_ => block)
Кто-нибуть знает что здесь означает AnyContentAsEmpty: AnyContent ? AnyContentAsEmpty я так понимаю это объект, который передается в BodyParsers.utils.ignore...? Даже не знаю как гуглить это AnyContentс двоеточием.


Ответ

Это называется ascription
Используется когда ты хочешь "поднять" тип до родительского. Пример аскрипции при инициализации:
// some имеет тип Some[Int] val some = Some(43) // some имеет тип Option[Int] val some: Option[Int] = Some(43) // вот тут аскрипция - и some имеет тип Option[Int] val some = Some(43): Option[Int]
Ещё используют при вызове функции - как твоём примере. Дело в том, что функция ignore дженерик, т.е. её параметр типа А инициализируется типом из аргумента. От этого зависит тип который она вернёт.
BodyParsers.utils.ignore(AnyContentAsEmpty) // res1: play.api.mvc.BodyParser[play.api.mvc.AnyContentAsEmpty.type] BodyParsers.utils.ignore(AnyContentAsEmpty: AnyContent) //res0: play.api.mvc.BodyParser[play.api.mvc.AnyContent]
Вот другой часто используемый пример -
вот так - НЕ скомпилируется
val list = List(1)
list.foldLeft(Right(0))( (accumulator, currentNum) => { if (currentNum == 0) Left(new ArithmeticException) else accumulator.right.map(_ + currentNum) })
Из-за того что я передал Right(0) в первые скобки - то тип accumulator стал Right[Int], и вся функция ждёт Right[Int]
А если я "подниму" тип до Either с помощью аскрипции, то могу в функции возвращать и Right и Left
list.foldLeft(Right(0): Either[Exception, Int])( (accumulator, currentNum) => { if (currentNum == 0) Left(new ArithmeticException) else accumulator.right.map(_ + currentNum) })
Ссылки: дока, стековерфлоу на английском.

среда, 12 июня 2019 г.

Сокращенная запись анонимных функций Scala

Изучая Scala нарвался на отличную возможность сократить запись анонимных функций, к примеру: List(2,3,1).sortWith(_ < _) вместо List(2,3,1).sortWith((a,b) => a < b) Не могу найти аналог для одного аргумента, например: List(2,3,1).map(_) //Вызовет ошибку компиляции вместо List(2,3,1).map((a) => a) Понимаю,что это не самая важная штука в Скала, но можно ли как-то записать такую функцию в сокращенном виде?


Ответ

Функция, которая возвращает свой аргумент, называется Тождественное отображение. Такая функция есть в Scala - это identity. Но сокращенной записи для неё нет.
scala> List(1,2,3).map(identity) res1: List[Int] = List(1, 2, 3)

вторник, 14 мая 2019 г.

Java, Scala, Groovy и проч. что выбрать для разработки десктопа? [закрыт]

Есть запрос на написание десктопного мультиплатформенного приложения. Условие язык должен быть на платформе Java. Писать надо быстро, так что надо чтобы язык имел приличную гуйную библиотеку. Что посоветуете? По C/C++/Perl и проч. просьба не умничать. У приложения есть довольно большой набор Java библиотек с бизнес-логикой, так что платформа строго Java - вариантов нет.


Ответ

Сами по себе Scala и Groovy пока не содержат в себе отдельных production-ready решений для GUI, кроме оберток над swing. При этом вариантов, вообще говоря, два - SwingBuilder для Groovy и scala.swing для Scala Если вы хорошо знакомы с Groovy / Scala (и если вы не единственный из команды, кто может этим похвастаться), то можете воспользоваться одним из этих подходов. При этом, очевидно, стоит учитывать время на необходимость разобраться в деталях соответствующих оберток и возможные риски из-за их непродуманности или недоделанности. Очень обидно будет нарваться на UnsupportedOperationException("Not yet implemented") в конце спринта. Если нет, а выбор между vanilla Java / Scala / Groovy появился только благодаря "модности" последних, то работайте через них с java.swing, либо вообще остановитесь на просто Java Лично я, не будь у меня как минимум года работы со Scala / Groovy / Clojure / Kotlin, не стал браться за разработку бизнес-решения на их базе.

четверг, 18 апреля 2019 г.

Scala. Список футур. Выполнение шаг за шагом

Хола, коллеги! У меня есть список некоторых сущностей. Они могут быть вложены друг в друга. Например:
case class Entity(id: Long, parentId: Long)
val list = List( Entity(1,0), Entity(2,1), Entity(3,2), Entity(4,1), Entity(5,0) )
Мне нужно добавить их в БД. Мой сервис:
val listOfFutures = list map createEntity // createEntity - мой метод, // который добавляет сущность в БД. // Возвращает Future[Entity] Future.sequence( listOfFutures ) map { _ => println("Ok!") }
Проблема в constraints(Как их по-русски назвать?). Получаю ошибку:
Create failed. No such parent 1 entity exists.
Я думаю, что проблема в том, что метод createEntity выполняется параллельно. Как заставить этот код выполняться последовательно, сущность за сущностью?


Ответ

Future считается монадой, так как у неё есть последовательный flatMap
list.foldLeft(Future.successful(()))(( prevF, entity) => prevF .flatMap( _ => createEntity(entity)) .map( _ => ()) )
Аналогичный код, используя for-comprehension:
list.foldLeft(Future.successful(()))(( prevF, entity) => for { _ <- prevF _ <- createEntity(entity) } yield () )
Объяснение:
val entity = Entity(0, 1) createEntity(entity).map( _ => println("Ok!"))
Метод map запоминает полученную функцию и выполняет её после того как получит успешный результат Future
Meтод flatMap работает также, только ожидает что полученная функция тоже будет возвращать Future
createEntity(entityOne).flatMap( _ => createEntity(entityTwo))
Таким образом мы соединяем две Future последовательно, т.е. вторая вызовется только после успешного выполнения первой.
Отсюда очевидно что мы можем выстраивать длинные последовательные цепочки.
createEntity(entityOne) .flatMap( _ => createEntity(entityTwo)) .flatMap( _ => createEntity(entityThree)) .flatMap( _ => createEntity(entityFour))
А результатом будет новая Future. Ок, а как поступить если у нас есть список Future? Так же как мы бы поступили бы например с числами.
val listNumbers = List(1, 2, 3, 4, 5)
val zeroNumber = 0 def joinNumbers(a: Int, b: Int): Int = a + b
listNumbers.foldLeft(zeroNumber)(joinNumbers)
Почему foldLeft, а не map? Потому что прибавляем по очереди, и нам нужна сумма предыдущих чисел. Зачем нужен zeroNumber? Ну а вдруг список пустой - вернем 0.
Теперь с Future:
val listEntities: List[Entity] = ???
val zeroFuture = Future.successful(()) def joinFuture(prevF: Future[Unit], entity: Entity): Future[Unit] = prevF .flatMap(_ => createEntity(entity)) // тут вторая Future .map( _ => ())
listEntities.foldLeft(zeroFuture)(joinFuture)
Следующий createEntity выполняется после предыдущих. Зачем нужен zeroFuture - ну а вдруг список пустой. UPD:
Попробую подробней расписать почему foldLeft, а не map. Сначала взглянем на map
val listOfFutures = list.map( entity => createEntity(entity))
map просто проходит по листу и вызывает создание Энтити. Он не ждёт пока создание завершится, он просто создаёт новую Future и идёт дальше к следующему элементу листа.
Т.е. на каждой итерации нам нужно "нечто" что будет знать о том что на предыдущем шаге Энтити создался:
val listOfFutures = list.map( entity => val prevEntityWasCreated = ??? // что-то, что знает о предыдущем Энтити
// flatMap заставляет выполнятся ПОСЛЕ prevEntityWasCreated.flatMap(_ => createEntity(entity)) )
Т.е. проходимся по списку и на каждом этапе ждем пока завершиться предыдущий. Что-то подобное нам надо, верно?
Точнее такое:
val listOfFutures = list.map( (prevEntityWasCreated, entity) =>
prevEntityWasCreated.flatMap(_ => createEntity(entity)) )
Супер. Второй энтити ждет когда создастся первый, третий ждёт когда создастся второй. А чего ждёт первый? Надо создать пустую Future специально для первой этнити:
val zeroFuture = Future.successful(()) val listOfFutures = list.map(zeroFuture)( (prevEntityWasCreated, entity) =>
prevEntityWasCreated.flatMap(_ => createEntity(entity)) )
Всё отлично, только тип не совпадает:
val zeroFuture: Future[Unit] = Future.successful(()) val foo: Future[Entity] = prevEntityWasCreated.flatMap(_ => createEntity(entity))
Просто добавим map и изменим тип на одинаковый
val zeroFuture: Future[Unit] = Future.successful(()) val foo: Future[Unit] = prevEntityWasCreated.flatMap(_ => createEntity(entity)).map( _ => ())
Последнии штрих - переименовать новую функцию в foldLeft
list.foldLeft(Future.successful(()))(( prevF, entity) =>
val foo: Future[Unit] = prevF .flatMap( _ => createEntity(entity)) .map( _ => ()) // возвращаем Future для следующего элемента foo )

вторник, 26 февраля 2019 г.

saveToCassandra() : IllegalArgumentException: Multiple constructors with the same number of parameters not allowed

Что я делаю не так? Здесь выдаёт ошибку при запуске:
textfile.saveToCassandra("logs", "logstable")
Весь код:
object App extends java.io.Serializable {
final val APP_NAME = "SparkBatchTest"
def main (args: Array[String]) {
val conf = new SparkConf().setAppName(APP_NAME).setMaster("local[2]") .set("spark.cassandra.connection.host", "localhost") .set("spark.cassandra.connection.native.port", "9042") val sc = new SparkContext(conf)
val textfile = sc.textFile("hdfs:////tmp/kafka/test/15-12-10/FlumeDat*")
textfile.foreach(record => seperateFields(record)) textfile.saveToCassandra("logs", "logstable") //здесь ругается
}
def seperateFields(line: String): Tuple5[Object, Object, Object, Object, Object] = { println("Waiting...") Thread sleep 3000 println("Saving...") val split = line.split(" ").toArray[Object] println(line) return (split(0) + " " + split(1), if (line contains "Down") "0" else "1", split(5), split(6), split(4)) } }
Перед форматированием содержание файла следующее:
2015-12-10 12:04:48.299 AMP (amp-management-5-sa)[6953]: Aruba RAP-109 a63253686jypmO68380 Down System Device ID: 2490 Top > mariscos puerto vallarta 2-Standard
таблица в Cassandra выглядит следующим образом:
datetime | location | logid | status | systemid ---------------------+----------+---------+---------------------+---------- 2015-12-23 15:10:01 | 1 | RAP-109 | a73225704jypmO54359 | Aruba 2015-12-23 15:55:54 | 0 | RAP-109 | a62710684jypmO66318 | Aruba 2015-12-23 15:10:02 | 1 | RAP-109 | a2222705jypmO69412 | Aruba 2015-12-23 15:09:45 | 1 | RAP-109 | a80296226jypmO21003 | Aruba 2015-12-23 15:25:29 | 1 | RAP-109 | a11170884jypmO9634 | Aruba 2015-12-23 15:55:53 | 1 | RAP-109 | a18255961jypmO91299 | Aruba 2015-12-23 16:17:27 | 1 | RAP-109 | a41956492jypmO85560 | Aruba


Ответ

Решено, переписал следующие строки:
val textfile = sc.textFile("hdfs:////tmp/kafka/test/15-12-10/FlumeDat*")
val res = sc.parallelize(Seq(seperateFields(textfile.first()))) res.saveToCassandra("logs", "logstable", SomeColumns("datetime","location","logid","status","systemid"))
и ещё тут чуть-чуть:
def seperateFields(line: String): Tuple5[String, String, String, String, String] = {...
и всё заработало!

вторник, 12 февраля 2019 г.

Логика и назначение Product trait

Столкнулся с проблемой непонимания как работает trait Product. Пример:
List(1,2).productIterator.toList
Возвращает
List[Any] = List(1, List(2))
Покопавшись в документации пока не смог найти ответа на вопрос почему 2-ой элемент возвращается как List(2) а не просто 2. Спасибо.


Ответ

Для начала объясню что такое List, потом объясню как работает productIterator
И так List - это абстрактный класс, а значит создать его экземпляр нельзя. То что выглядит как List - на самом деле один из его наследников. У класса List два наследника - кейс-класс :: и кейс-объект Nil
Давай посмотрим на их реализацию:
final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] { override def tail : List[B] = tl override def isEmpty: Boolean = false }
case object Nil extends List[Nothing] { override def isEmpty = true override def head: Nothing = throw new NoSuchElementException("head of empty list") override def tail: List[Nothing] = throw new UnsupportedOperationException("tail of empty list") // Removal of equals method here might lead to an infinite recursion similar to IntMap.equals. override def equals(that: Any) = that match { case that1: scala.collection.GenSeq[_] => that1.isEmpty case _ => false } }
То, что Nil - это пустой список, знают многие, поэтому рассмотрим кейс-класс ::. Как видишь у него два поля - head и tl. Заметь что head- это один элемент, а tl - коллекция(List). Т.е. когда мы пишем
List(1)
на самом деле создается такой кейс-класс:
::(1, Nil) ::(head = 1, tl = Nil)//тоже самое с именованными параметрами
А когда ты создаешь List(1, 2) на самом деле создается такое:
::(1, ::(2, Nil))
::(head = 1, tl = ::(2, Nil)) //тоже самое с именованными параметрами
Обрати внимание на то, что второй параметр имеет тип List, а значит какого бы ты наследника не передал (:: или Nil) - виден он будет как List
Теперь про productIterator, этот метод возвращает итератор на аргументы класса. В нашем случае у объекта Nil - нету аргументов, а у класса :: есть всего два аргумента - head и tl. Их значения ты и видишь.
P.S. А метод productIterator у них есть - благодаря тому, что они кейс-класс и кейс-объект.
Полезные материалы:
http://www.alessandrolacava.com/blog/scala-case-classes-in-depth/ http://www.scala-lang.org/api/current/scala/collection/immutable/List.html исходники Scala

четверг, 29 ноября 2018 г.

Об алгоритме вывода типов рекурсивных функций

В Scala не реализован вывод типов для рекурсивных функций, в качестве аргумента Кей Хорстман в своей книжки пишет что "Алгоритм Хиндли-Милнера не стабильно себя ведёт в языках с ООП". Но, например, в F# и OCaml реализован вывод типов для рекурсивных функций и возникают вопросы: что же тогда имел ввиду Кей Хорстман? Есть ли примеры программ на ООП языке в которых алгоритм даёт сбой(ошибка согласованности типов в рантайме при успешно пройденной проверке типов на этапе компиляции)? Или косяк именно в каких-то особенностях Scala?


Ответ

Система типов Scala однозначно содержит F \sub. Вероятно содержит намного больше (это сейчас активное направление исследований у нас) но уже в F \sub многие вопросы, и в том числе этот - неразрешимая задача.
Ссылка на статью
Если честно, я не знаю как это работает в F# и OCaml. Думаю они делают best-effort.