Разные парадигмы сияют на разных работах, множественная диспетчеризация сияет на всех из них.

Введение

Парадигма программирования является важным компонентом не только языка программирования; но также проекты и экосистема, которые приходят на этот язык. На протяжении всей истории вычислительной техники мы видели, как воплощалось в жизнь множество замечательных парадигм программирования, и, безусловно, в будущем появится еще больше. Одним довольно недавним примером резкого нового изменения в определениях парадигмы была «парадигма множественной отправки» и, более того, применение этой парадигмы в Джулии. Это, безусловно, звучит несколько глупо, и можно утверждать, что парадигма множественной диспетчеризации действительно была задумана в Standard ML. При этом, однако, Джулия действительно продвигает парадигму по нескольким ключевым причинам. На самом деле я писал StandardML раньше, и то, как он написан, определенно менее доступно, но если вам интересно узнать об этом языке, вы всегда можете прочитать о нем больше здесь:



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

Вдобавок ко всему, множественная отправка — это отличная вещь, независимо от того, работаете ли вы в определенной парадигме. В результате множественной диспетчеризации Джулия, как правило, имеет отличный баланс функций и возможностей в каждой парадигме программирования. Это может звучать немного странно — и поверьте мне, это так, но внутри этого языка можно делать всевозможные странные дженерики и интересные вещи. Сегодня я хотел рассмотреть парадигмы, которые способна имитировать множественная диспетчеризация, а затем изучить как сильные, так и слабые стороны связанных с ними методов. Некоторые из них не обязательно являются парадигмами; это общие концепции программирования, но многие из них кажутся другой парадигмой для написания кода, и я думаю, что они действительно демонстрируют гибкость синтаксиса Джулии — и концепции множественной диспетчеризации или парадигмы в целом.

"Блокнот"

Функциональная парадигма

Парадигма функционального программирования обычно используется для программирования Джулии — и на то есть веская причина. Одним из ключевых преимуществ этой парадигмы по сравнению с другими парадигмами является то, что она должна быть декларативной. Другими словами, при таком использовании языка нам не о чем беспокоиться. Большинство методов являются глобальными, мы используем модули и ожидаем, что методы будут экспортированы для нас. Использование языка Julia таким образом просто, но, вероятно, в большинстве случаев это лучший подход к решению проблемы.

Есть много разных ситуаций, когда некоторые традиционные функциональные взгляды на вещи будут великолепны для Джулии. Учитывая, что Julia в основном используется для научных вычислений, легко понять, почему они совпадают. Специалисты по данным любят свои записные книжки, декларативный синтаксис, и код должен соответствовать этому. Вот взгляд обывателя на функциональное программирование Джулии в чистом виде. Я понял, что нет смысла тратить время на написание случайного метода, так что я собираюсь дорабатывать метод getargs() из моего проекта Toolips.jl, пока я этим занимаюсь. Вот какая функция сейчас:

function getargs(http::Any)
    split(http.message.target, '?')[2]
end

Это возвращает строку различных значений вместе с их именем и знаком равенства. Пример строки, которая будет задана этим, следующий:

pre-getargs()           post-getargs()
"route/?what=5&no=8" -> "what=5&no=8"

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

target = "hello=5&d=7"
function finishargs(target)
    args = split(target, '&')
    for arg in args
        println(arg)
    end
end

И чтобы продемонстрировать, что это нам дает, вот что распечатано:

finishargs(target)
hello=5
d=7

Следующим шагом будет разделение аргумента пополам по знаку равенства. После этого нам нужно будет разобрать их на некоторые юлианские типы, предпочтение будет намного проще, чем реальность, если вы хотите сослаться на исходный код, который я написал для чтения таких типов из строк, тогда вы можете всегда проверяйте мою старую статью, где я написал программу для чтения CSV и столкнулся с той же проблемой ввода:

Я буду использовать метод readtypes() из этих примеров, поменяв местами только Int64 и Bool — потому что это разумно. Так же некоторые мысли ушли в массивы и словари.

args = split(target, '&')
    arg_dict = Dict()
    for arg in args
        keyarg = split(arg, '=')
        x = tryparse(keyarg[2])
        push!(arg_dict, Symbol(keyarg[1]) => x)
    end
    return(arg_dict)

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

function tryparse(data)
    x = nothing
    try
        x = parse(Int64, data)
    catch
        try
        x = parse(Bool, data)
        catch
            try
            x = parse(Float64, data)
            catch
            try
                x = parse(Array, data)
            catch
                x = parse(Dict, data)
            end
        end
    end
end
    return(x)
end

Теперь давайте проверим вывод из нашего примера:

finishargs(target)
Dict{Any, Any} with 2 entries:
  :hello => 0
  :d     => 7

Это будет отличным дополнением к моему модулю, и было забавно пройтись по нему — но давайте рассмотрим парадигму, в которой мы работали, чтобы достичь этого. Это было относительно просто, мы даже нигде не использовали диспетчеризацию, кроме Any в getargs(), которую на самом деле нужно было изменить на HTTP.Stream (я не был уверен, какого типа будет аргумент, когда писал его, вот и все. ) В любом случае, что сразу бросается в глаза, так это то, насколько просто работает подобная запись. Это может стать немного сложнее, но в целом все так же просто, как метод для конструктора. Что, кстати, поскольку мы смотрим на эту парадигму, также важно рассмотреть конструкторы Джулии:

