#saralein
Explore tagged Tumblr posts
Text
27 notes
·
View notes
Text
Understanding Clojure Protocols through UI
The last two weeks I've been learning Clojure, and spent some time the last few days trying to create a UI protocol without success.
In Clojure, a protocol is a set of method signatures (name, arguments) without implementation. By defining a protocol with defprotocol, a corresponding interface is automatically generated with matching methods. This interface allows for use of the protocol method signatures for instances of the interface. Instances of the interface provide the implementation, ultimately resulting in a polymorphic set of methods.
The Clojure documents provide the following protocol example:
(defprotocol P (foo [x]) (bar-me [x] [x y])) (deftype Foo [a b c] P (foo [x] a) (bar-me [x] b) (bar-me [x y] (+ c y))) (bar-me (Foo. 1 2 3) 42) = > 45
It seems simple enough, yet I couldn't get the protocol I wanted working. After some toiling, I came up with working code.
Below is not the exact code, but an isolated version of it. In complexity, I think it falls somewhere between the above code and the very specific examples I seem to find on Stack Overflow. (Perhaps my Clojure Google skills are not up to par.)
I created the code below in three stages to highlight basic behavior one might want from a protocol.
Level 1: Let’s build a console UI
The goal of this level is to create a user interface protocol and a console-specific implementation.
First, I created an app with Leiningen.
lein new app ui-protocol
The code I added to the core.clj shows what we eventually want:
Access to a user interface namespace,
A means to create our example-ui, and
A way to use example-ui to print "Hi." to the console.
Notice that ui/display-prompt takes the instance example-ui as an argument.
core.clj:
(ns ui-protocol.core (:gen-class) (:require [ui-protocol.user-interface :as ui])) (defn -main [] (let [example-ui (ui/create)] (ui/display-prompt example-ui "Hi.")))
From here, we start creating our protocol in the user interface namespace. First step, set up your defprotocol. In defprotocol, we'll create the footprint of methods we want. No implementation is provided.
Notice the x argument. That is the instance argument mentioned above. Each method will need this x argument, followed by any additional arguments you need for your eventual implementation. For example, in display-prompt we will need x and a message to display.
(I went ahead and also added get-input to the UI.)
With our protocol set up, we define a defrecord. This will contain the implementation we use for protocol methods. After setting up our arguments (currently none), we tell our defrecord to use the defprotocol we defined: UI.
As an aside, I've seen defrecord described as like a hashmap, but with its own class. According to the Clojure docs, defrecord generates compiled bytecode for the class with the given name, implementation, etc. defrecord has hash semantics like get, count. In addition to this though, defrecord allows for type-based polymorphism using the protocol/interface it implements. So, in the instance of our UI, you could have typed-based polymorphism through a two defrecords, ConsoleUI and BrowserUI, which both use the footprint of our defprotocol.
Our display-prompt and get-input methods will match the footprint in the protocol, except we also provide the desired implementation. In display-prompt, for example, we add (println message).
Finally, we add a method to create our instance of ConsoleUI: create. Now, we can create example-ui in our core.clj and use its methods.
ui_protocol.clj:
(ns ui-protocol.user-interface) (defprotocol UI (display-prompt [x message]) (get-input [x])) (defrecord ConsoleUI [] UI (display-prompt [x message] (println message)) (get-input [x] (read-line))) (defn create [] (map->ConsoleUI {}))
(map->ConsoleUI {}) is one way to create an instance of ConsoleUI with the information in {}. As we'll see later, {} may contain information from arguments. Note: I've since experimented and found (ConsoleUI.) in place of (map->ConsoleUI {}) works just as well here, and I think I'd pick that option going forward. If ConsoleUI had any arguments at this point, we'd use (ConsoleUI. arg), replacing arg with whatever the actual name is.
Level 1 code on GitHub.
Level 2: Let's use UI methods within the UI
So, we have two basic UI methods. Perhaps we'd like another method which combines the two. Our -main method will use ui/prompt-for-input instead of ui/display-prompt.
core.clj
(ns ui-protocol.core (:gen-class) (:require [ui-protocol.user-interface :as ui])) (defn -main [] (let [example-ui (ui/create)] (ui/prompt-for-input example-ui "Please enter a number: ")))
We add this method to our UI protocol and start to add it to our ConsoleUI, but now what? I updated x to this. In our prompt-for-input implementation, we can refer to internal methods with a dot (.display-prompt and .get-input) and pass this as an argument to the method.
Now we have a method that uses other internal methods to display a message to the user and retrieve input.
ui_protocol.clj
(ns ui-protocol.user-interface) (defprotocol UI (display-prompt [this message]) (get-input [this]) (prompt-for-input [this message])) (defrecord ConsoleUI [] UI (display-prompt [this message] (println message)) (get-input [this] (read-line)) (prompt-for-input [this message] (.display-prompt this message) (.get-input this))) (defn create [] (map->ConsoleUI {}))
Level 2 code on GitHub.
Level 3: Abstract that IO
We can go a little futher and abstract dependencies on println and read-line. In -main, we plan to use this abstraction with example-io. Note that we'll pass example-io to example-ui as an argument.
core.clj
(ns ui-protocol.core (:gen-class) (:require [ui-protocol.input-output :as io] [ui-protocol.user-interface :as ui])) (defn -main [] (let [example-io (io/create-console-io) example-ui (ui/create-console-ui example-io)] (ui/prompt-for-input example-ui "Please enter a number: ")))
We create a new defprotocol for input and output: IO. Our implementation in ConsoleIO will house the dependency on println and read-line.
input_output.clj
(ns ui-protocol.input-output) (defprotocol IO (display [this message]) (input [this])) (defrecord ConsoleIO [] IO (display [this message] (println message)) (input [this] (read-line))) (defn create-console-io [] (map->ConsoleIO {}))
Our UI, specifically, our ConsoleUI is modified to accept console-io as an argument (both the defrecord and creation method). Our input/output can now be used in ConsoleUI implementation.
user_input.clj
(ns ui-protocol.user-interface (:require [ui-protocol.input-output :as io])) (defprotocol UI (display-prompt [this message]) (get-input [this]) (prompt-for-input [this message])) (defrecord ConsoleUI [console-io] UI (display-prompt [this message] (io/display console-io message)) (get-input [this] (io/input console-io)) (prompt-for-input [this message] (.display-prompt this message) (.get-input this))) (defn create-console-ui [console-io] (map->ConsoleUI {:console-io console-io}))
Level 3 code on GitHub.
This code is very basic, but writing it helped my understanding of protocols. Hopefully it might help someone else who wants an example aside from the Clojure docs.
Note: lein run can be used to run the code.
0 notes
Text
mytaeddy hat auf deinen Eintrag geantwortet “sara <3”
full offence but i am crying
noah fence but !!SCHTOPP!!
#mytaeddy#saralein no why would you!!!!!! STOP! SO NICH!!!!! OB ICH HIER BIN...UND NICHT!!! DU WEINST HIER NICH RUM!!!#saralein#see you gotta tag now! it's a present be happy!!!
1 note
·
View note
Text
Marjolein when Sarah called her a good girl (she's about to be dog food)
8 notes
·
View notes
Text
pov Sarah Mckiggan tries to convince you to be a jester in her court after calling you a weak little coward and a sad lost waif
16 notes
·
View notes
Text
Fun and cute date idea! Trap the girl you like in a torturous death show and watch and giggle and eat popcorn as she runs away from person-eating dogs and watches her friends die right before her eyes ☺️
22 notes
·
View notes
Text
Someone: You can't just call someone a coward and try make them a jester in your court. That's not how you flirt
Sarah: But she's a poor little meow meow
13 notes
·
View notes
Text
Marjolein might not know what happened to everyone exactly once she and Jill got hellscaped, right? So...
Marjolein decides to try her hand playing hit game Transcendation. She gets through it well because in my heart she's a gamer, and gets to the final boss NPC. They start talking through the NPC's ai, and Marjolein is surprised at how realistic it is. She's far more interested in talking to the game AI than actually beating the game, she spends ages going it...
Which is how Marjolein and Mother accidentally become online girlfriends.
7 notes
·
View notes
Text
I think that Lloyd taught Sarah and Marjo how to fence and they practiced against eachother and Sarah would always pretend the only reason she was flushed and out of breath was because of the physical exercise and not because of the homoerotic nature of fighting with Marjolein
7 notes
·
View notes
Text
cringe culture is dead have we considered Sarah and Marjolein to Mother Mother songs specifically this one. Sarah singing this to Marjo. Yeah.
9 notes
·
View notes
Text
"A little girl talk" yeah. Sure. You're talking. Totally doing very heterosexual activities. There's no deep longing from Sarah at all that she may finally be able to form a relationship with someone without having to get them drunk and coerce them.
8 notes
·
View notes