Fork me on GitHub
#membrane
<
2022-02-01
>
zimablue13:02:16

Hi, I continue to work with your framework although there are other things I am working on. For your component part to work, it seems to me that it necessarily needs to take functional inverses of all selection functions used inside defui

zimablue13:02:35

just wondering if that understanding is right, because: that seems like quite difficult black magic it must eventually run into some sort of "full employment theorem" (eg. you can't invert a hash function, as a silly example, or a function which is dependant upon the next prime as a slightly less silly one) it feels like quite a strong condition on the functions where it IS possible, meaning that all (state->component) interactions might fall into or close to another type of thing (statements expressible in core.logic, state machines?)

zimablue15:02:50

here's an example which I think doesn't work because the (mapping) relationship to invert is too hard

(defui checkboxes 
  [{:keys [checked?-states]}]
  (vec (map-indexed 
         (fn [idx val] 
           (translate-by-index 
             idx 
             (basic/checkbox {:checked? val}))) checked?-states)))

phronmophobic19:02:52

The design constraints for membrane's builtin components are: • No side effects in view or event functions • Receive data only through function arguments • corollary: No hidden/local state and don't use global state To make that easier, defui will try to do all of the wiring for you. defui doesn't automatically wire map-indexed , but there some alternatives that hopefully aren't offensive. In some cases, the alternatives are slightly more verbose, but you get all the wiring for free and all the components remain pure which has its own benefits. Option 1: explicitly derive val from checked?-states so that defui can track it for you

(defui checkboxes 
  [{:keys [checked?-states]}]
  (mapv (fn [idx]
          (let [val (nth checked?-states idx)]
            (translate-by-index 
             idx 
             (basic/checkbox {:checked? val}))))
        (range (count checked?-states))))
Option 2: Pass the reference to val explicitly
(defui checkboxes 
  [{:keys [checked?-states]}]
  (vec (map-indexed 
        (fn [idx val] 
          (translate-by-index 
           idx 
           (basic/checkbox {:checked? val
                            :$checked? [$checked?-states (list 'nth idx)]})))
        checked?-states)))
Options 3: Catch the ::basic/toggle event and return the intents explicitly
(defui checkboxes
  [{:keys [checked?-states]}]
  (vec (map-indexed 
        (fn [idx val] 
          (translate-by-index 
           idx
           (ui/on
            ::basic/toggle
            (fn [_]
              [[:update $checked?-states
                update idx not]])
            (basic/checkbox {:checked? val})))) checked?-states)))
There are more options, but those are the 3 main techniques.

phronmophobic20:02:14

> For your component part to work, it seems to me that it necessarily needs to take functional inverses of all selection functions used inside defui I would say that your understanding is correct with a few caveats. It's not required that all state used by a builtin component be derived using only known inversible function. All components are pure functions so you can always explicitly pass the reference for any state rather than have defui handle it for you. All event handlers are pure, so you can always override the event handler. That means you almost never have to rewrite a builtin component to use it, regardless of the context. The automatic wiring provided by defui is just for convenience and brevity. The worse case scenario is that you do what you would normally do in most other frameworks and wire up the state yourself. However, if there is another common idiom in your code base, you can often programmatically achieve the same convenience without rewriting any of the builtin components. As an example, I created an https://github.com/phronmophobic/membrane-re-frame-example that uses re-frame+membrane to build a desktop and terminal todo app. However, re-frame doesn't have a text input component since the web already has a builtin component. Instead of writing my own re-frame text input, I wrote a https://github.com/phronmophobic/membrane/blob/master/src/membrane/re_frame.cljc#L117 that will automatically convert any builtin membrane component into a re-frame component that works with re-frame's state management. I could then just use membrane's text input. > just wondering if that understanding is right, because: >   that seems like quite difficult black magic >   it must eventually run into some sort of "full employment theorem" (eg. you can't invert a hash function, as a silly example, or a function which is dependant upon the next prime as a slightly less silly one) >   it feels like quite a strong condition on the functions where it IS possible, meaning that all (state->component) interactions might fall into or close to another type of thing (statements expressible in core.logic, state machines?) My intuition was very similar to this sentiment at first. Even though some use cases do require the workarounds mentioned above, I was surprised by how infrequently workarounds were needed. Clojure emphasizes just using plain data for everything which means' supporting a handful of selection like get and nth covers a huge number of typical use cases.

phronmophobic20:02:24

Having said that, I still think of membrane as being in the design phase so if you have any suggestions or improvements, I'm open to feedback.

zimablue21:02:45

Thanks for your thoughtful reply, and especially some workarounds, I wasn't intending my posts as criticism, just checking I understood the intent/trying to think it through. It's late here, I'll hopefully try some more code tomorrow.

👍 1
zimablue21:02:23

does your first example work because defui DOES wire mapv?

phronmophobic22:02:47

It works because of the let statement. when it sees that val is (nth checked?-states idx), it can figure out how to create a reference to val

phronmophobic22:02:25

defui does understand for, but it currently doesn't support any destructuring (it will at some point)

zimablue22:02:06

for my failing example, it was tracing a keypath of val inside checked?-states, for your first example it would trace a path of (idx) -> (nth) right

zimablue22:02:02

option two is some hook that I was unaware of which lets you explicitly define the "setter" right? nice

phronmophobic22:02:04

for the failing example, it has no idea where val is coming from

zimablue22:02:40

and option 3 is event interception

zimablue22:02:59

nice, that is quite flexible I think, I will try them out

zimablue22:02:27

I'm having fun so far, this is a very clojure-esque library that's so close to magic that it's disconcerting

phronmophobic22:02:29

> option two is some hook that I was unaware of which lets you explicitly define the "setter" right? nice yes. It's definitely a feature that could use more documentation. I've been calling it the "reference" rather than a "setter" since the reference let's you do more than just update the value.