Синтаксис анонимной функции Scala

Я узнаю больше о Scala, и у меня возникли небольшие проблемы с пониманием примера анонимных функций в http://www.scala-lang.org/node/135. Я скопировал весь блок кода ниже:

object CurryTest extends Application {
    def filter(xs: List[Int], p: Int => Boolean): List[Int] =
        if (xs.isEmpty) xs
        else if (p(xs.head)) xs.head :: filter(xs.tail, p)
        else filter(xs.tail, p)

    def modN(n: Int)(x: Int) = ((x % n) == 0)

    val nums = List(1, 2, 3, 4, 5, 6, 7, 8)
    println(filter(nums, modN(2)))
    println(filter(nums, modN(3)))
}

Меня смущает применение функции modN

def modN(n: Int)(x: Int) = ((x % n) == 0)

В примере вызывается с одним аргументом

modN(2) and modN(3)

Что означает синтаксис modN(n: Int)(x: Int)?

Поскольку он вызывается с одним аргументом, я предполагаю, что это не оба аргумента, но я не могу понять, как значения из nums используются функцией mod.


person Jeff Storey    schedule 11.10.2009    source источник


Ответы (3)


Это забавная вещь в функциональном программировании, называемая каррированием. В основном Моисей Шенфинкель и последний Хаскелл Карри (хотя Шонфинкелинг звучит странно...) пришли к идее, что вызов функции с несколькими аргументами, скажем, f(x,y), аналогичен цепочке вызовов {g(x)}(y) или g(x)(y), где g — это функция, которая производит другая функция в качестве вывода.

В качестве примера возьмем функцию f(x: Int, y: Int) = x + y. Вызов f(2,3) выдаст 5, как и ожидалось. Но что происходит, когда мы каррируем эту функцию — переопределяем ее как f(x:Int)(y: Int) и вызываем как f(2)(3). Первый вызов, f(2), создает функцию, которая принимает целое число y и добавляет к нему 2 -> поэтому f(2) имеет тип Int => Int и эквивалентна функции g(y) = 2 + y. Второй вызов f(2)(3) вызывает только что созданную функцию g с аргументом 3, поэтому, как и ожидалось, оценивается как 5.

Другой способ увидеть это - пройти через сокращение (функциональные программисты называют это бета-сокращением - это похоже на функциональный способ пошагового выполнения строка за строкой) вызова f(2)(3) (обратите внимание, следующее не является действительным синтаксисом Scala).

f(2)(3)         // Same as x => {y => x + y}
 | 
{y => 2 + y}(3) // The x in f gets replaced by 2
       |
     2 + 3      // The y gets replaced by 3
       |
       5

Итак, после всего этого разговора f(x)(y) можно рассматривать как следующее лямбда-выражение (x: Int) => {(y: Int) => x + y}, которое является допустимым для Scala.

Я надеюсь, что все это имеет смысл - я попытался немного объяснить, почему вызов modN(3) имеет смысл :)

person Flaviu Cipcigan    schedule 11.10.2009
comment
Отличное объяснение. Я изменил ваш ответ на принятый (плюс вы исправили ошибку в предыдущем примере). - person Jeff Storey; 11.10.2009
comment
Проголосуйте за Schonfinkeling параметра в функцию. С этого момента я буду использовать только это слово. :-D - person Leonel; 26.04.2019

Вы частично применяете функцию ModN. Применение частичных функций является одной из основных особенностей функциональных языков. Для получения дополнительной информации ознакомьтесь со статьями Currying и без точек.

person mdm    schedule 11.10.2009

В этом примере modN возвращает функцию, которая модифицирует конкретное N. Это избавляет вас от необходимости делать следующее:

def mod2(x:Int): Boolean = (x%2) == 0
def mod3(x:Int): Boolean = (x%3) == 0

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

def modN(n: Int, x: Int): Boolean = (x % n) == 0

val nums = List(1, 2, 3, 4, 5)
println(nums.filter(modN(2, _)))
println(nums.filter(modN(3, _)))
person David Winslow    schedule 11.10.2009
comment
Спасибо за подробный ответ. В этом втором примере с заполнителем, можете ли вы объяснить, для чего предназначен последний : Int, то есть: def modN(n: Int)(x: Int): Int vs def modN(n: Int)(x: Int) просто разница в синтаксисе, когда можно использовать заполнитель, а когда нельзя? - person Jeff Storey; 11.10.2009
comment
Кроме того, я только что попробовал второй пример с заполнителем _, и компилятор жалуется, что modN имеет неправильное количество аргументов. - person Jeff Storey; 11.10.2009
comment
Это связано с тем, что modN(x, y) не является допустимым способом вызова функции (Scala не выполняет автоматическое удаление карри, то есть преобразование из f(x)(y) в f(x, y)). Следовательно, правильный способ вызова modN в этом примере будет modN(2)(_). Также небольшая придирка - тип возвращаемого значения modN неверен, должно быть modN(n: Int)(x: Int): Boolean = (x % n) == 0. Или вы могли бы позволить сделать вывод об этом. :) - person Flaviu Cipcigan; 11.10.2009
comment
Я обновил второй пример, чтобы он был менее неправильным. Спасибо за объяснение Флавиу. - person David Winslow; 11.10.2009