Apply values to a procedure

Hello,

I was in the need of applying a procedure to a result that return 3 values (x,y,z) to put this in a list.

I know this works:

(call-with-values (lambda () (values 1 2 3)) list)

but i find it not pleasant to wear the lamba () ... just because it is a convention? or perheaps there is something to understand that i do not see? so i decided to wrote this little macro in the way apply works:

(define-syntax apply-values
  (syntax-rules ()
    ((_ proc some-values) (call-with-values (lambda () some-values) proc))))

example:

(apply-values list (values 1 2 3))
'(1 2 3)

i think it is more simple and readable than the solution with call-with-values but i appreciate to know if i'm wrong or missing something and if it exist other syntax to do it?

Regards,

Damien

You can use (thunk body ...) instead of (lambda () ...) in this case.

It's not convention, though: it's explicitly used to delay evaluation. The call-with-values procedure expects a procedure of 0 arguments that produces multiple values, which are handed to the continuing procedure (second argument). Because Racket does not permit things like (add1 (values 1 2) 3), similarly (call-with-values (values 1 2 3) list) doesn't make sense.

The "extra notation" is not in the way so much with (call-with-values (thunk (f args)) list) (in other words, extracting the computation of the values to a named function; if it's nullary, simply (call-with-values f list)). Depending on your need you could also use define-values, let-values, match/values (and several other match cousins), or Qi.

3 Likes

if it exist other syntax to do it?

Warning: Self-promotion ahead

SRFI 210: Procedures and Syntax for Multiple Values has a lot of useful stuff to make working with multiple values cleaner. It's available for Racket in my extra-srfi-libs package. With it, your example could be written as

$ racket
Welcome to Racket v8.15 [cs].
> (require srfi/210)
> (list/mv (values 1 2 3))
'(1 2 3)
>

Documentation for list/mv:

(list/mv element1 … producer)

Syntax: Each element and producer can be any expression.

Semantics: A list/mv expression is evaluated as follows: Each element and producer are evaluated in an unspecified order. A list constructed from the single values of the elements and the multiple values of producer in that order is then returned.

2 Likes

yes i understood it was not only conventions for nothing but a way to , as you say, delay the evaluation.

> (call-with-values (lambda () (values 1 2 3)) list)
'(1 2 3)
> (call-with-values (thunk (values 1 2 3)) list)
'(1 2 3)

i can avoid define and let , only use define for procedures with Scheme+ , i'm sometimes learn the philosophy of Qi, and search for examples in concrete code, i would be interested in having a solution to this problem (see below) with Qi?

for now i have a text file and read the lines in a vector (tdl),then parse them to generate a 3D point list compatible with plot , those solutions worked :

(define Lplot (map list->vector (call-with-values (thunk (for/lists (Lx Ly Lz)
									      ([tr tdl])
									      (apply values (rest (string-split tr)))))
					                    list)))

:joy:

or the one of @shawnw with list/mv works too:

(define Lplot (map list->vector (list/mv (for/lists (Lx Ly Lz)
							      ([tr tdl]) 
							      (apply values (rest (string-split tr)))))))

also my solution with my apply-values works:

(define Lplot (map list->vector (apply-values list
	  						(for/lists (Lx Ly Lz)
	  							   ([tr tdl]) 
	  							   (apply values (rest (string-split tr)))))))

indeed there is a more compact solution with Racket for/lists and #:result :

(define Lplot  (for/lists (Lx Ly Lz #:result (map list->vector (list Lx Ly Lz)))
	  			    ([tr tdl])
	  			    (apply values (rest (string-split tr)))))

Here's one solution in Qi:

(define Lplot (~> (tdl) vector->list △ (>< (~> string-split rest)) (△ vector) ▽))

("separate") and ("collect") are the standard way to translate between lists and values in Qi.

Note: this solution uses Qi 5 which is in the process of being released this week, specifically this change regarding the "zip" functionality used in your code, so you will need to wait a few days before trying the above solution :slight_smile:

Otherwise, if you can't wait, this variant should also work in the currently-released Qi:

(define Lplot (~>> (tdl) vector->list △ (>< (~> string-split rest)) (map vector)))

oh... i had made an error that i did not corrected online till, if you @countvajhula wrote the Qi code starting from my code? i over-complexified the problem, plot3d take not the arguments of my previous code, the correct code is:

(define Lplot  (for/list ([tr tdl])
	  			    (list->vector (map string->number (rest (string-split tr))))))

it is simplier to code.

tris a string of the tdllist like this sample of text file:

# index		X [Rm]		Y [Rm]		Z [Rm]
0.000000	-5.226162	-0.133805	1.462782
1.000000	-5.239695	-0.133871	1.446325
2.000000	-5.253049	-0.133932	1.429818
...

when plotted the trajectory (ref. 1) in Mercury solar orbital (MSO) coordinates looks like this:

or like this : (but problem with auto scaling on y, i should fix it...)

that would be interesting to define Lplot in Qi but tonight i still have not the solution, because my skill in Qi are of beginner, i let you @countvajhula help me.

I will think to that tomorrow.

[1] :
https://www.researchgate.net/figure/The-BepiColombo-baseline-trajectory-with-a-launch-on-19-July-2014-and-arrival-on-13-Nov_fig1_236163826

http://scienceandsf.com/index.php/2020/04/22/the-bepicolombo-space-probe-is-on-its-way-to-mercury-this-will-be-only-the-third-mission-to-the-solar-systems-innermost-world/

Those charts look very cool!

In the updated version, using a standard for/list comprehension seems like a fine way to do it. But if you want to use higher-order functions instead, here's how you could do it in Qi:

(require qi qi/list)
(define Lplot
  (~> (tdl)
    (map (~> string-split rest (map string->number) list->vector))))

The only differences are that the outer for/list is converted to a map, and the inner right-to-left nesting is converted to left-to-right ~>.

Note: Requires Qi 5.

with the current packaged version of Qi (not Qi 5) i got an error:

Welcome to DrRacket, version 8.14 [cs].
Language: Determine language from source; memory limit: 8192 MB.
. ~>: bad syntax in: (~> string-split rest (map string->number) list->vector)

but i suppose there exist a solution with the current version, that would help me to learn Qi and i want to use it in the actual code and more.

I'm searching for a a current solution....

i'm near a solution (perheaps not the best Qi code) :

(define Lplot (map (☯ (~> string-split rest (map string->number _) list->vector)) tdl))

but in Qi my parsing fails because of /t (tabs) that does not appear with Racket classic code , i do not understand,if it is related to Qi or no?

here is a screenshot showing it seems to works in REPL with littteral string but not from code with string get from files, the more strange is that with Racket code and the same library function string-splitit is ok but not when used by Qi :thinking:

a solution is this :

(define Lplot (map (☯ (~> string-split rest (map string->number _) list->vector)) (vector->list tdl)))

but it is part of flow and part of other transformation with map and vector->list , this should be integrated in the same flow....

Sans Qi 5, I would consider

(require qi)
(define Lplot
(~> (tdl)
(sep (~> string-split rest (sep string->number) vector))
collect))

using the (sep flo) is (~> sep (amp flo)) idiom. This version also avoids collecting the result of (amp string->number) into a list only to throw it to list->vector; with Qi, we can just pass all the values to the vector constructor :wink:

again, i used list but instead i have a vector as input, sort of this works:

 (~> ((vector->list #("0 0.1 0.2 0.3" "1 1.1 1.2 1.3")))
			    (△ (~> string-split rest (△ string->number) vector))
			    ▽)
'(#(0.1 0.2 0.3) #(1.1 1.2 1.3))

but i do not understand well why we need 2 ~> ? can not it be done with a global single one ~> ? and is it possible to insert the initial vector->list in the flow?

i begin to understand Qi :

> (~> ((~> (#("0 0.1 0.2 0.3" "1 1.1 1.2 1.3")) vector->list)) (△ (~> string-split rest (△ string->number) vector))  ▽)
'(#(0.1 0.2 0.3) #(1.1 1.2 1.3))
1 Like

You could remove the vector->list so it's part of the flow:

(~> (#("0 0.1 0.2 0.3" "1 1.1 1.2 1.3"))
  vector->list (△ (~> string-split rest (△ string->number) vector))  ▽)

but i do not understand well why we need 2 ~> ? can not it be done with a global single one ~> ? and is it possible to insert the initial vector->list in the flow?

In this case, the form expects a single flow parameter (which could be a use of ~>). But if you like, since Qi is macro-extensible just like Racket, you could define your own variant that implicitly does ~>:

(define-qi-syntax-rule (△> f ...)
  (△ (~> f ...)))

(~> (#("0 0.1 0.2 0.3" "1 1.1 1.2 1.3"))
    vector->list (△> string-split rest (△ string->number) vector)  ▽)
1 Like

this is working with vector->list also in the flow:

(define Lplot (~> ((~> (tdl) vector->list)) (△ (~> string-split rest (△ string->number) vector)) ▽))

If i understand Qi is working with list , so vector have to be converted in list, perheaps a silly question but isn't it possible to make it works with vectors too ? as it already use values too and perheaps other types...

and this is working with the SRFI-105 curly infix :

(define Lplot {({(tdl) ~> vector->list}) ~> (△ {string-split ~> rest ~> (△ string->number) ~> vector}) ~> ▽})

but when activating a strict SRFI 105 compatibility mode by setting:

(define srfi-strict #t) ; enable strict compatibility with SRFI 105

in SRFI-105-curly-infix.rkt in the source code of SRFI-105 parser which require to have downloaded the source code , not just installed it via package manager.

also this is not yet working in infix:

(define Lplot {((tdl) ~> vector->list) ~> (△ (string-split ~> rest ~> (△ string->number) ~> vector)) ~> ▽})

but i will try to support it in the next release of Scheme+ now i know Qi.

yes @countvajhula ,simply ,your above solution works too:

(define Lplot (~> (tdl) vector->list (△ (~> string-split rest (△ string->number) vector)) ▽))
;; works wit strict SRFI-105 enabled
(define Lplot {(tdl) ~> vector->list ~> (△ {string-split ~> rest ~> (△ string->number) ~> vector}) ~> ▽})

If i understand Qi is working with list , so vector have to be converted in list, perheaps a silly question but isn't it possible to make it works with vectors too ? as it already use values too and perheaps other types...

Yeah, making work with generic sequences or collections would be quite interesting and coincidentally also came up in today's Qi meetup.

;; works wit strict SRFI-105 enabled
(define Lplot {(tdl) ~> vector->list ~> (△ {string-split ~> rest ~> (△ string->number) ~> vector}) ~> ▽})

Nice! Although, I'm not sure if it will be easy to express nonlinear flows in infix syntax, for instance, things like == and -<. If you manage to do that I'd be curious to learn about how you do it.

ah ah "Les grands esprits se rencontrent." as we say in France. (origine: Voltaire)

It should be feasible ,if not already done, indeed the infix procedures i wrote are becoming more and more hard. But the real question is that ~> is well describing a flow like an arrow and is well readable and understandable in infix notation for a linear flow but -< is more readable in prefix notation, as the symbol -< represent a graph going in 2 directions only a 2D representation can show it , but the code are written linearly. It is like that, nothing to do i think unless showing it as we already do it in scheme by putting the 2 flow on 2 different lines:

(define-flow rms
  (~> △ (-< (~> (>< sqr) +)
            count) / sqrt))

and it displays better in prefix than in infix.

1 Like

i can even go further with Qi and start a pipe-line that begin with the reading of the disk file like that:

(define Lplot (~> (src) read-lines cddddr cdr (△ (~> string-split rest (△ string->number) vector)) ▽))

note: srcis "../BepiColombo-Mio_MSO-orbit_1min_short.txt"

just a question to replace cddddr cdr by vectors (because cdddddr does not exist) i want to use a slicing procedure of my own like this ($bracket-apply$ v 5 :) , $bracket-apply$ is part of SRFI 105 but how can i pass the parameter v which will be the flow (then a vector)? i tried ($bracket-apply$ _ 5 :) but i have an error.

Is $bracket-apply$ a macro? If so, this section of the docs contains some recipes.

Another option is to use drop from racket/list, which is a regular function.

And thanks for sharing your insights re: using infix and prefix notation side by side. You're totally right, it doesn't have to be one or the other.