Translating to CLJS

clojurescript

#1

In an effort to learn ClojureScript, I’m wanting to do quick/simple examples I’ve either done before or that are used by other languages. The first one I’m trying is one of Elm’s example of a simple counter.

I’m using only shadow-cljs and hiccups. I’d like to do this as minimally as possible for learning purposes. Using Reagent would obviously make this trivial, but I’d rather not.

Here’s what I have so far:

(ns counter.core
  (:require-macros [hiccups.core :as hiccups :refer [html]])
  (:require [hiccups.runtime :as hiccupsrt]))

(def init-model (atom 0))

(defn view
  [model]
  (html [:div
         [:div (str "Count: " model)]
         [:button "+"]
         [:button "-"]]))

(defn update-model
  [msg model]
  (case msg
    "plus" (swap! model inc)
    "minus" (swap! model dec)
    model))

(defn ^:export main []
  (set! (.-innerHTML (js/document.getElementById "app"))
        (view (update-model "plus" @init-model))))

To test what I have thus far, I wanted to hardcode arguments when I call the view function there at the bottom. I’m getting the following error when I do:

Error: No protocol method ISwap.-swap! defined for type number: 0

I’m not really sure what to do with this. I couldn’t find much help elsewhere online when searching for this error message. Thanks for any help you could possibly offer me.


#2

update-model uses swap! which works on an atom. If you pass @model to it, it gets an integer instead of an atom. Use (update-model msg model) instead.

view on the other hand takes a value, not an atom. Pass @model to it


#3

Check out my gist

If you see an error on load, just press Ctrl+Enter. Not sure why, but Klipse seems to need to double compile the defmulti for it to work.

I didn’t even use hiccup, so I’m doing string templating to create the HTML by using cl-format. So this is a no dependency implementation. It only uses the ClojureScript standard libs.

The thing about the ELM example, is that beginnerProgram is a frontend framework in a way. It does a few things for you under the hood. So I needed to re-create a similar framework, thats my msg and -redraw functions. With those in place, you can build the counter in the same way you would in ELM.

  • So model must be an atom.
  • You create new update defmethods for every messages your app will have. Like in ELM, update takes a message, the model (already unwrapped from the atom).
  • And your view takes the model (already unwrapped once again), and returns HTML. Here I’ve used cl-format, but you can replace this with hiccup if you prefer.
  • Similarly to ELM, you can bind events in the HTML, and have it send a msg to your update function, so it can take actions on the model and redraw the html.
  • In this case, you can also pass arguments to your commands, like my +10 and -10 buttons do. Technically, I didn’t need :increment and :decrement commands, but I wanted to show how the arguments are optional.

#4

That did it. I guess I only thought I knew how atoms worked. Guess they’re still a little fuzzy, but it totally makes sense why I was having problems and this worked.

Thanks!


#5

This was great! Thank you so much. I was foreseeing the problem with implementing a beginnerProgram, so this helped me out immensely. I got your suggestion working with what I was trying (with hiccups and everything), and it works great. Again, thank you so much!

I do have a couple of questions:

  • I definitely had to go back and re-read about defmulti and defmethod. Is this a more Clojure-idiomatic way as opposed to using case statements? Or are these preferred in future instances where the defmethods can get more complex than one would want to deal with in a case statement, and it’s good to get into this habit?
  • When specifically calling counter.core.msg() for the onclick event, I wasn’t sure why I wasn’t able to do something like (msg "increment").

Anyway, this was good. This helps a lot.


#6

There’s nothing wrong with using the case macro. In the end, defmulti is pretty much a glorified case. You can also easilly refactor a case to a defmulti, so its easy to start with a case and later move to a defmulti if needed.

The distinction is two fold:

  1. Syntactic: They differ in syntax, and so sometimes the syntax of one is more manageable and maybe easier to read than the other, depending on the situation. I’d say for small number of cases and when each case does very little, case is probably nicer.

  2. Semantic: They actually differ a little semantically. Case is closed to extension. Defmulti is open to extension. You can add more defmethod from outside the function, even from another namespace. So for situations where you expect people who use your namespace to add their own cases, only defmulti can do it. For case, they’ll need access to the source, and modify the original case expression.

My guess is that the hiccup lib you’re using doesn’t support that.


#7

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.