- PVSM.RU - https://www.pvsm.ru -
Я люблю Python. Нет, правда, это отличный язык, подходящий для широкого круга задач: тут вам и работа с операционной системой, и веб-фреймворки на любой вкус, и библиотеки для научных вычислений и анализа данных. Но, помимо Python, мне нравится функциональное программирование. И питон в этом плане неплох: есть замыкания, анонимные функции и вообще, функции здесь — объекты первого класса. Казалось бы, чего ещё можно желать? И тут я случайно наткнулся на Coconut — функциональный язык, компилируемый в Python. Всех любителей Python и ФП прошу под кат.
Что? Функциональный язык, который компилируется в Python? Но зачем, ведь функциональных фич и так полно, а если хочется дополнительных извращений, то есть модуль toolz.functoolz? Но давайте рассмотрим простую задачу: нам необходимо сложить квадраты чисел из некоторого списка.
l = [1, 2, 3, 4, 5]
Императивное решение "в лоб":
def sum_imp(lst):
s = 0
for n in lst:
s += n**2
return s
С использованием map и reduce (выглядит жутко):
from functools import reduce
from operator import add
def sum_map_reduce(lst):
return reduce(add, map(lambda n: n**2, lst))
С использованием генераторов списков (pythonic-way):
def sum_list_comp(lst):
return sum([n**2 for n in lst])
Последний вариант не так уж плох. Но в таких случаях хочется написать что-нибудь в духе
sum_sqr(lst) = lst |> map(n -> n**2) |> sum
Да-да, совсем как в OCaml, только без строгой типизации (язык-то у нас динамический). А что, если я вам скажу, что с Coconut мы действительно можем так сделать? С его помощью можно написать
sum_sqr(lst) = lst |> map$(n -> n**2) |> sum
и получить полноценное решение поставленной задачи без вызовов функций(от_функций(от_функций))).
Авторы языка пишут, что он добавляет в Python следующие возможности:
Также стоит отметить, что язык может работать в режиме интерпретатора, компилироваться в исходники Python и использоваться в качестве ядра для Jupyter Notebook (сам пока не проверял, но разработчики пишут, что можно).
А теперь остановимся на некоторых возможностях поподробнее.
Я уверен, что не мне одному доставляет боль запись лямба-выражений в питоне. Я даже думаю, что её специально создали такой, чтобы ей пользовались как можно реже. Coconut делает определение анонимной функции именно таким, как мне хотелось бы его видеть:
(x -> x*2)(a) # То же, что (lambda x: x*2)(a)
Композиция функций выглядит здесь почти как в хаскеле:
(f .. g .. h)(x) # То же, что и f(g(h(x)))
В модуле functools есть функция partial, которая позволяет создавать функции с фиксированными аргументами. У неё есть существенный недостаток: позиционные аргументы нужно подставлять строго по порядку. Например, нам нужна функция, которая возводит числа в пятую степень. По логике, мы должны использовать partial (мы ведь просто хотим взять функцию и зафиксировать один из аргументов!), но никакого выигрыша это не даст (pow в обоих случаях используется, чтобы отвлечься от того, что это встроенная операция):
from functools import partial
from operator import pow
def partial5(lst):
return map(lambda x: partial(pow(x, 5)), lst) # Какой кошмар!
def lambda5(lst):
return map(lambda x: pow(x, 5), lst) # Так немного лучше
Что может предложить Coconut? А вот что:
def coco5(lst) = map$(pow$(?, 5), lst)
Символ $ сразу после названия функции указывает на её частичное применение, а ? используется в качестве местозаполнителя. Почему-то этот пример не работает у меня в Windows, но с этим надо отдельно разобраться.
Ещё одна простая концепция, которая часто применяется в функциональных языках и даже в широко известном bash. Всего здесь имеется 4 типа пайплайнов:
Пайплайн | Название | Пример использования | Пояснение |
|> | простой прямой | x |> f | f(x) |
<| | простой обратный | f <| x | f(x) |
|*> | мультиаргументный прямой | x |*> f | f(*x) |
<*| | мультиаргументный обратный | f <*| x | f(*x) |
В самом простом случае паттерн-матчинг выглядит так:
match 'шаблон' in 'значение' if 'охранное выражение':
'код'
else:
'код'
Охрана и блок else могут отсутствовать. В таком виде паттерн-матчинг не очень интересен, поэтому рассмотрим пример из документации:
data Empty()
data Leaf(n)
data Node(l, r)
Tree = (Empty, Leaf, Node)
def depth(Tree()) = 0
@addpattern(depth)
def depth(Tree(n)) = 1
@addpattern(depth)
def depth(Tree(l, r)) = 1 + max([depth(l), depth(r)])
Как вы могли догадаться, Tree — это тип-сумма, который включает в себя разные типы узлов бинарного дерева, а функция depth предназначена для рекурсивного вычисления глубины дерева. Декоратор addpattern позволяет выполнять диспетчеризацию при помощи шаблона.
Для случаев, когда результат должен вычисляться в зависимости от первого подходящего шаблона, введено ключевое слово case. Вот пример его использования:
def classify_sequence(value):
'''Классификатор последовательностей'''
out = ""
case value:
match ():
out += "пусто"
match (_,):
out += "одиночка"
match (x,x):
out += "повтор "+str(x)
match (_,_):
out += "пара"
match _ is (tuple, list):
out += "последовательность"
else:
raise TypeError()
return out
parallel_map и concurrent_map из Coconut — это просто обёртки над ProcessPoolExecutor и ThreadPoolExecutor из concurrent.futures. Несмотря на их простоту, они обеспечивают упрощенный интерфейс для многопроцессного/многопоточного выполнения:
parallel_map(pow$(2), range(100)) |> list |> print
concurrent_map(get_data_for_user, all_users) |> list |> print
Мне всегда было завидно, что в .Net есть F#, под JVM — Scala, Clojure, про количество функциональных языков, компилируемых в JS я вообще молчу. Наконец-то я нашёл нечто похожее для Python. Я почти уверен, что Coconut не получит широкого распространения, хоть мне этого и хотелось бы. Ведь функциональное программирование позволяет решать множество проблем лаконично и изящно. Зачастую даже без потери читабельности кода.
→ Сайт языка [1]
Автор: gsedometov
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/243872
Ссылки в тексте:
[1] Сайт языка: http://coconut-lang.org/
[2] Источник: https://habrahabr.ru/post/322000/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.