Functional programming, get acquainted - OOP

I like to experiment with different paradigms and play with different interesting (for me) ideas (some of them turn into posts: one , two ). I recently decided to test if I could write object oriented code in a functional language.



Idea



I was looking for inspiration from Alan Kay , the creator of object-oriented programming.



OOP to me just means messaging; local storage, protection and hiding of states + processes; and also extremely late binding.

Original:



OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

I figured I'd be happy if I could implement messaging and internal state.



Actually, this is the main problem of the whole idea - the state.



condition



We shouldn't have state at all in functional programming. How then to change the values ​​in the FP? Usually, using recursion (pseudocode):



function list_sum(list, result)
  if empty?
    result
  else
    list_sum(tail(list), result + first(list))
list_sum([1, 2, 3, 4], 0)


, . , , , .



. :



function some_object(state)
  msg = receive_message()
  next_state = process_message(msg)
  some_object(next_state)


, . . ? ? :



/ , .

. some_object(state) " " . .





, (, Go). receive_message() , - ( ). .





Haskell, , , , , - . , Clojure, .. , ( ).



, , Clojure :



(def user (atom {:id 1, :name "John"}))
@user ; ==> {:id 1, :name "John" }
(reset! user {:id 1, :name "John Doe"})
@user ; ==> {:id 1, :name "John Doe"}


, .





- . (, JavaScript -, ; ). .



? " " . , process_message(message) β€” .



Clojure clojure.core.async, . . , :



(ns functional-oop.object
  (:require [clojure.core.async :as async]))

(defn- datastructure [message-handler channel]
  {:message-handler message-handler
   :channel channel})


:



(defn- object-loop [obj state]
  (let [message (async/<!! (:channel obj))
        next-state ((:message-handler obj) obj state message)]
    (if (nil? next-state)
      nil
      (recur obj next-state))))


async/<!! . :message-handler (self, this), .



, β€” :



(defn init [state message-handler]
  (let [channel (async/chan 10)
        obj (datastructure message-handler channel)]
    (async/thread (object-loop obj state))
    obj))

(defn send-msg [obj msg]
  (async/>!! (:channel obj) msg))


, . send-msg. async/>!!, , - .





, , , ? . , string builder.



String builder β€” , :



builder = new StringBuilder
builder.add "Hello"
builder.add " world"
builder.build # ===> "Hello world"


:



(defn message-handler [self state msg]
  (case (:method msg)
    :add (update state :strings conj (:str msg))
    :add-twice (let [add-msg {:method :add, :str (:str msg)}]
                 (object/send-msg self add-msg)
                 (object/send-msg self add-msg)
                 state)
    :reset (assoc state :strings [])
    :build (do
             ((:callback msg) (apply str (:strings state)))
             state)
    :free nil
    ;; ignore incorrect messages
    state))

(def string-builder
  (object/init {:strings []} message-handler))


( , )



, , , , . 5 .



"hello world":



(object/send-msg string-builder {:method :add, :str "Hello"})
(object/send-msg string-builder {:method :add, :str " world"})

(let [result-promise (promise)]
  (object/send-msg string-builder
                   {:method :build
                    :callback (fn [res] (deliver result-promise res))})
  @result-promise)

;; ===> "Hello world"


. ?



- - . ? (promises).



. , . , .



@result-promise . , ( ).



add-twice, , .. . , , .. . . ( ?) , .



, - :



1.   :add-twice   "ha"
2.   :build  ,    "haha"


. - , :build , :add-twice :add ( , ).



, , . - , ( β€” Ruby on Rails) .

, , β€” . race condition ( ). β€” !:)



. . ?





β€” () , (). , , (, Ruby). .



"" . , ():



(ns functional-oop.klass.method
  (:require [functional-oop.object :as object]))

(defn- call-message [method-name args]
  {:method method-name :args args})

(defn call-on-object [obj method-name & args]
  (object/send-msg obj (call-message method-name args)))

(defn for-message [method-map msg]
  (method-map (:method msg)))

(defn execute [method self state msg]
  (apply method self state (:args msg)))


. β€” , : .



for-message. , . execute , : , , , .



:



(ns functional-oop.klass
  (:require [functional-oop.object :as object]
            [functional-oop.klass.method :as method]))

(defn- message-handler [method-map]
  (fn [self state msg]
    ;; Ignore invalid messages (at least for now)
    (when-let [method (method/for-message method-map msg)]
      (method/execute method self state msg))))


, :



(defn new-klass [constructor method-map]
  (object/init {:method-map method-map
                :constructor constructor
                :instances []}
               (message-handler {:new instantiate})))


, . , , , . new-klass klass, :new. , .



, β€” , β€” , ( ) . , , , .



, instantiate? :



(defn- instantiate [klass state promise-obj & args]
  (let [{:keys [constructor method-map]} state
        instance (object/init (apply constructor args)
                              (message-handler method-map))]
    (update state :instances conj @(deliver promise-obj instance))))


, , . .



:



(defn new-instance
  "Calls :new method on a klass and blocks until the instance is ready. Returns the instance"
  [klass & constructor-args]
  (let [instance-promise (promise)]
    (apply method/call-on-object klass :new instance-promise constructor-args)
    @instance-promise))


, - string-builder.



(defn- constructor [& strings]
  {:strings (into [] strings)})

(def string-builder-klass
  (klass/new-klass
   constructor
   {:add (fn [self state string]
           (update state :strings conj string))
    :build (fn [self state promise-obj]
             (deliver promise-obj
                      (apply str (:strings state)))
             state)
    :free (constantly nil)}))

(def string-builder-1 (klass/new-instance string-builder-klass))
(method/call-on-object instance :add "abc")
(method/call-on-object instance :add "def")
(let [result (promise)]
  (method/call-on-object instance :build result)
  @result)
;; ==> "abcdef

(def string-builder-2 (klass/new-instance string-builder-klass "Hello" " world"))
(method/call-on-object instance :add "!")
(let [result (promise)]
  (method/call-on-object instance :build result)
  @result)
;; ==> "Hello world!"


!



?



- ( , , ). . , . - . DSL , , .. Clojure.



. β€” , , .



- ?



β€” (). : , . ( ). , . :



# add
Title: Buy lots of toilet paper

# add
Title: Make a TODO list

# list
TODO list:
- Buy lots of toilet paper
- Make a TODO list

# complete
Index: 1

# list
TODO list:
- Buy lots of toilet paper
+ Make a TODO list

# exit




, ( ). , Haskell. , , . Haskell , . , - RabbitMQ.



, , . , , . .



, , , - :)



.





, Erlang. , .




All Articles