Практические ежедневные уловки для улучшения навыков функционального программирования в Julia
Будучи таким выразительным языком, при программировании Джулия легко начать думать:
Нет ли еще более совестливого и элегантного способа решения этой проблемы?
Многие проблемы можно решить напрямую с помощью подходов, к которым вы привыкли в других языках. Однако в Джулии часто есть еще более короткий и ясный способ сделать что-то.
Частичное применение
Частичное применение функции или каррирование - это то, что популяризировал Haskell. Это означает, что, не предоставляя все аргументы, вы можете вернуть новую функцию с остальными аргументами.
Это может показаться запутанным, поэтому позвольте мне привести несколько примеров. Обычно вы проводите такое сравнение:
julia> 3 < 4
true
Что идентично этому, потому что почти все в Julia - это функция:
julia> <(3, 4)
true
Что произойдет, если вы не приведете все аргументы?
julia> <(4)
(::Base.Fix2{typeof(<),Int64}) (generic function with 1 method)
Вместо этого вы получаете вызываемый объект. Мы можем сохранить это и использовать позже:
julia> f = <(4);
julia> f(3)
true
Чем это полезно? Это делает действительно элегантной работу с такими функциями, как map
, filter
и reduce
.
Найдите все элементы, которые меньше значения
Найдите элементы в списке или диапазоне, которые меньше заданного значения . julia> filter(<(5), 1:10) 4-element Array{Int64,1}: 1 2 3 4
Вы также можете использовать его для поиска больших значений:
julia> filter(>(5), 1:10)
5-element Array{Int64,1}:
6
7
8
9
10
Найти индекс элемента
Мы можем найти индекс каждого вхождения числа 4, например.
julia> findall(==(4), [4, 8, 4, 2, 1, 5])
2-element Array{Int64,1}:
1
3
Или мы можем просто найти первое вхождение:
julia> findfirst(==(4), [4, 8, 4, 2, 1, 5])
1
Это, конечно, одинаково хорошо работает со строками:
julia> findlast(==("foo"), ["bar", "foo", "qux", "foo"])
4
Отфильтровать определенные типы файлов
Допустим, вы хотите получить список всех .png
файлов в текущем каталоге. Как ты это делаешь?
Мы можем использовать функцию endswith
.
julia> endswith("somefile.png", ".png")
true
Как и многие другие функции, его можно использовать в частичном приложении, что делает его удобным для использования в фильтрах:
pngs = filter(endswith(".png"), readdir())
Отрицание функции предиката
К сожалению, я обнаружил этот трюк довольно поздно. Но оказывается, что вы можете поместить !
перед функцией, чтобы создать новую функцию, которая инвертирует ее вывод. На самом деле это не встроено в язык Julia, а просто функция, определенная как:
!(f::Function) = (x...)->!f(x...)
Опять же, может быть не совсем понятно, о чем я говорю, поэтому давайте рассмотрим несколько примеров.
Удаление пустых строк
Предположим, вы читаете все строки в файле, заданном filename
, и хотите вырезать пустые строки, вы можете сделать это следующим образом:
filter(line -> !isempty(line), readlines(filename))
Но это более элегантный подход с частичным применением !
:
filter(!isempty, readlines(filename))
Вот пример его использования в REPL с фиктивными данными:
julia> filter(!isempty, ["foo", "", "bar", ""])
2-element Array{String,1}:
"foo"
"bar"
Трансляция и карта
У Джулии есть функция broadcast
, которую вы можете рассматривать как модную версию карты. Вы даже можете использовать его аналогичным образом:
julia> map(sqrt, [9, 16, 25])
3-element Array{Float64,1}:
3.0
4.0
5.0
julia> broadcast(sqrt, [9, 16, 25])
3-element Array{Float64,1}:
3.0
4.0
5.0
Настоящая сила приходит при работе с функциями, принимающими несколько аргументов, и вы хотите, чтобы один из аргументов был повторно использован, а другие изменены.
Преобразование списка строк в числа
Чтобы преобразовать, скажем, строку в число, вы используете функцию parse
следующим образом:
julia> parse(Int, "42")
42
Наивный способ применить это к нескольким текстовым строкам - написать:
julia> map(s -> parse(Int, s), ["7", "42", "1331"])
3-element Array{Int64,1}:
7
42
1331
Мы можем упростить это с помощью функции broadcast
:
julia> broadcast(parse, Int, ["7", "42", "1331"])
3-element Array{Int64,1}:
7
42
1331
На самом деле это настолько полезно и часто используется в Julia, что существует еще более короткая версия с суффиксом точка .
:
julia> parse.(Int, ["7", "42", "1331"])
3-element Array{Int64,1}:
7
42
1331
Вы даже можете связать это:
julia> sqrt.(parse.(Int, ["9", "16", "25"]))
3-element Array{Float64,1}:
3.0
4.0
5.0
Преобразование футляра для змеи в футляр для верблюда
В программировании у нас часто есть идентификаторы, написанные как hello_how_are_you
, которые мы можем захотеть скрыть в верблюжьем регистре, написанном как HelloHowAreYou
. Оказывается, вы легко можете сделать это с помощью одной строчки кода в Julia.
julia> greeting = "hello_how_are_you"
"hello_how_are_you"
julia> join(uppercasefirst.(split(greeting, '_')))
"HelloHowAreYou"
Избегайте глубокого вложения с помощью оператора трубы
Распространенная жалоба поклонников ООП на более функционально ориентированный язык, такой как Джулия, заключается в том, что трудно читать глубоко вложенные вызовы функций. Однако мы можем избежать глубокой вложенности, используя конвейер |>.
Просто чтобы дать простое представление о том, что он делает. Вот пример эквивалентных выражений:
julia> string(sqrt(16)) "4.0" julia> 16 |> sqrt |> string "4.0"
Это также работает с широковещательной передачей, поэтому вы можете использовать его для передачи нескольких значений между этапами в своего рода конвейере.
julia> [16, 4, 9] .|> sqrt .|> string 3-element Vector{String}: "4.0" "2.0" "3.0"
С этим мы можем упростить наш змеиный футляр до верблюжьего примера.
julia> split(greeting, '_') .|> uppercasefirst |> join "HelloHowAreYou"