Ответ 1
В идиоматических clojure ваши функции не относятся к данным, они работают с этими данными. Вместо структур maps или записи используются, и вы определяете функции, которые принимают эти структуры в качестве параметров. Например, пример вашего яблока может выглядеть следующим образом:
; Define a record type with given fields
; `defrecord` macro defines a type and also two constructor functions,
; `->apple` and `map->apple`. The first one take a number of arguments
; corresponding to the fields, and the second one takes a map with
; field names as keys. See below for examples.
(defrecord apple [type color cost markup])
; Define several functions working on apples
; Note that these functions do not have any kind of reference to the datatype,
; they exploit map interface of the record object, accessing it like a map,
; so you can supply a real map instead of record instance, and it will work
(defn get-info [a] (str (:color a) " " (:type a) " apple"))
(defn get-price [a] (* (:cost a) (:markup a)))
; Example computation
; Bind `a` to the record created with constructor function,
; then call the functions defined above on this record and print the results
(let [a (->apple "macintosh" "red" 5 1.5)
a-info (get-info a)
a-price (get-price a)]
(println a-info a-price))
; Will print the following:
; red macintosh apple 7.5
; You can also create an instance from the map
; This code is equivalent to the one above
(let [a (map->apple {:type "macintosh" :color "red" :cost 5 :markup 1.5})
a-info (get-info a)
a-price (get-price a)]
(println a-info a-price))
; You can also provide plain map instead of record
(let [a {:type "macintosh" :color "red" :cost 5 :markup 1.5}
a-info (get-info a)
a-price (get-price a)]
(println a-info a-price))
Обычно вы используете записи, когда вам нужен статический объект с известными полями, доступными из кода Java (defrecord
генерирует правильный класс, а также множество других функций, описанных по ссылке выше), а карты используются во всех других case - аргументы ключевого слова, промежуточные структуры, динамические объекты (например, те, которые возвращаются из SQL-запроса) и т.д.
Итак, в clojure вы можете представить пространство имен как единицу инкапсуляции, а не структуру данных. Вы можете создать пространство имен, определить в нем свою структуру данных, написать все необходимые функции с помощью простых функций и пометить внутренние функции как частные (например, определить их с помощью формы defn-
, а не defn
). Тогда все не частные функции будут представлять собой интерфейс вашего пространства имен.
Если вам также нужен полиморфизм, вы можете посмотреть multimethods и protocols. Они предоставляют средства для специальных и подтипирующих видов полиморфизма, то есть переопределения поведения функции - то же самое, что вы могли бы сделать с наследованием Java и перегрузкой метода. Мультиметоды более динамичны и мощны (вы можете отправлять результат любой функции аргументов), но протоколы более эффективны и понятны (они очень похожи на интерфейсы Java, кроме наследования и расширяемости).
Обновить: ответ на ваш комментарий для другого ответа:
Я пытаюсь определить, какой подход заменяет методы OOP
Полезно понять, что такое "методы ООП".
Любой метод в обычном объектно-ориентированном языке, таком как Java или, в особенности, С++, по существу, является простой функцией, которая принимает неявный аргумент this
. Из-за этого неявного аргумента мы считаем, что методы "принадлежат" к некоторому классу, и эти методы могут работать на объекте, на который они "вызывают".
Однако ничто не мешает вам писать дружественную глобальную функцию (в С++) или общедоступный статический метод (в Java), который берет объект в качестве первого аргумента и выполняет все, что можно сделать из метода. Никто не делает этого из-за полиморфизма, который обычно достигается с помощью понятия методов, но мы не рассматриваем его прямо сейчас.
Так как clojure не имеет понятия состояния 'private' (за исключением функциональности взаимодействия с Java, но это совершенно другая вещь), функции не должны быть связаны каким-либо образом с данными, на которых они работают. Вы просто работаете с данными, представленными как аргумент функции, и все. И полиморфная функциональность в clojure выполняется по-разному (многоточие и протоколы, см. Ссылки выше), чем в Java, хотя есть некоторые сходства. Но это вопрос для другого вопроса и ответа.