[racket] Use of map and eval to evaluate symbol in namespace

From: Henry Lenzi (henry.lenzi at gmail.com)
Date: Sun Aug 3 15:29:10 EDT 2014

; Hello all --
; So here's how I solve all those little problems regarding symbols
and evaluation of medication definitions.
; Would you please bear with me? I apologize for the length.
; This is the approach I've taken. I've chosen no to use any macrology
or parser/lexer technique because I don't grok them and they
; don't really seem necessary, for reasons explained in the code comments.
; I have not decided to hash tables, for the following reason: there's
a part of the code (the drug definitions, the instructions), that
; should be easy enough for non-programmers to edit. If they are kept
very simple, it's possible, because the users have to edit those
; files. So, even though it is source code, it's not as intimidating
as editing source code if hash tables.
; Another aspect is that I hope modules provided some sort of safety
in terms of syntax checking. That is to say, if you used make a
; typo in the medication part of the DSL, the system will (hopefully)
bork because no such module exists. I believe this also creates
; an opportunity for "syntax validation" if a proper input phase is
designed. But Lisp/Scheme being a dynamic language, the run-time
; will bork immediately once it sees funny things. This is a way to
guarantee the DSL is correct, which we get for free by using Racket.
; A fourth aspect is that, if each drug is kept a different module
(which I haven't done here, BTW), then we can make for easier
; internationalization, by keeping modules by languages, e.g.,
hctz25-en, hctz25-pt_br. I believe Dan has an interest in this project
; so  it's best to design with that in mind.
; Final comment regards "database". We get "database" for free, by
registering prescriptions with patient register numbers. The OS
; takes care of pretty musch anything else. And there's no need for
atomicity and concurrency. Like I said, this is stupid code.
#lang racket

; code-review-for-racketeers-2014-08-03-a.rkt
; For this exercise, suppose a Recipe.txt file. Let´s suppose the idea
is that the physician
; has two options: 1) he or she opens Notepad and writes the
prescription file (Recipe.text);
; 2) or, the software asks for inputs and writes the file (this will
not be covered in this
; exercise). The written prescription in the shorthand DSL would look
like below, with the
; exception of a first field with patient ID data not included (to be
done later).
; The prescription has a rigid syntax would look like this (line
breaks included):
; 1-
; hctz25 30 pl 1xd
; 2-
; simva20 30 pl 1xn

; Needed for EVAL, used later on
(define-namespace-anchor a)

; These definitions should be in a different module.
; This way we get syntax checking for free.
; MED - medication. Includes dosage.
(define hctz25 "Hydrochlorothiazide 25mg")
(define simva20 "Simvastatin 20mg")
; FORM - whether the patient will take home pills, a tube, a flask, capsules
(define pl "pills")
; POS - posology, whether the patient will take 1 pill 3x a day, or 2
pills 2x a day, etc.
(define 1xd "Take 1 pill P.O. 1x/day")
(define 1xn "Take 1 pill P.O. 1x at night")
; INSTs - special instructions. INST is just a prefix INST+MED without
the dosage.
(define INSTOMZ "half an hour before breakfast, with a glass of water")
; Formatters - simple for now, but should be a function of the space available.
(define  line "-----------")

