Пример рекурсивной функции Haskell с foldr

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

в: видео YouTube, есть пример функции, который озадачивает меня гораздо больше, чем это, вероятно, должно, с точки зрения того, как это на самом деле работает:

firstThat :: (a -> Bool) -> a -> [a] -> a
firstThat f = foldr (x acc -> if f x then x else acc)

для ясности и поскольку это не было сразу очевидно для меня, я приведу пример применения этого функция для некоторых аргументов:

firstThat (>10) 2000 [10,20,30,40] --returns 20, but would return 2000, if none of the values in the list were greater than 10

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

кажется firstThat принимает три аргумента:

  1. функция, которая принимает один аргумент и возвращает логическое значение. С > оператор на самом деле является функцией infix, первый аргумент в приведенном выше примере кажется результатом частичного приложения к – это правильно?
  2. неопределенное значение того же типа ожидается в качестве отсутствующего аргумента функции, представленной в качестве первого аргумента
  3. список значений вышеупомянутого типа

но фактическая функция firstThat Кажется, определяется иначе, чем его объявление типа, только с одним аргументом. С foldr обычно принимает три аргумента, которые я собрал, происходит какое-то частичное приложение. Лямбда-выражение в качестве аргумента для foldr отсутствуют аргументы тоже.

Итак, как именно работает эта функция? Я извиняюсь, если я слишком тупой или не вижу леса за деревьями, но я просто не могу обернуть голову вокруг него, что расстраивает.

любое полезное объяснение или пример(ы) были бы весьма признательны.

спасибо!

2 ответов


но фактическая функция firstThat кажется, определяется иначе, чем его объявление типа, только с одним аргументом. С foldr обычно принимает три аргумента, которые я собрал, происходит какое-то частичное приложение.

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

во-первых, рассмотрим эту функцию:

add :: Num a => a -> a -> a
add x y = x + y

как вы знаете, мы также можем определить это так:

add :: Num a => a -> a -> a
add = (+)

это работает, потому что функции Haskell являются значениями, как и любые другие. Мы можем просто определить значения add, как равное другому значению,(+), который просто является функцией. Для объявления функции не требуется специального синтаксиса. В результате написание аргументов явно (почти) никогда не требуется; основное причина, почему мы делаем это, потому что это часто делает код более читаемым (например, я мог бы определить firstThat без написания f параметр явно, но я не буду этого делать, потому что результат довольно отвратительный).

во-вторых, всякий раз, когда вы видите тип функции с тремя аргументами...

firstThat :: (a -> Bool) -> a -> [a] -> a

... вы также можете прочитать это так...

firstThat :: (a -> Bool) -> (a -> [a] -> a)

... то есть функция одного аргумента, которая порождает функцию двух аргументов. Это работает для всех функции более чем одного аргумента. Ключевой вынос - это то, что в глубине души,все функции Haskell принимают только один аргумент. Вот почему частичное приложение работает. И так далее...

firstThat :: (a -> Bool) -> a -> [a] -> a
firstThat f = foldr (\x acc -> if f x then x else acc)

... вы можете точно сказать, что вы написали явно все параметры, которые firstThat берет-то есть, только один :)


лямбда-выражение в качестве аргумента для foldr отсутствуют аргументы тоже.

не совсем так. foldr (если ограничено списками)...

foldr :: (a -> b -> b) -> b -> [a] -> b

... и поэтому переданная ему функция принимает два аргумента (не стесняйтесь добавлять воздушные кавычки вокруг "двух", учитывая обсуждение выше). Лямбда была написана как...

\x acc -> if f x then x else acc

... с двумя явными аргументами, x и acc.


функция, которая принимает один аргумент и возвращает логическое значение. Поскольку оператор > на самом деле является функцией infix, первый аргумент в приведенном выше примере кажется результатом частичного приложения к функции > – это правильно?

да: (>10) сокращенно \x -> x > 10, так как (10>) будет сокращенно от \x -> 10 > x.

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

прежде всего, это не пропущенный аргумент: опуская аргумент, вы получаете значение функции. однако тип 2-го аргумента действительно соответствует аргументу функции >10, так же, как он соответствует типу элементов списка [10,20,30,40] (что лучше рассуждений).

список значений вышеупомянутого типа

да.

но фактическое функция firstThat, по-видимому, определяется иначе, чем ее объявление типа, только с одним аргументом. Поскольку foldr обычно принимает три аргумента, я собрал, что происходит какое-то частичное приложение. Лямбда-выражение, предоставленное в качестве аргумента для foldr, похоже, также не хватает его аргументов.

это потому, что дала, например,foo x y z = x * y * z, эти 2 строки эквивалентны:

bar x     = foo x
bar x y z = foo x y z

- это из-за концепции под названием Карри. Currying также причина, почему сигнатуры типа функции не (a, b) -> c но вместо a -> b -> c, что в свою очередь эквивалентно a -> (b -> c) из-за правильной ассоциативности -> оператор типа.

следовательно, эти две строки эквивалентны:

firstThat f     = foldr (\x acc -> if f x then x else acc)
firstThat f x y = foldr (\x acc -> if f x then x else acc) x y

Примечание: что вы также можете использовать Data.List.find в сочетании с Data.Maybe.fromMaybe:

λ> fromMaybe 2000 $ find (>10) [10, 20, 30]
20
λ> fromMaybe 2000 $ find (>10) [1, 2, 3]
2000

посмотреть также: