0
votes

I'm trying to read data from a database in typed Racket and convert it to a list of structures. The code below is the untyped version that works perfectly. This is the smallest bit I can make that demonstrates the problem.

#lang racket

(require db)

(define dbc (sqlite3-connect #:database "dmdb.sqlite3" #:mode 'read/write))

(struct player (name size str dex con base-hp current-hp base-ac attack) #:mutable)

(define get-players
  (lambda (c)
    (for/list
        ([(n s str dex con bhp chp bac attack)
            (in-query c "select * from players")])
      (player n s str dex con bhp chp bac attack))))

The typed version of the procedure is as shown below:

#lang type/racket
(require typed/db)

(: get-players (-> Connection (Listof player)))
(define get-players
  (lambda (c)
    (for/list
        ([(n s str dex con bhp chp bac attack)
            (in-query c "select * from players")])
      (player n s str dex con bhp chp bac attack))))

When I try to compile it in typed Racket I receive some strange error messages:

Type Checker: Expression should produce 9 values, but produces 1 values of
types SQL-Datum in: (for/list (((n s str dex con bhp chp bac attack
(in-query c "select * from players"))) (player n s str dex con bhp chp bac
attack))

Again, the code works perfectly as long as it is not typed.

2
Where are you getting the Connection type from? - Leif Andersen
I forgot to include the #lang typed/racket and (require typed/db) in the second example. I've edited the post to correct that. Thanks for noticing. - querist
Ah, okay, thanks. I have an answer now, writing it up. - Leif Andersen
Fantastic! Thank you. - querist

2 Answers

0
votes

It looks like even though the syntax of for in typed racket supports multiple values, you cannot actually type check a sequence of multiple values in typed/racket. You can, however, convert a sequence of values into a list with the in-values-sequence. From there, you can use define-values to split out the list back into values, or just use the list inline. For the function you gave, this would look like:

(: get-players (-> Connection (Listof player)))
(define (get-players c)
  (for/list ([a (in-values-sequence (in-query c "select * from players"))])
    (player (list-ref a 0)
            (list-ref a 1)
            (list-ref a 2)
            (list-ref a 3)
            (list-ref a 4)
            (list-ref a 5)
            (list-ref a 6)
            (list-ref a 7)
            (list-ref a 8))))

I suspect this might have an effect on your program's performance, but I can't say for sure what. If you want to create a sequence of multiple values, you could ask the people on the racket mailing list for help, as they likely know something I don't.

0
votes

With some help from Leif Andersen I found an answer. Below is the smallest bit of code I could create that allowed me to test my solution. It involves using query-rows instead of in-query to extract the data and then manipulating the resulting list of vectors.

#lang typed/racket
(require typed/db/base typed/db/sqlite3)


; for testing - cut and paste into interactions window
(define dbc (sqlite3-connect #:database "dmdb.sqlite3" #:mode 'read/write))

(struct player ([name : String] [size : String]
                [str : Integer] [dex : Integer] [con : Integer] [base-hp : Integer]
                [current-hp : Integer]
                [base-ac : Integer] [attack : Integer]) #:mutable )

(define lv (query-rows dbc "select * from players"))

(define ll : (Listof (Listof SQL-Datum))
    (for/list ((v lv))
      (vector->list v)))

(: lv->ll (-> (Listof (Vectorof SQL-Datum)) (Listof (Listof SQL-Datum))))
(define lv->ll
  (lambda (llv)
    (for/list ((v llv))
      (vector->list v))))

(: list->player (-> (Listof SQL-Datum) player))
(define list->player
  (lambda (a)
    (player (cast (list-ref a 0) String)
            (cast (list-ref a 1) String)
            (cast (list-ref a 2) Integer)
            (cast (list-ref a 3) Integer)
            (cast (list-ref a 4) Integer)
            (cast (list-ref a 5) Integer)
            (cast (list-ref a 6) Integer)
            (cast (list-ref a 7) Integer)
            (cast (list-ref a 8) Integer))))

(: list->playerlist (-> (Listof (Listof SQL-Datum)) (Listof player)))
(define list->playerlist
  (lambda (l)
    (for/list ((p l))
      (list->player p))))

(: listvectors->playerlist (-> (Listof (Vectorof SQL-Datum)) (Listof player)))
(define listvectors->playerlist
  (lambda (l)
    (list->playerlist (lv->ll l))))

(disconnect dbc)

I know that casts are considered ugly in some circles, but I needed to force the data types and I'm trusting that no-one is going to screw with the database once the program's out there. If they mess up their DB, it's their own problem, I guess.