; The main part of a prescription DSL is pretty rigid in syntax, being
composed of blocks of theses parts:
; Please note that, in this DSL, the MED part includes the drug dosage
(e.g., HCTZ25, where
; the HCTZ designates the drug, and the 25 the dosage).
; An example would be:
; HCTZ25 30 PL 1XD
; meaning: Hydrochlorothiazide 25mg -------------- 30 pills
;                Take 1 pill P.O. 1X day
; INST are special instructions. They basically are more detailed
explanation to the patient about
; how to use the medication properly. Not always there's a INST in the
prescription DSL.
; INSTs are, in fact, a PREFIX for the MED without the dose. For
example, OMZ20 is Omeprazol 20mg.
; The instruction for OMZ would be INSTOMZ ("half an hour before
breakfast, with a glass of water").
; In this case, the DSL line would be:
; meaning: Omeprazol 20mg ------------------- 30 pills
;               Take 1 pill P.O. 1X day
;               half an hour before breakfast, with
;               a glass of water
; Questions regarding proper formatting of INST are not addressed at
this moment.
; Now follows a description of some problems I encountered and the
choices made in solving them:
; (define in (open-input-file "Recipe.txt"))
; If you just (string-split (read-line in)) you'll get:
; => '("hctz25" "30" "cp" "1xd")
; and that will not evaluate the symbols to their string descritptions.
; Because of that, you need to do a:
; > (map string->symbol (string-split (read-line in)))
; which will evaluate to
; => '(hctz25 |30| cp 1xd)
; This would be ideal to MAP EVAL to, but the problem is the |30|
; So, the idea is SET!ing that list to a name we can call easily, i.e.,
; med-line-holder, because then we can extract the pieces (since we
can't do list
; surgery easily, such a "replace the the element at position 1 with
so-and-so element").
; Since the prescription syntax is pretty rigid, we can get away with this
; simple approach.

(define med-line-holder '()) ; initial value of med-line-holder is an empty list
(define med-name-holder '())
(define med-quant-holder '())
(define med-form-holder '())
(define med-pos-holder '())
(define med-inst-holder '()) ; remember, not always INSTructions
happen in a DSL prescription .

(define in (open-input-file "Recipe.txt"))
(port-count-lines! in)
(define (clpr) (close-input-port in))

; a med-line-holder is a list that has MED QUANT FORM POS (and sometimes INST)
; This is obtained from a plain text file. When it is read, it becomes something
; like this: '(hctz25 |30| cp 1xd)
(define (set-med-line-holder)
  (set! med-line-holder (map string->symbol (string-split (read-line in)))))

(define (set-med-name-holder)
   ; (set! med-name-holder (eval (car med-line-holder))) ;; in the REPL
  (set! med-name-holder (eval (car med-line-holder)
(namespace-anchor->namespace a))))

(define (set-med-quant-holder) ; the CADR of the med-line-holder
  ; (set! med-quant-holder (eval (symbol->string (cadr med-line-holder))))
  (set! med-quant-holder (eval (symbol->string (cadr med-line-holder))
(namespace-anchor->namespace a))))

(define (set-med-form-holder) ; the CADDR of the med-line-holder -
gets the FORM, e.g., pills, etc.
  ; (set! med-form-holder (eval (symbol->string (caddr med-line-holder))))
  (set! med-form-holder (eval (caddr med-line-holder)
(namespace-anchor->namespace a))))

(define (set-med-pos-holder) ; the CADDDR of the med-line-holder -
gets the POS, e.g., 1xd
    ; (set! med-pos-holder (eval (symbol->string (cadddr med-line-holder))))
  (set! med-pos-holder (eval (cadddr med-line-holder)
(namespace-anchor->namespace a))))

(define (set-med-inst-holder) ; the LAST of the med-line-holder - gets the INST
    ; (set! med-pos-holder (eval (symbol->string (last med-line-holder))))
  (set! med-pos-holder (eval (last med-line-holder)
(namespace-anchor->namespace a))))

; One problem here regards the optional INST instructions.
; How to create a SETter function that will only SET! med-inst-holder
; if there's an INST instruction? Note that INST is a prefix. A real
instruction is, e.g.,
; INSTOMZ (for OMZ20).
(define (look-for-line)
  (if (regexp-match #px"\\d\\-" (read-line in))

(define (display-stuff)
  (display med-line-holder) (newline)
  (display med-name-holder) (newline)
  (display med-quant-holder) (newline)
  (display med-form-holder) (newline)
  (display med-pos-holder) (newline))
; The problem remains of what to do with the eventual INST.

; Successive calls to (look-for-line) would read the next lines.
; Output would alternate between a DSL line, or a NO-LINE (from look-for-line,
; if it hits a line with no text in Recipe.txt

(define (output-a-line)
 (string-join (list med-name-holder line med-quant-holder med-form-holder "\n"
                             med-pos-holder "\n")))

(define (format-a-line)
  (display (output-a-line)))

;(define (output-a-line)
; (display (string-join (list med-name-holder line med-quant-holder
med-form-holder "\n"
;                             med-pos-holder "\n"))))


; 1) How do we find out how many lines to (look-for-line)?
;    This is one of the resons I specified the "1-", "2-" in the Recipe.txt. Not
;    only it makes for easy visual understanding, but it may be used
to provide a hint
;    for this problem.
;    Possible approaches:
;    - Maybe this can be solved with REGEXPS? This information could
provide a sentinel
;      variable for an iterator function?
;    - Is there some sort if line counting function? (Note that I have set
;      (port-count-lines! in) somewhere above in the code.
; 2) How do we know we've reached the end of the file?
; 3) How to deal with the not-always-present INST?
;    - How do we check for INSTs? With a REGEXP?
;    - Choosing between INSTs with REGEXPS is not necessary, as they
will be loaded in a module,
;      so the system will "know" which one to choose.
; 4) Another idea would be "slurp" the whole of the prescription, and
then deal with evaluation. How?
; (define f1
;    (file->string
;   "C:\\Path\\to\\sources\\Recipe.txt"))
;> (string-normalize-spaces f1)
;"1- hctz25 30 pl 1xd 2- simva20 30 pl 1xn"
; That's all for now, folks!
; Many thanks for all the help so far, Racketeers!
; Cheers,
; Henry Lenzi

On Sat, Aug 2, 2014 at 12:44 AM, Henry Lenzi <henry.lenzi at gmail.com> wrote:
> Hello everyone -
> First of all, a big Thank You to all of you and for taking the time for
> responding.
> I'll have to set aside sometime during this weekend to see if I can
> understand the ideas you've been so kind to offer.
> However, I should confess that I've made some progress with way simpler
> stuff which I hope to post later on.  Like I've said, this is stupid
> software. Anyways, none of this is final.
> It really just used a plain text solution, since the format if a recipe is
> so rigid. The question of expanding the symbols from files to run-time was
> easier than I thought.
> The idea of using modules might have the nice collateral effect if some sort
> of primitive type (or syntax) checking for free. I like the idea someone
> offered of using modules for medication definitions. Actually, one module
> per definition makes it very easy for future users to add new medications.
> The ease of syntax is important because it allows for the customization by
> non-sophisticated users (physicians, nurses).
> Cheers,
> Henry Lenzi.

Posted on the users mailing list.