struct OurType{T<:AbstractFloat}
    x::T
end

Хотя сначала они кажутся простыми; в этом вся прелесть. Можно было бы легко сидеть здесь до конца своего существования, используя только методы и конструкторы, как это сделано здесь. При этом у Джулии действительно есть серьезные способности, выходящие за рамки этой парадигмы.

Объектно-ориентированного программирования

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

Одним из случаев использования объектно-ориентированного программирования является имитация синтаксиса других языков. Например, PyCall.jl задает тип PyObject, содержащий поля, которые затем являются юлианскими методами. Есть также несколько довольно приятных преимуществ, которые могут сделать этот вариант разумным в самых разных обстоятельствах. Вот несколько причин, почему я часто использую эту парадигму.

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

Создание объектно-ориентированных конструкторов в Julia на самом деле невероятно просто. Хотя это, безусловно, не синтаксис класса, и очевидно, что структуры не являются классами, с точки зрения конечного пользователя код действительно может выглядеть очень похоже на Python. Для этого примера я приведу конструктор нормального распределения:

struct NormalDist
           σ::Float64
           μ::Float64
           N::Int64
           cdf::Function
           apply::Function
           function NormalDist(array::Vector{Number})
              N = length(array)
               σ = std(array)
               μ = mean(array)
               apply(xt) = Array{Real}([i = (i-μ) / σ for i in xt])
               cdf(xt) = bvnuppercdf(σ, μ, xt)
               new(σ, μ, N, cdf, apply)
            end
end

Этот конструктор включает два поля, которые важны для того, чтобы Джулия эмулировала ООП. Конечно, это функции. Затем синтаксис становится очень Python-esc:

x = [5, 10, 15, 20]
normaled = NormalDist(x).apply(x)

В результате всех этих усилий создать объектно-ориентированный синтаксис в Julia невероятно просто. Отличный пример того, как это часто реализуется в портах Python. Одним из примечательных примеров является PyCall.jl, который создает типы PyObject, содержащие преобразованные вызовы функций в виде полей. Если бы я собирался сделать интерфейс для чего-то вроде Python или Javascript, которые оба являются объектно-ориентированными языками.

Единственным существенным недостатком этого является то, что документация. Всякий раз, когда эти поля существуют, отдельные методы не могут быть снабжены документацией. Любые предоставленные строки документации нельзя вызывать с помощью ?() в этом поле. В результате документацию часто приходится оставлять в конструкторе — что, конечно, неудобно.

Метапрограммирование

Хотя обычно я не считаю метапрограммирование парадигмой программирования, во многих отношениях в Джулии оно может им стать. Джулия обращается с метапрограммированием настолько удачно, что имеет смысл использовать его во многих различных сценариях. Макросы в сочетании с оценкой, синтаксическим анализом и очень надежным типом Expr делают этот подход очень ценным для программистов Julia.

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



А полное руководство по метапрограммированию вы можете прочитать в этой статье здесь:



Я буду рассматривать пример на основе Compose.jl из последней статьи. Compose.jl — это высокоуровневый API декларативной векторной графики. Однако проблема с этим API заключается в том, что все работает, что может привести к большому объему перевода данных. Вместо этого мы могли бы использовать метапрограммирование, чтобы вместо этого использовать код в наших формах:

using Compose
function scatterthempoints(x::Array, y::Array)
    startstr = "compose(context(), fill(\"orange\"),"
    for (xp, yp) in zip(x, y)
        startstr = startstr * "circle($xp, $yp, .02),"
    end
    startstr = startstr * ")"
    expression = Meta.parse(startstr)
    eval(expression)
end

Здесь строка объединяется, а затем анализируется в выражение. Приятно то, что нам не нужно было создавать какие-либо структуры для хранения всех данных для этих аргументов, но мы по-прежнему можем наслаждаться роскошью объединения нескольких методов без необходимости в этой структуре.

x, y = [.2, .6, .7], [.2, .6, .7]
scatterthempoints(x, y)

Преимущество использования кода связано с несколькими компромиссами. Прежде всего, вам нужно разобрать и оценить хороший кусок кода. Как правило, всякий раз, когда запускается Julia, все наши определения уже определены — мы знаем, сколько памяти нам нужно для их определения, и мы точно знаем, что мы запускаем. Каждый раз, когда вы что-то оцениваете, вы фактически говорите: вот уязвимость. Чтобы было ясно, это не значит, что вы не можете исследовать этот код, и я, конечно, ничего не имею против метапрограммирования, но, безусловно, будут сценарии, когда вы захотите обратить внимание на подобные вещи. Другая проблема заключается в том, что метод eval() имеет ограничение на переполнение.

Джулиан Программирование

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

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

Спасибо, что прочитали мою статью. Julia — возмутительно крутой язык не только с собственной парадигмой, но и с дженериками, позволяющими выбрать подходящую парадигму для конкретной работы. При выборе языка наиболее распространенная причина выбора заключается в том, насколько хорошо он подходит для работы. Хотя, безусловно, можно создать игровой движок с функциональным языком программирования, это, вероятно, будет запутанным и, по крайней мере, немного сложным в использовании. Джулия, однако, может очень умело использовать обе эти парадигмы и связана с основными принципами множественной диспетчеризации, которые, разумеется, очень эффективны.