membrane

zimablue 2022-02-01T13:31:16.930399Z

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

zimablue 2022-02-01T13:33:35.306759Z

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?)

zimablue 2022-02-01T15:48:50.089629Z

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)))

phronmophobic 2022-02-01T19:25:52.658679Z

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.

phronmophobic 2022-02-01T20:06:14.205639Z

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

phronmophobic 2022-02-01T20:07:24.328039Z

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.

zimablue 2022-02-01T21:58:45.328339Z

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
zimablue 2022-02-01T21:59:23.490639Z

does your first example work because defui DOES wire mapv?

phronmophobic 2022-02-01T22:00:47.572139Z

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

phronmophobic 2022-02-01T22:01:25.852099Z

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

zimablue 2022-02-01T22:02:06.454479Z

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

zimablue 2022-02-01T22:05:02.965299Z

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

phronmophobic 2022-02-01T22:05:04.272529Z

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

zimablue 2022-02-01T22:05:40.799209Z

and option 3 is event interception

zimablue 2022-02-01T22:05:59.400479Z

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

zimablue 2022-02-01T22:06:27.104429Z

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

phronmophobic 2022-02-01T22:06:29.407649Z

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