drym-org / qi

An embeddable flow-oriented language.
58 stars 12 forks source link

Mixing fine-grained and blanket templates #61

Open countvajhula opened 2 years ago

countvajhula commented 2 years ago

Currently, _ and __ can be used independently but not together.

There are cases where it could make sense to use them together:

(~> ("a" "c" "d") (string-append _ "b" __))

It'd be worth exploring what it would take for this to work -- ideally it can be done while leveraging functionality provided by dependencies (such as fancy-app), without having to reinvent it.

Also, _ can be used in the first (function) position, as of #58 . But __ cannot. Would it be worth supporting a case like this one: (~> (+ 1 2) (__ 3 4 5))?

NoahStoryM commented 2 years ago

There are cases where it could make sense to use them together:

Yeah, it looks good to me!

But cannot. Would it be worth supporting a case like this one: (~> (+ 1 2) ( 3 4 5))?

Well, I'm looking into whether >- makes sense, I think (__ args ...) would be useful, but not for matching function and arguments, it probably only matches functions.

Let me introduce the >- in my mind -- it's similar to ==, but splits the input according to the arguments (the functions' arity) :

(~> (1 2 3 4 5 6 7 8 9 10) ; 10 values
    (>-
     +    ; at least 0, match 0 values.
     add1 ; 1
     sub1 ; 1
     -    ; at least 1, match 8 values.
     ))
=> (values
    0    ; (+)
    2    ; (add1 1)
    1    ; (sub1 2)
    -46  ; (- 3 4 5 6 7 8 9 10)
    )

So I guess __ in first position may work like >-:

(~> (+ add1 sub1 -) (__ 1 2 3 4 5 6 7 8 9 10))
=> (values
    0    ; (+)
    2    ; (add1 1)
    1    ; (sub1 2)
    -46  ; (- 3 4 5 6 7 8 9 10)
    )

And it makes sense in category theory: assume there are f : A -> B and g : X -> Y, then f * g can be regarded as a function A * X -> B * Y.

benknoble commented 2 years ago

Disclaimer: this has nothing to do with the actual issue RE: _ and __.

Let me introduce the >- in my mind -- it's similar to ==, but splits the input according to the arguments (the functions' arity) :

(~> (1 2 3 4 5 6 7 8 9 10) ; 10 values
    (>-
     +    ; at least 0, match 0 values.
     add1 ; 1
     sub1 ; 1
     -    ; at least 1, match 8 values.
     ))
=> (values
    0    ; (+)
    2    ; (add1 1)
    1    ; (sub1 2)
    -46  ; (- 3 4 5 6 7 8 9 10)
    )

This seems ambiguous to me: another satisfying assignment of inputs to flows would be

(+ 1 2 3 4 5 6 7)
(add1 8)
(sub1 9)
(- 10)

and there are 6 (?) more based on how many inputs are given to +. There would need to be a rule about how to solve the constraints of multiple multi-arity flows to decide which inputs go where. And that sounds… well… hard.

NoahStoryM commented 2 years ago

Disclaimer: this has nothing to do with the actual issue RE: _ and __.

Oh, I've been working on how to use function values\, and one strategy that came to my mind is the >- I mentioned earlier. After seeing the __ in the first position, I felt that __ could also be used to use function values. And in order to illustrate this usage, I seem to have introduced some content that is not related to this issue. Do I need to open a new issue to discuss it?

There would need to be a rule about how to solve the constraints of multiple multi-arity flows to decide which inputs go where. And that sounds… well… hard.

Yes, how to decide the input is a question that has been confusing me. I tried to implement one with amb:

(define (split-input n arity*)
  (let loop ([n n] [arity* arity*] [res '()])
    (cond
      [(zero? n) (reverse res)]
      [(null? arity*) (amb)]
      [else
       (define arity (car arity*))
       (define m
         (match arity
           [(? natural?) arity]
           [(? arity-at-least?) (pick-least n (list arity))]
           [(? list?) (pick-least n arity)]))
       (unless (>= n m) (amb))
       (loop (- n m) (cdr arity*) (cons m res))])))
(define pick-least
  (λ (n args)
    (let loop ([args args])
      (define arg (car args))
      (match arg
        [(? natural? m)
         (unless (>= n m) (amb))
         (amb m (loop (cdr args)))]
        [(? arity-at-least?)
         (let loop0 ([m (arity-at-least-value arg)])
           (unless (>= n m) (amb))
           (amb m (loop0 (add1 m))))]))))
(split-input 10 (map procedure-arity (list + add1 sub1 -))) ; '(0 1 1 8)
(split-input 10 (map procedure-arity (list + add1 sub1)))   ; '(8 1 1)

Of course, using amb is an inefficient approach, and I'm investigating whether there is a more efficient solution. But I want to point out that in general, >- is unlikely to enumerate very large data, so it might make sense to implement it by amb. And even if dealing with complex cases is necessary, using procedure-reduce-arity can avoid a lot of overhead.

countvajhula commented 2 years ago

Discussing these in a purely theoretical way could be interesting, but in general I'd love to also see examples where proposed additions to the language would make some practical cases simpler or better in some way. And yes, this should be a separate issue 🙂

NoahStoryM commented 2 years ago

Ah, I don't have much experience with qi, but I thought something similar would be useful when I tried doing meru exercises. And when I was learning category theory I did see some constructs using f * g as morphism (though I'm not sure how these constructs manifest in programming yet, I need some time to investigate further).

I need some time to sort out my previous thoughts, after that I will open a new issue. :)