Пример рекурсивной функции 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
принимает три аргумента:
- функция, которая принимает один аргумент и возвращает логическое значение. С
>
оператор на самом деле является функцией infix, первый аргумент в приведенном выше примере кажется результатом частичного приложения к – это правильно? - неопределенное значение того же типа ожидается в качестве отсутствующего аргумента функции, представленной в качестве первого аргумента
- список значений вышеупомянутого типа
но фактическая функция 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
посмотреть также: