Русский
Русский
English
Статистика
Реклама

Coconut

ФП на Python посредством Coconut! gt print

10.05.2021 10:05:19 | Автор: admin

В этом посте представлен язык Coconut, функциональное надмножество языка Python, целью которого является создание элегантного функционального кода, оставаясь при этом в знакомой среде Python и библиотеках, и приведено несколько показательных примеров.

"Здравствуй, Coconut!" |> print

Язык Coconut (на момент написания поста его последней версией является v1.5.0) - это функционально-ориентированное строгое надмножество языка Python, и поэтому все, что валидно для Python, также валидно для Coconut, при этом Coconut транспилируется в Python. По сути Coconut представляет собой игровую площадку для освоения парадигмы функционального программирования, тестирования идей в области ФП, отработки приемов решения задач в указанной парадигме и для учебных целей.

На странице веб-сайта языка утверждается, что язык Coconut создан быть вам полезным. Coconut расширяет репертуар программистов на Python, задействуя инструменты современного функционального программирования, упрощая использование этих инструментов и усиливая их мощность. Иными словами, язык Coconut делает с функциональным программированием то, что язык Python сделал с императивным программированием.

Будем надеяться, что этот пост докажет эти утверждения на практике.

На всякий случай, установить Coconut можно посредством менеджера пакетов pip: pip install coconut

Coconut - это строгое надмножество языка Python

Написание кода Python в функциональном стиле нередко выливается в сложную задачу, начиная от незначительных неудобств, таких как многословный синтаксис лямбд, и заканчивая более серьезными проблемами, такими как связывание в цепочку лениво вычисляемых итераторов и сопоставление с шаблонами. Coconut - это функциональное надмножество языка Python, целью которого является создание элегантного и функционально-ориентированного кода в стиле Python.

Поскольку функции являются гражданами первого сорта, Python позволяет строить программы с использованием функций более высокого порядка. Однако делать на Python что-то, что делается в повседневном режиме на типичном функциональном языке, зачастую бывает обременительно. Отсутствие сжатого синтаксиса для лямбд, каррирования и функциональных композиций иногда становится крупной неприятностью. А отсутствие нестереотипного сопоставления с шаблонами может стать решающим фактором, чтобы отказаться от решения на основе ФП.

Разработанный в 2016 году диалект Python с открытым исходным кодом обеспечивает синтаксис для использования функций, которые можно найти в функционально-ориентированных языках, таких как Haskell и Scala. Многие функции Coconut включают в себя более элегантные и читаемые способы выполнения того, что уже делает Python. Например, программирование в стиле конвейера позволяет передавать аргументы функции в функцию с помощью отдельного синтаксиса. Например, print("Здравствуй, мир!") можно написать как "Здравствуй, мир!" |> print. Лямбды, или анонимные функции в Python, могут писаться четче, например (x) -> x2 вместо lambda x: x2.

Вот неполный перечень того, что предлагает Coconut:

  • Сопоставление с шаблонами

  • Алгебраические типы данных

  • Деструктурирующее присваивание

  • Частичное применение функций

  • Ленивые списки

  • Функциональная композиция

  • Более удобные лямбды

  • Инфиксная нотация

  • Конвейерное программирование

  • Операторные функции

  • Оптимизация хвостовых вызовов

  • Параллельное программирование

В настоящее время версия coconut-develop (pip install coconut-develop) имеет полную поддержку синтаксиса и поведения сопоставления с шаблонами Python 3.10, а также полную обратную совместимость с предыдущими версиями Coconut. Эта поддержка будет выпущена в следующей версии Coconut v1.6.0.

Coconut обрабатывает различия между принятым в Python и Coconut поведением сопоставления с шаблонами следующим образом:

Всякий раз, когда вы будете использовать конструкцию сопоставления с шаблонами с другим поведением в Python, Coconut выдает предупреждение. Кроме того, такие предупреждения предоставляют альтернативный синтаксис, указывая в явной форме поведение, которое вы ищете, и Coconut выбирает вариант поведения, которое он будет использовать по умолчанию, основываясь на том, какой стиль сопоставления с шаблонами использовался: синтаксис в стиле Coconut или же синтаксис в стиле Python, таким образом сохраняя полную совместимость как с Python, так и с Coconut.

Компиляции исходного кода coconut во что-то другое, кроме исходного кода Python, в планах не стоит. Исходник на Python является единственной возможной целью транспиляции для Coconut, которая поддерживает возможность создания универсального кода, работающего одинаково на всех версиях Python такое поведение невозможно с байт-кодом Python.

Далее, мы определим различные болевые точки при написании функционального кода на Python и продемонстрируем, как Coconut решает эти проблемы. В частности, мы начнем с базовой задачи программирования и перейдем к разработке конвейера ML с помощью функционального подхода.

Задача о решете Эратосфена

Решето Эратосфена (Sieve of Eratosthenes) - это алгоритм нахождения всех простых чисел до некоторого целого числа n, который приписывают древнегреческому математику Эратосфену Киренскому. Как и во многих случаях, здесь название алгоритма говорит о принципе его работы, то есть решето подразумевает фильтрацию, в данном случае фильтрацию всех чисел за исключением простых. По мере прохождения списка нужные числа остаются, а ненужные (они называются составными) исключаются.

Решение задачи средствами Python

Решение задачи о решете Эратосфена на чистом Python состоит из двух функций: primes и sieve. Функция primes вызывает внутреннюю функцию sieve.

from itertools import count, takewhiledef primes():    def sieve(numbers):        head = next(numbers)        yield head        yield from sieve(n for n in numbers if n % head)    return sieve(count(2))list(takewhile(lambda x: x < 60, primes()))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59]

При вызове функции sieve мы создаем генератор count, генерирующий целые числа, начиная с 2 и до бесконечности. В теле функции sieve мы берем головной элемент списка и выдаем его (yield) в качестве результата. В следующей строке кода мы выдаем результат (yield from) рекурсивного вызова функции sieve, которая в своем аргументе поочередно выбирает число по условию.

Обратите внимание, что numbers в выражении next(numbers) отличается от numbers в выражении n for n in numbers if n % head. Вся причина в том, что функция next - это операция с поддержкой состояния: взяв головной элемент списка, у вас останется хвост списка.

В последней инструкции использована функция list, поскольку takewhile производит генератор, и без list не получится заглянуть вовнутрь списка.

Таким образом, мы имеем довольно-таки императивный код: сделать это, сделать то и т.д.

Пошаговая замена кода Python на код Coconut

Всего за 7 шагов и легким движением руки(с) мы преобразуем чистый код Python в чистый функциональный код Coconut.

1. Убрать lambda

Замена ключевого слова lambda оформляется как комбинация символов ->.

from itertools import count, takewhiledef primes():    def sieve(numbers):        head = next(numbers)        yield head        yield from sieve(n for n in numbers if n % head)    return sieve(count(2))list(takewhile(x -> x < 60, primes()))

2. Ввести прямой конвейер

Прямой конвейер переставляет обычный порядок приложения функций f(g(h(d))) на вперед-направленный: d -> h -> g -> f и оформляется через комбинацию символов |>.

from itertools import count, takewhiledef primes():    def sieve(numbers):        head = next(numbers)        yield head        yield from sieve(n for n in numbers if n % head)    return sieve(count(2))primes() |> ns -> takewhile(x -> x < 60, ns) |> list

3. Ввести каррирование

Каррирование, или карринг, - это в сущности частичное приложение функции. Каррирование оформляется через символ $.

from itertools import count, takewhiledef primes():    def sieve(numbers):        head = next(numbers)        yield head        yield from sieve(n for n in numbers if n % head)    return sieve(count(2))primes() |> takewhile$(x -> x < 60) |> list

4. Ввести итераторную цепочку

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

from itertools import count, takewhiledef primes():    def sieve(numbers):        head = next(numbers)        return [head] :: sieve(n for n in numbers if n % head)    return sieve(count(2))primes() |> takewhile$(x -> x < 60) |> list

5. Ввести сопоставление с шаблоном

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

from itertools import count, takewhiledef primes():    def sieve([head] :: tail):        return [head] :: sieve(n for n in tail if n % head)    return sieve(count(2))primes() |> takewhile$(x -> x < 60) |> list

6. Преобразовать функции в выражения

Во многих функциональных языках вся работа происходит с выражениями. При таком подходе последнее вычисленное выражение автоматически возвращает значение, и поэтому отпадает необходимость в указании возвращаемого значения. В сущности все сводится к удалению ключевого слова return и введению символа = вместо символа : для определения функции как выражения.

from itertools import count, takewhiledef primes() =    def sieve([x] :: xs) = [x] :: sieve(n for n in xs if n % x)    sieve(count(2))primes() |> takewhile$(x -> x < 60) |> list

7. Использовать встроенные высокопорядковые функции

Иными словами, убрать инструкции import. При написании программ в функциональном стиле отпадает необходимость загружать функциональные библиотеки, т.к. функции высокого порядка используются очень часто.

def primes() =    def sieve([x] :: xs) = [x] :: sieve(n for n in xs if n % x)    sieve(count(2))primes() |> takewhile$(x -> x < 60) |> list
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59]

В итоге мы получили полностью функциональный код. Весь мыслительный процесс основан на определениях: функция primes определяется как выражение sieve и его вызов, а определение sieve состоит из итераторной цепочки. Начав с императивного кода:

from itertools import count, takewhiledef primes():    def sieve(numbers):        head = next(numbers)        yield head        yield from sieve(n for n in numbers if n % head)    return sieve(count(2))list(takewhile(lambda x: x < 60, primes()))

мы пришли к чистому функциональному коду:

def primes() =    def sieve([x] :: xs) = [x] :: sieve(n for n in xs if n % x)    sieve(count(2))primes() |> takewhile$(x -> x < 60) |> list

Обратите внимание, насколько версия кода на языке Coconut похожа на версию кода на языке Haskell:

primes :: [Int]primes = sieve [2..]where    sieve (x :: xs) = x : sieve (filter (\n -> n `rem` x /= 0) xs    sieve []        = []                                 ?> takewhile (<60) primes 

Еще несколько примеров

  • Сопоставление с шаблонами

def quick_sort([]) = []@addpattern(quick_sort)def quick_sort([head] + tail) =    """Отсортировать последовательность,     используя быструю сортировку."""    (quick_sort([x for x in tail if x < head])    + [head]    + quick_sort([x for x in tail if x >= head]))    quick_sort([3,6,9,2,7,0,1,4,7,8,3,5,6,7])
[0, 1, 2, 3, 3, 4, 5, 6, 6, 7, 7, 7, 8, 9]
  • Алгебраические типы данных

data vector2(x, y):    """Immutable two-element vector."""    def __abs__(self):        return (self.x**2 + self.y**2)**.5data Empty()data Leaf(n)data Node(l, r)def size(Empty()) = 0@addpattern(size)def size(Leaf(n)) = 1@addpattern(size)def size(Node(l, r)) = size(l) + size(r)
  • Оптимизация хвостовых вызовов

def factorial(0, acc=1) = acc@addpattern(factorial)def factorial(n is int, acc=1 if n > 0) =    """Вычислить n!, где n - это целое число >= 0."""    factorial(n-1, acc*n)def is_even(0) = True@addpattern(is_even)def is_even(n is int if n > 0) = is_odd(n-1)def is_odd(0) = False@addpattern(is_odd)def is_odd(n is int if n > 0) = is_even(n-1)factorial(6)  # 720
  • Рекурсивный итератор

@recursive_iteratordef fib_seq() =    """Бесконечная последовательность чисел Фибоначчи."""    (1, 1) :: map((+), fib_seq(), fib_seq()$[1:])            fib_seq()$[:10] |> parallel_map$(pow$(?, 2)) |> list
[1, 1, 4, 9, 25, 64, 169, 441, 1156, 3025]
  • Конвейер

"Здравствуй, Мир!" |> x -> x.replace('Мир', 'Coconut') |> print
Здравствуй, Coconut!
  • Прочее

product = reduce$(*)def zipwith(f, *args) =    zip(*args) |> map$(items -> f(*items))    list(zipwith(lambda x: x > 4, [1,2,3,4,5,6,7,8,9,0]))
[False, False, False, False, True, True, True, True, True, False]

Выводы

Надеюсь, что наглядность приведенных выше примеров вызовет интерес у читателей и побудит их заняться более глубоким изучением парадигмы ФП. Фактически Coconut предлагает синтаксический сахар, т.е. ряд оптимизаций в написании кода, которые превращают код в функциональный, являясь игровой площадкой для тестирования идей с использованием парадигмы функционального программирования.

Справочные материалы:

Пост подготовлен с использованием информации веб-сайта языка и материалов Энтони Квонга.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru