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. , .