The string to specify the column types is not a terrible idea. Does it have other configuration options, like whether or not to assume the first row is the headers, or specifying the separator character?
The typecode-string approach in Lil is very similar to how Q handles it with dyadic 0:.
In this specific example I could do without the typecode-string since arithmetic operators like sum, -, and * will coerce string columns into numbers, but I think this way is cleaner.
If I knew the CSV file didn't have built-in headers, I'd write the Lil script like this:
purchases:readcsv["country,amount,discount\n",read["headerless.csv"] "sii"]