tying some loose ends

ย 8th February 2024 at 9:48pm

Some preliminary hindsights from CP

Today I had my first class of cรกlculo de programas, and it was interesting. I recognized many of the motivations behind the theory of it all (the functional programming, the need of knowing whether a program is correct, etc.) and I see that even without having no knowledge of the contents (yet?), this project is shaping up nicely to fit with its best practices.

In particular, I'm proud of having designed it so that the procedure that satisfies my requirements (getting information from an API and creating tiddlers with it) will most likely be a composition of different functions. Not only that, the development of this current stage hasn't been linear: I've designed some API requests, and I've managed to create some struct instances matching those requirements, but it is clear that if it goes from A to B to C, I am currently trying to work out the B while A and C are (mostly) done.

Handling the groupId intermediate steps.

Each snatched record API response has a groupId, which I need to query further to get more information.

; retrieves a list of groupId from each record in RecordsVector
; RecordsVector -> List[groupId]
(define (get-groupids-from-records rec-vec)
  (map (lambda (rec) (cdr (assoc 'groupId rec)))
	   (vector->list rec-vec)))
;; NOTE testing with srfi-67 `list-compare` wasn't successful
;; there's a problem with the function, to maybe tackle later.
;(test "can obtain groupIds from records vector"
	;#t
	;(eq? 0
		 ;(list-compare (get-groupids-from-records (get-records snatched-response))
					   ;(list 372720 36086 337601 881619 2187250 53404 28235 120741 19522 1981603))))

While testing this function (nothing too fancy: it just retrieves a field from all instances of record in a vector), I devised a test in which I compare two lists. But this is not built-in the Scheme language; in fact, it is implemented in srfi-67...but it so happens that the module is not correctly set up. I suppose this happens when using smaller, more niche ecossystems. It's possible that I'll help my scheme friend in fixing this is the near future, but it is paramount that I finish this script first.

; for each groupId, produce a request to the groupId route
; List[groupId] -> List[intarweb#uri]
(define (get-uris-for-groupids-request groupids)
  (map (lambda (id) (build-uri-with-fields GROUP-URI (list (list "id" (number->string id)))))
	   groupids))
(test "creates a list of requests to groupId route from a list of groupId"
	  #t
	  (every string?
			(get-uris-for-groupids-request (get-groupids-from-records (get-records snatched-response)))))

; for each groupId, produce a request to the groupId route
; List[intarweb#uri] -> List[intarweb#response]
(define (get-response-from-group-endpoint groupid-uris)
  (map (lambda (uri) (get-response-from-endpoint uri))
	   (groupid-uris)))
(test "get a list of sucessful responses to groupId endpoint"
	  #t
	  (every (lambda (msg) (string=? "success" msg))
			 (map (lambda (response)  (cdar response))
				  (get-response-from-group-endpoint
					 (get-uris-for-groupids-request
					  (get-groupids-from-records
						 (get-records snatched-response)))))))

If I were to assemble all the function signatures,

; snatched-route-response -> RecordsVector
; RecordsVector -> List[groupId]
; List[groupId] -> List[intarweb#uri]
; List[intarweb#uri] -> List[intarweb#response]

There are four functions at play and they are all chained in the last test. I'm not sure if it is possible to make it more succint (it feels a bit immature to leave it this way). I'm aware that error handling was not done (yet?), and it could easily fail for 429: Too Many Requests (as it has already happened).

Finally, the last function (get-records was already defined):


; retrieves a vector of records from the intarweb#response
; snatched-route-response -> RecordsVector
(define (get-records json-response)
	(cdadar (cdr json-response)))
(test "can obtain vector of records"
	  #t
	  (vector? (get-records snatched-response)))

;; creates a list of record-info objects from a record vector
;; RecordsVector -> List[record-info]
(define (produces-record-info-list-from-vector-of-records rec-vec)
  (map (lambda (rec) (make-record-info-obj-from-groupid-response rec))
	   (get-response-from-group-endpoint
		 (get-uris-for-groupids-request
		   (get-groupids-from-records rec-vec)))))
(test "creates a list of record-info structs from a vector of records"
	  #t
	  (every record-info?
		(produces-record-info-list-from-vector-of-records (get-records snatched-response))))

From here onward, I can focus on the tiddler creation. It is likely that some of this code needs care โ€” the tests could be improved, as it is repeating a lot of calls, resulting in 429 error codes โ€” and further improvements: I am not fetching the album artwork of each record, and I think that would be a nice addition to this platform.

I was just starting to refactor the tests, and I realised something:

; for each groupId, produce a request to the groupId route
; List[intarweb#uri] -> List[intarweb#response]
(define (get-response-from-group-endpoint groupid-uris)
  (map (lambda (uri) (get-response-from-endpoint uri))
	   groupid-uris))
(test "get a list of sucessful responses to groupId endpoint"
	  #t
	  (every (lambda (msg) (string=? "success" msg))
			 (map (lambda (response)  (cdar response))
				  (get-response-from-group-endpoint
					(get-uris-for-groupids-request
					  (get-groupids-from-records
						(get-records snatched-response)))))))

this can be promptly refactored into this

; for each groupId, produce a request to the groupId route
; List[intarweb#uri] -> List[intarweb#response]
(define (get-response-from-group-endpoint groupid-uris)
  (map (lambda (uri) (get-response-from-endpoint uri))
	   groupid-uris))
(define groupid-responses (map (lambda (response)  (cdar response))
				  (get-response-from-group-endpoint
					(get-uris-for-groupids-request
					  (get-groupids-from-records
						(get-records snatched-response))))))
(test "get a list of sucessful responses to groupId endpoint"
	  #t
	  (every (lambda (msg) (string=? "success" msg))
			 groupid-responses))

but in the next function,

;; creates a list of record-info objects from a record vector
;; RecordsVector -> List[record-info]
(define (produces-record-info-list-from-vector-of-records rec-vec)
  (map (lambda (rec) (make-record-info-obj-from-groupid-response rec))
	   (get-response-from-group-endpoint
		 (get-uris-for-groupids-request
		   (get-groupids-from-records rec-vec)))))
(test "creates a list of record-info structs from a vector of records"
	  #t
	  (every record-info?
		(produces-record-info-list-from-vector-of-records (get-records snatched-response))))

it is impossible to reuse the same information; thus, the refactor is a bit useless. What I need is the following function signature:

;; creates a list of record-info objects from a list of groupId responses
;; List[intarweb#response] -> List[record-info]

And then I can assembly a proper function that receives an API request and produces a list of record-info.

;; creates a list of record-info objects from a list of groupId responses
;; List[intarweb#response] -> List[record-info]
(define (produces-record-info-list-from-vector-of-records groupid-responses)
  (map (lambda (rec) (make-record-info-obj-from-groupid-response rec))
	   groupid-responses)
(test "creates a list of record-info structs from a vector of records"
	  #t
	  (every record-info?
		(produces-record-info-list-from-vector-of-records groupid-responses)))

A final version follows:

; for each groupId, produce a request to the groupId route
; List[intarweb#uri] -> List[intarweb#response]
(define (get-response-from-group-endpoint groupid-uris)
  (map (lambda (uri) (get-response-from-endpoint uri))
	   groupid-uris))
; NOTE this should be loaded from a test file, too, akin to test-snatched-response.json
(define groupid-responses (get-response-from-group-endpoint
							(get-uris-for-groupids-request
							  (get-groupids-from-records
								(get-records snatched-response)))))
(test "get a list of sucessful responses to groupId endpoint"
	  #t
	  (every (lambda (msg) (string=? "success" msg))
		     (map (lambda (response) (cdar response))
					   groupid-responses)))

;; creates a list of record-info objects from a list of groupId responses
;; List[intarweb#response] -> List[record-info]
(define (produces-record-info-list-from-vector-of-records groupid-responses)
  (map (lambda (rec) (make-record-info-obj-from-groupid-response rec))
	   groupid-responses))
(test "creates a list of record-info structs from a vector of records"
	  #t
	  (every record-info?
		(produces-record-info-list-from-vector-of-records groupid-responses)))