Статьи

Scala: предикатная композиция с линзовидными структурами

Я поигрался с несколькими идеями о том, как применить линзовую конструкцию к произвольным коллекциям Scala, которая также сочетает в себе силу композиции предикатов в отличие от более стандартного понятия функциональной композиции. Чтобы задать глупый вопрос, что, если бы мы могли сделать что-то похожее на Объектив Scalaz с «get», «set» и «mod», но применить к коллекции? Что если действия, которые мы предпринимаем, используя эту структуру данных, основаны на некотором предикатном условии? Что если бы мы хотели иметь возможность решать Fizz Buzz, обновляя только части списка без написания функции, которая была бы полностью специфична для Fizz Buzz. Что-то типа…

1
2
3
4
5
6
val fizz = Choose({x: Int => x % 3 == 0})
val buzz = Choose({x: Int => x % 5 == 0})
val fizzbuzz = fizz and buzz
val myList = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
 
val result = fizz set("Fizz", buzz set("Buzz", fizzbuzz set("FizzBuzz", myList)))

Конечно, тип вывода List будет отличаться от типа ввода List, но в идеальном мире, волшебство.

Это не ужасно новая идея. На самом деле нечто очень похожее уже было сделано раньше. Куда? Haskell конечно! Посмотрите на Haskage: объектив . В нем вы увидите ряд «странных» структур, из которых особенно выделяется Traversal . Должен признать, что Traversal довольно изящный, довольно мощный и, безусловно, красивый. Я собираюсь убить его, но как любой неофит с оружием, готовый стрелять-прицел.

Делать выбор

В моей библиотеке предикатов я выложил 22 признака предиката со следующими операторами:

  • и
  • или же
  • исключающее
  • NAND
  • ни
  • nxor

с добавлением оператора Not, определяемого для каждого. Он отлично работал с методом фильтра TraversableLike (и до сих пор работает, если бы я его опубликовал). Однако я думаю, что наполовину написал пятнадцать различных версий следующего, каждая со своим собственным методом «doStuff», по всему коду, который я работал на:

1
2
3
4
5
6
case class MyClass(pred: SomeType){
  def apply(myCollection: SomeCollectionType) =  myCollection map{
    if(pred(x)) doStuff(x) else x
  }
  def and(that: MyClass) = copy(x => pred(x) && that.pred(x))
}

Понятно, что я хотел что-то, что также имело эти операторы, но само по себе не было предикатом Нечто такое, что объединяет действие с фильтрацией, например:

1
2
3
4
5
6
7
class PredicatedMod[A](pred: A => Boolean, f: A => A){
  def apply[Repr <: TraversableLike[A, Repr]](that: Repr) = that map{
    if(pred(x)) f(x) else x
  }
  def and(that: ChooseFunction[A]) = new ChooseFunction(pred and that.pred, f)
  def or(that: ChooseFunction[A]) = new ChooseFunction(pred or that.pred, f)
  //def xor, def nand, etc.

Простая концепция и действительно выражается в нескольких строках кода. Фактически, PartialFunction может делать большую часть того же самого, хотя без состава предиката:

1
2
3
4
myList map{
  case x if pred(x) => f(x)
  case x => x
}

Однако это обрабатывает только случай «mod» конструкции, подобной Lens (и «set» для константных функций, f (x) = b .) Он не обрабатывает случай «set», в котором у нас есть некоторая замкнутая концепция. итерации или мутации в функции, которую мы хотели бы применить (при условии, что она является прозрачной по ссылкам).

Выбор мудро

Так когда же функция ссылочно прозрачна, но содержит некоторое представление об итерации или мутации? Когда он формирует локально захваченное замыкание вокруг мутирующего состояния, например:

1
2
3
4
5
6
7
8
class Set[A](myList: List[A]) extends (List[A] => List[A]){
  def apply(that: List[A]) ={
    val iter = myList.toIterator
    that map{ x =>
      if(iter hasNext ()) iter next () else x
    }
  }
}

Это PITA, которую нужно писать многократно, поэтому в библиотеке коллекций Scala есть метод исправления, определенный в SeqLike . Более того, метод исправления является немного более мощным, чем указанная выше функция, поскольку он также позволяет вызывающей стороне указывать начальный индекс и длину замещения. К сожалению, это действие типа «все или ничего», с определенным началом и окончанием, которое распространяется на весь сегмент.

Важно отметить, что исправление не поддается приложению на основе условия предиката (это не так), а скорее то, что оно работает сразу для всей коллекции. Проще говоря, мы могли бы обойтись без PartialFunction как почти достаточной в последнем разделе, потому что она работала по частям по точкам, где каждый элемент коллекции был независим от другого. Здесь подстановка зависит от того, сколько предыдущих значений было подставлено. Иными словами, каждое последующее действие в коллекции зависит от накопленного набора действий, которые произошли до этого момента.

1
2
3
4
5
6
7
8
class PredicatedSet[A](pred: A => Boolean, myList: List[A]) extends (List[A] => List[A]){
  def apply(that: List[A]) ={
    val iter = myList.toIterator
    that map{ x =>
      if((iter hasNext ()) && pred(x)) iter next () else x
    }
  }
}

Как я уже говорил ранее, это PITA, чтобы писать снова и снова не потому, что он занимает много кода, а потому, что он использует так мало кода, что у нас возникает соблазн не упаковывать его. Обобщая это для более чем одного типа коллекции или семейства коллекций, начинается «веселье» и легко допускается небольшая ошибка.

Создание выбора

Эта «забавная» часть, я сделал это для тебя. Я создал то, что я называю Choice, в новом пакете моей библиотеки Predicates . Как вариант «переносного фильтра», который работает с непараллельными версиями библиотеки коллекций Scala, он также противоречив. Я также сделал два варианта из них: выбрать и игнорировать . Последнее работает, принимая обратное условие, содержащееся в предикате, для применения в функциях-членах «get», «set» и «mod». Наконец, создается выбор, производящий другой выбор, который является объединением (и) базовых предикатов.

Пример во вступительных абзацах? Это работает, Сорта. Это работает, если вы преобразуете список в список [любой] . Функции «mod» и «set» не позволяют изменять тип возвращаемого значения. Они не могут. Что если условие предиката никогда не будет выполнено, как в случае с Never1 ? Без изменений, без модификаций и, следовательно, чисто дорогой сквозной.

Я все еще развлекаюсь с библиотекой, и поэтому я не выпустил ее (хорошо, у меня есть два больших пальца, когда дело доходит до получения чего-либо на Maven или Sonatype.) Я потенциально планирую добавить «переключатель» функция, которую вы можете рассматривать как «modOrElse», которая выглядит следующим образом:

1
2
val choice = Choose({x: Int => x %2 == 0}) 
val processed = choice switch(myCollection, f, g)

и, возможно, можно добавить в функции-члены «section», «fold» и «scan».

В разделе «Предикаты» я все еще работаю над несколькими идеями о PartialFunctions (например, с 22 из них) и простым способом поднятия FunctionN в PartialFunctionN с помощью неявного класса . Это незавершенная работа, когда я не провожу время с любимыми и не зацикливаюсь на чтении, которое я пропустил в последние несколько лет.

В любом случае, если у вас есть идеи или есть что-то, что вы хотели бы увидеть в них, раскошелиться и внести свой вклад, потому что мой собственный темп довольно медленный.