Статьи

Отсутствует мост OO и FP в Скале

Scala сочетает функциональное и объектно-ориентированное программирование многими приятными способами. Вы можете использовать обе FP как OO-подобные конструкции, в зависимости от того, что лучше подходит для текущей задачи Но всегда есть возможности для улучшения! Вот одна вещь, я думаю, отсутствует ( короткая версия внизу).

Сторона FP

В Scala легко преобразовать метод в функцию. Например, если у вас есть метод:

def say(to: Person, what: String): String = { ... } 

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

val sayFun: (Person, String) => String = say _

Кроме того, Scala поддерживает несколько списков параметров, которые иногда также называют каррированием и которые облегчают частичное применение:

// Method with multiple parameter lists
def connect(person1: Person)(person2: person): Connection = { ... }
 
// Function, created by partially applying the previous method
val createConnectionWithAdam: Person => Connection =
      connect(new Person("Adam")) _

Оо сторона

В каждом классе есть конструктор. Но что на самом деле конструктор? Вы даете значения для аргументов конструктора и получаете взамен новый экземпляр класса. Так что это действительно просто функция!

class Person(name: String, age: Int) { ... }
 
// The "signature" of new is (String, Int) => Person
val somebody = new Person("John", 34)

Ссылка на сайт?

Однако в Scala комбинация OO и FP терпит неудачу: вы не можете использовать две функции методов (преобразование в функцию, каррирование), упомянутые выше, с конструкторами. Оба из них не будут работать :

val makePerson: (String, Int) => Person = new Person _
 
class Person2(name: String)(age: Int) { ... }
val makeNewJack: Int => Person = new Person2("Jack") _

Вы можете обойти это, используя сопутствующие объекты и применить (или любой другой фабричный метод, применить просто имеет более хорошие обозначения впоследствии):

object Person2 {
   def apply(name: String)(age: Int) = new Person2(name, age)
}
 
val makeNewJack: Int => Person = Person2("Jack") _

Но для этого нужно повторить подпись конструктора в объекте-компаньоне, и никто не любит дублирование кода, верно? 😉

Использование регистра

Где это может быть полезно? Например в классическом заводском примере. Представьте, что у вас есть класс, который зависит от некоторых служб, а также от некоторых данных, доступных во время выполнения. Конечно, мы используем IoC, поэтому экземпляры других сервисов предоставляются нашему классу:

// This service depends on a concrete Person instance
class Service3(service1: Service1, service2: Service2)(person: Person) {
   ...
}
 
// Note that the multiple parameter notation above als provides a nice
// separation of the parameters that should be "injected" - services,
// and the data that can be variable.
 
// This service depends on Service1, and wants to create it having Person
// instances
class Service4(makeService3: Person => Service3) {
  // Usage:
  for (person <- persons) makeService3(person).doSomething()
}
 
class Main {
   // Bootstrap: (or - no-framework DI container 😉 )
   val service1 = new Service1
   val service2 = new Service2
   // That's the part that is illegal in Scala
   val makeService3 = new Service3(service1, service2) _
   val service4 = new Service4(makeService1)
 
   ...
 
   // Today we'd have to write: val makeService3 =
   //     (person: Person) => new Service3(service1, service2, person)
}

Это также связано с моим постом о DI и OO , а также о том, как современные структуры DI затрудняют определение сервисов, которые зависят от данных и сервисов, для которых мы хотели бы иметь несколько копий.

Примечание

При просмотре конструкторов как методов / функций (какими они на самом деле являются;)), я предполагаю Ruby-подобную нотацию:

Person.new("John", 34)

было бы лучше, и добавление поддержки _ и нескольких списков параметров было бы очевидно.

Итог для поклонников TL; DR

Почему бы не рассматривать конструкторы классов как любой другой метод / функцию? Сделайте это законным:

class Person(name: String, age: Int)
val makePerson = new Person _ // type: (String, Int) => Person
 
class Person2(name: String)(age: Int)
val makeNewJack = new Person2("Jack") _ // type: Int => Person

Адам