У меня проблема с написанием уроков в Джулии. Я просмотрел документацию и не видел никаких документов по классам.
В Python классы, например,
class Dog:
# ----blah blah---
Как это возможно в Джулии?
Класс похож на struct
+ методы. В Джулии они (намеренно) не объединены вместе. Так что просто создайте struct
и добавьте методы. docs.julialang.org/en/latest/manual/types
Так что это структуры + методы вместо классов в Джулии. Вау спасибо.
@tholy, пожалуйста, дайте мне простой пример, чтобы продемонстрировать это хотя бы для лучшего понимания.
Ближе всего к классам с методами в Julia можно подобраться с помощью модуля:
module DogClass
export Dog, bark
struct Dog
name::String
end
function bark(d::Dog)
println(d.name, " says woof!")
end
end #MODULE
using .DogClass # note the . here, means look locally for module, not library
mydog = Dog("Fido")
bark(mydog)
Модуль кажется излишним - действительно, это похоже на неуместное использование модулей. Но ваша структура Dog и функция bark — отличные примеры того, как получить эту функциональность.
Это игрушечный пример. При переносе существенного класса из чего-то вроде C++ пользователь может захотеть эмулировать закрытые методы, что позволяет использовать вид модуля (хотя и не полностью), поскольку будут экспортированы только общедоступные методы.
Большое спасибо. Теперь я понимаю больше об использовании модулей
У Юли нет занятий. Вместо этого мы определяем новые типы, а затем определяем методы для этих типов. Методы не "принадлежат" типам, с которыми они работают. Вместо этого можно сказать, что метод принадлежит общая функция с тем же именем, что и метод. Например, существует множество версий («методов») функции length
; вместе они образуют общую функцию length
.
Вот расширенный пример юлианского подхода к программированию с типами и методами. Новые типы объявляются с помощью ключевого слова struct
:
struct Person
name::String
age::Int64
end
Теперь мы можем определить методы для типа Person
:
name(p::Person) = p.name
age(p::Person) = p.age
bio(p::Person) = println("My name is ", name(p)," and I am ", age(p), " years old.")
Методы могут быть определены для различных комбинаций типов аргументов. Чтобы проиллюстрировать это, давайте сначала определим несколько новых типов:
abstract type Pet end
struct Cat <: Pet
name::String
color::String
end
name(c::Cat) = c.name
color(c::Cat) = c.color
species(::Cat) = "cat"
struct Dog <: Pet
name::String
color::String
end
name(d::Dog) = d.name
color(d::Dog) = d.color
species(::Dog) = "dog"
bio(p::Pet) = println("I have a ", color(p), " ", species(p), " named ", name(p), ".")
struct Plant
type::String
end
type(p::Plant) = p.type
bio(p::Plant) = println("I have a ", type(p), " house plant.")
На данный момент мы видим, что мы определили три разных метода с одним аргументом для bio
:
julia> methods(bio)
3 methods for generic function "bio":
[1] bio(p::Plant) in Main at REPL[17]:1
[2] bio(p::Person) in Main at REPL[4]:1
[3] bio(p::Pet) in Main at REPL[14]:1
Обратите внимание на комментарий в выводе methods(bio)
: «3 метода для универсальной функции 'bio'». Мы видим, что bio
— это общая функция, в котором в настоящее время определены 3 метода для разных функций подписи. Теперь давайте добавим метод с двумя аргументами для bio
:
function bio(person::Person, possession)
bio(person)
bio(possession)
end
Обратите внимание, что эта функция является общей в аргументе possession
, поскольку внутренний вызов bio(possession)
будет работать независимо от того, является ли possession
растением, кошкой или собакой! Итак, теперь у нас есть четыре метода для bio
:
julia> methods(bio)
4 methods for generic function "bio":
[1] bio(p::Plant) in Main at REPL[17]:1
[2] bio(p::Person) in Main at REPL[4]:1
[3] bio(p::Pet) in Main at REPL[14]:1
[4] bio(person::Person, possession) in Main at REPL[18]:1
Теперь давайте создадим несколько экземпляров наших типов:
alice = Person("Alice", 37)
cat = Cat("Socks", "black")
dog = Dog("Roger", "brown")
plant = Plant("Boston Fern")
Итак, наконец, мы можем протестировать наши bio
методы:
julia> bio(alice, cat)
My name is Alice and I am 37 years old.
I have a black cat named Socks.
julia> bio(alice, dog)
My name is Alice and I am 37 years old.
I have a brown dog named Roger.
julia> bio(alice, plant)
My name is Alice and I am 37 years old.
I have a Boston Fern house plant.
Примечание: модули используются в основном для управления пространство имен. Один модуль может содержать определения для нескольких типов и нескольких методов.
Я считаю, что это более подробно описывает мой вопрос.
Другое дело, что я думаю, что было бы лучше использовать «тип растения» вместо «типа» при определении структуры для растения. Это имеет смысл, потому что «тип» зарезервирован, хотя он и устарел, но когда вы пишете код с «типом», Джулия думает, что вы хотите создать «тип». И, наконец, насколько важны следующие строки кода? имя(d::Собака) = d.name цвет(d::Собака) = d.color видов(::Собака) = "собака"
@kkirui Хорошее замечание о «типе». Хотя я использую Julia v1.1 и не вижу никаких предупреждений об использовании «типа». Я думаю, что это должно быть нормально в Джулии v1.0 или выше.
В этом примере методы name
, color
и species
кажутся немного лишними. Однако они помогают определить «интерфейс» для абстрактного типа Pet
. Обратите внимание, что метод bio(p::Pet)
работает с любым подтипом Pet
, потому что методы name
, color
и species
реализованы для каждого подтипа. Это позволяет изменить внутреннее представление Cat
s и Dog
s в любой момент в будущем, если это необходимо, пока методы name
, color
и species
все еще реализованы и по-прежнему возвращают тот же тип вывода.
Внутри класса в Python есть эта функция ``` python def __init__(): blah end ``` Можно ли добавить в приведенный выше ответ? @Кэмерон Бейганек
Я не совсем понимаю, почему все методы (name
, color
, species
и т. д.) объявлены вне структуры. Я думал, что целью класса было хранить набор атрибутов и методов, принадлежащих ему, а не глобальному пространству имен. (Например, чтобы я мог вызывать их с помощью точечной записи: alice.bio()
). Могу ли я сделать это в Джулии?
У @Bill Julia нет занятий. Функции в Julia используют множественную отправку, что означает, что правильный метод для отправки выбирается путем просмотра типов всех позиционных аргументов, а не только первого аргумента. Тип объекта, предоставленного в качестве первого аргумента функции, не «владеет» методом больше, чем типы других аргументов, поэтому не имеет смысла связывать методы со структурами. На самом деле происходит обратное — все различные методы для foo
можно рассматривать как члены функции общийfoo
.
@Bill Такой подход к разделению типов данных и функций/методов распространен в функциональных языках, включая R.
Спасибо @Cameron Beiganek. Я читаю о множественной отправке. Думаю, я понял, но как мне избежать практической проблемы конфликтов имен? Например, я создал метод для своего объекта: step(gym::CartPole, u)
и поместил их оба в модуль, и теперь я получаю WARNING: both CartPoleBTEnv and Base export "step"; uses of it in module Main must be qualified
. Есть ли какие-нибудь простые примеры или учебные пособия о том, как это сделать?
Для начинающих, таких как я, я нашел небольшое введение в функции и методы в Джулии здесь: Думай, Джулия.
@Bill Ваш вопрос о конфликтах имен достоин отдельного вопроса о переполнении стека. Но вкратце, вы можете либо переименовать свою функцию, вызвать ее через CartPoleBTEnv.step()
, либо сделать ее расширением функции Base.step
. Последний вариант удобен, но не идеален, так как функция Base step
имеет значение «получить размер шага объекта AbstractRange
», что совершенно отличается от смысла вашей step
функции. Мне жаль, что они выбрали имя step
для этой функции в Base. На мой взгляд, stepsize
было бы лучше.
Это не просто step
. Имена функций, такие как begin
, end
, break
, catch
, show
, export
, будут создавать похожие конфликты. В конце концов, я расширил Base.step
, но тогда вы живете в страхе, что какой-то другой процесс может однажды вызвать ваш метод!
Краткий ответ: мы не делаем.