How to print Racket structs - racket

Is there any way to control how a struct is printed?
For example if I have a transparent struct containing an image:
(struct photo (label image-data) #:transparent)
But I don't want to print the image-data field.

I want to extend Ben's answer a bit. You can also combine gen:custom-write with make-constructor-style-printer to make the struct printing significantly easier. This function handles the differences between printing, writing, quote depth, and output port for you.
Extending his example gives:
#lang racket
(require pict
racket/struct)
(struct photo (label image-data)
#:transparent
#:methods gen:custom-write
[(define write-proc
(make-constructor-style-printer
(lambda (obj) 'photo)
(lambda (obj) (list (photo-label obj)))))])
(displayln (photo "fish" (standard-fish 100 100)))
;; Prints #<photo: fish>
(println (photo "fish" (standard-fish 100 100)))
;; Prints (photo "fish")
Now write, display, and print all work as you would expect

Yes! Use the gen:custom-write generic interface.
#lang racket
(require pict)
(struct photo (label image-data)
#:transparent
#:methods gen:custom-write
[(define (write-proc photo-val output-port output-mode)
(fprintf output-port "#<photo:~a>" (photo-label photo-val)))])
(photo "fish" (standard-fish 100 100))
;; Prints "#<photo:fish>"
The first argument to write-proc is the struct to be printed.
The second argument is the port to print to.
The third argument suggests how the context wants the value printed, see the docs:
http://docs.racket-lang.org/reference/Printer_Extension.html#%28def._%28%28lib._racket%2Fprivate%2Fbase..rkt%29._gen~3acustom-write%29%29

Related

Error when trying to set variable on LISP in a BMI problem

It's a simple college problem. I have to get the result using the BMI calc
My code below:
(write-line "BMI CALC")
(defun calc nil
(prog (w h) ; define p e h as local variables init with nil
(print "Weight: ")
(setq w (read))
(print "Height: ")
(setq h (read))
(return (/ w (* h h)))
)
)
(format t "BMI: ~D~%" (calc))
(setq bmi calc)
(cond
((< bmi 18.5) (print "Under weight"))
((< bmi 24.9) (print "Normal weight"))
((< bmi 29.9) (print "Overweight"))
((< bmi 34.9) (print "Obesity 1"))
((< bmi 39.9) (print "Obesity 2"))
(t (print "Obesity 3"))
)
And I got this result below:
BMI CALC
"Weight: " 78
"Height: " 1.7
BMI: 26.989618
*** - SETQ:variable CALC has no value
I really don't understand why this error.
I expected to print the BMI result, like "Under weight" or "Obesity 1".
What value do you think variable calc has on this line: (setq bmi calc)?
Because, as that error says, it doesn't have any.
Also, ending parentheses belong to the same line and you should read about let (a special operator for creating local variables).
Here is an improved version, which you can test by calling (my-bmi):
(defun calc ()
(prog (w h)
(print "Weight: ")
(setq w (read *query-io*))
(print "Height: ")
(setq h (read *query-io*))
(return (/ w (* h h)))))
(defun my-bmi ()
(print "BMI CALC")
(let ((bmi (calc)))
(format t "BMI: ~D~%" bmi)
(print
(cond
((< bmi 18.5) "Under weight")
((< bmi 24.9) "Normal weight")
((< bmi 29.9) "Overweight")
((< bmi 34.9) "Obesity 1")
((< bmi 39.9) "Obesity 2")
(t "Obesity 3")))))
Remarks about your code
You are using a global variable and writing the whole assignment as a script: namely you call (setq bmi ...) where bmi is not introduced by a let. And there is very few reusable parts. It's a bit of a bad practice to have side-effects like that. Here you are only writing a small program for an assignment so it is not very bad but, as you are also learning programming, you should also try to structure your code as small functions that don't have side-effects. This would be cleaner and would help the program grow in a real situation.
This is related to the previous point, but you are mixing different things in functions: parsing input, computing the BMI, outputting the result. In practice we often need to architecture code into layers: here you can have a pure mathematics data layer that computes the BMI, a input/ouptut layer that prompt for values, and a main program that glues all of those parts together.
Regarding I/O, you are likely to experience difficulties later if you don't flush the output or clear the input streams during your interactions with the user: as streams are typically buffered, sometimes format won't display immediately a string and this will be confusing. Using force-output is a way to flush all the buffers and ensure the user sees the text you wrote.
Alternative implementation
This is an example of how I would do it, using intermediate functions to encapsulate different tasks.
For example:
(defun bmi-formula (&key height weight)
(/ weight (* height height)))
This is a function that you can invoke interactively as follows in the REPL:
CL-USER> (bmi-formula :height 1.80 :weight 60)
18.51852
Notice that it only computes a value from numerical values, there is no parsing involved.
Likewise, you can convert a BMI to an obesity judgment as follows (as an aside, using BMI for that is an outdated practice, I am a bit suprised/annoyed that you are being asked to write this function):
(defun bmi-obesity-judgment (bmi)
(cond
((< bmi 18.5) 'underweight)
;; etc.
))
Here the function takes a bmi and returns a symbol. If you want later to localize your application in another language, you can map each symbol to a word in another language (e.g. (ecase judgment (underweight "magro") ...) please forgive my attempt at Portuguese).
Now you can write a function that grabs input from the user. Typically I would write an auxiliary function as follows:
(defun prompt (message type)
(loop
(clear-input *query-io*)
(fresh-line *query-io*)
(write-string message *query-io*)
(let ((value (read *query-io*)))
(when (typep value type)
(return value))
(warn "~a is not of type ~a" value type))))
What it does is ensure the input is cleared (there is no pending/buffered values to be read), it prints a new-line if necessary, a custom message, flush the output stream, then read a value and check if that value matches a given type. If that's not the case the code is executed in a loop.
For example, you can produce a property list as follows:
(defun input-bmi-parameters ()
(list
:weight (prompt "Weight (kgs): " '(integer 0 1000))
:height (prompt "Height (meters): " '(real 0 5))))
Finally you can piece all the functions together, write a title, write the output, etc:
(defun bmi-program ()
(format *query-io* "~&BMI Calculator. Enter weight and height and be judged.~%")
(let ((parameters (input-bmi-parameters)))
...))
Notice how I am using let to introduce a local variable parameters bound to a list (see also in prompt where I use local variable value). Apart from interacting with the *query-io*, the code does not have side-effects, all the state is contained in the function. That means that you could run more than one bmi-program in parallel, without them messing around with the same set of global variables: you could have a server where each connection executes bmi-program in a different thread, in which *query-io* is bound to the current TCP stream.

How to tell `make-module-evaluator` to use a custom reader like #lang does?

I'm trying to execute a custom made #lang on a given string (not in a file). Let's call it broccoli.
Setup
My lang is defined as so:
broccoli/main.rkt
(module reader racket/base
(require broccoli/private/reader)
(provide read read-syntax)) ; basically a reprovide
broccoli/private/reader.rkt
(provide
(rename-out
[my-read read]
[my-read-syntax read-syntax]))
(define (my-read in)
(syntax->datum
(my-read-syntax #f in)))
(define (my-read-syntax src in)
(with-syntax ([parse-tree (parse src (make-tokenizer in src))]) ; brag stuff
(strip-context
#'(module program broccoli/private/expander
parse-tree))))
broccoli/private/expander.rkt
(provide
(rename-out [module-begin #%module-begin]))
(define-syntax-rule (module-begin expr)
(#%module-begin
(provide meal)
(define meal (transform 'expr)))) ; some kind of computation
It works fairly well used the classic way :
#lang broccoli
Hello world!
will produce:
(module program broccoli/private/expander
(program
(sentence (word "Hello") (word "world"))))
which will then expand into:
(provide meal)
(define meal (list 42 38)) ; the result is for the sake of the example, don't mind it
But I'm trying to apply it to an arbitrary string I get from a network request, and send back the result.
And this time, it gets more complicated.
Here's what I tried:
Try #1
(define text "Hello world!")
(define evaluator (make-evaluator 'broccoli text)) ; Error: no #%module-begin found
(evaluator 'meal)
Try #2
(define text "Hello world!")
(define module (broccoli-read-syntax #f (open-input-string text)))
(define evaluator (make-module-evaluator #:language 'broccoli/private/expander module))
(evaluator 'meal) ; Error: meal undefined
Try #3
(define text "Hello world!")
(define evaluator (make-module-evaluator (string-append "#lang broccoli " text)))
(evaluator 'meal) ; Error: meal undefined
Try #4 (it works but it's not what I want)
(define text "Hello world!")
(define module (broccoli-read-syntax #f (open-input-string text)))
(define ns (make-base-namespace))
(eval module ns)
(namespace-require ''program ns)
(define result (eval 'meal ns))
This last result works correctly, but it doesn't use a sandbox, and uses eval directly.
I'm sure there's a better way, but I don't get what's going wrong.
I was sooo close!
I had to require the generated module inside the evaluator (which implies providing #%app, #%top, #%top-interaction, require and quote from the expander).
(define evaluator (make-module-evaluator (string-append "#lang broccoli\n" text)))
(evaluator '(require 'program)) ; missing step
(evaluator 'meal)
What I find weird in this solution is that it doesn't behave the same way the documentation says:
> (define base-module-eval
(make-module-evaluator '(module m racket/base
(define (f) later)
(define later 5))))
> (base-module-eval 'later)
5
I suggest using read-lang-module to turn the string into a syntax object representing a module. And then use make-module-evaluator.

Is it possible to use extract-struct-info outside a macro?

Consider the following racket code to get a list of accessors of a given struct:
#lang racket
(require (for-syntax syntax/parse racket/struct-info racket/list))
(struct point [x y])
;; get the list of accessors from a struct
;; ex. (get point) = '(point-x point-y)
(define-syntax (get stx)
(syntax-parse stx
[(_ struct)
(define struct-info (extract-struct-info (syntax-local-value #'struct)))
(define accessors-list (map syntax-e (fourth struct-info)))
#``(#,#accessors-list)]))
(get point)
Using syntax-local-value, we can extract the value of the identifier bound by the pattern variable struct.
Using extract-struct-info, we can extract the structure type information in a list form (it has 6 elements). From here the accessors list can be extracted (it's the fourth element in the list).
Question
How can I access the information about a struct (as shown in Structure Type Transformer Binding) at the non-macro level? The two functions above cannot be used directly on structs outside a transformer because the struct is a procedure at that point (and extract-struct-info takes in a struct-info).
You cannot use syntax-local-value and extract-struct-info at run time. You must use run-time struct introspection instead.
If you make your struct transparent, like this:
(struct point [x y] #:transparent)
then you can get analogous values from a point instance using struct-info and struct-type-info:
(define a-point (point 3 4))
(define-values (type skipped?) (struct-info a-point))
;; type = #<struct-type:point>, skipped = #f
(define-values (name inits autos acc mut imms super super-skipped?)
(struct-type-info type))
(acc a-point 0) ;; => 3
The value of type above is the same as struct:point (implicitly defined by the struct definition), so if you know you're dealing with a point struct specifically, you coud use that instead. You still need #:transparent (or you need a sufficiently powerful inspector) to use struct-type-info, though.

subprocess in racket

I am writing a program which needs communication between processes.
my code:
#lang racket
(define-values (sp o i e) (subprocess #f #f #f "c://player1.exe" ))
(define count 10)
(for ([c (in-naturals)])
(cond
[(equal? count 0) (error "Province is empty!") ]
[else
(write "server" i)
(set! count (sub1 count))
(flush-output i)
(display (read o))]))
and the player1.exe code:
#lang racket
(define (interact notification)
(cond
[(eq? notification "server") (write "true" (current-output-port))]
[else (write "false" (current-output-port))]))
(for ([c (in-naturals)])
(interact (read (current-input-port)))
(write "player" (current-output-port))
(sleep 0.1)
flush-output (current-output-port))
I am getting output if I run without loops. I am also getting output when only player is sending messages. But with both server and player sending messages the program gets hanged.
What do you think the problem is?
The last line in your player1.exe file looks suspicious. flush-output is not actually being applied as a function. Rather than
flush-output (current-output-port)
you probably mean:
(flush-output (current-output-port))
From a style point of view: the functions read, write, and flush-output all work on the current input and output ports by default, so you don't need to provide them. Take a look at the documentation for those functions, such as flush-output, and you'll see that it mentions that the current-output-port is its default.
So the line that we just looked at can be written as:
(flush-output)
More issues: don't use eq? to compare strings. Use string=? instead. The reason is that there can be two strings that have the same textual content, but for which eq? will still be able to distinguish the two. e.g.:
kui $ racket
Welcome to Racket v5.2.1.
> (eq? "a" (string-copy "a"))
#f
> (string=? "a" (string-copy "a"))
#t

Treating the values from a list of slots and strings

I want to do a macro in common lisp which is supposed to take in one of its arguments a list made of slots and strings. Here is the prototype :
(defclass time-info ()
((name :initarg name)
(calls :initarg calls)
(second :initarg second)
(consing :initarg consing)
(gc-run-time :initarg gc-run-time)))
(defun print-table (output arg-list time-info-list) ())
The idea is to print a table based on the arg-list which defines its structure. Here is an example of a call to the function:
(print-table *trace-output*
'("|" name "||" calls "|" second "\")
my-time-info-list)
This print a table in ascII on the trace output. The problem, is that I don't know how to explicitely get the elements of the list to use them in the different parts of my macro.
I have no idea how to do this yet, but I'm sure it can be done. Maybe you can help me :)
I would base this on format. The idea is to build a format string
from your arg-list.
I define a helper function for that:
(defun make-format-string-and-args (arg-list)
(let ((symbols ()))
(values (apply #'concatenate 'string
(mapcar (lambda (arg)
(ctypecase arg
(string
(cl-ppcre:regex-replace-all "~" arg "~~"))
(symbol
(push arg symbols)
"~a")))
arg-list))
(nreverse symbols))))
Note that ~ must be doubled in format strings in order to escape them.
The printing macro itself then just produces a mapcar of format:
(defmacro print-table (stream arg-list time-info-list)
(let ((time-info (gensym)))
(multiple-value-bind (format-string arguments)
(make-format-string-and-args arg-list)
`(mapcar (lambda (,time-info)
(format ,stream ,format-string
,#(mapcar (lambda (arg)
(list arg time-info))
arguments)))
,time-info-list)))
You can then call it like this:
(print-table *trace-output*
("|" name "||" calls "|" second "\\")
my-time-info-list)
Please note the following errors in your code:
You need to escape \ in strings.
Second is already a function name exported from the common-lisp
package. You should not clobber that with a generic function.
You need to be more precise with your requirements. Macros and Functions are different things. Arrays and Lists are also different.
We need to iterate over the TIME-INFO-LIST. So that's the first DOLIST.
The table has a description for a line. Each item in the description is either a slot-name or a string. So we iterate over the description. That's the second DOLIST. A string is just printed. A symbol is a slot-name, where we retrieve the slot-value from the current time-info instance.
(defun print-table (stream line-format-description time-info-list)
(dolist (time-info time-info-list)
(terpri stream)
(dolist (slot-or-string line-format-description)
(princ (etypecase slot-or-string
(string slot-or-string)
(symbol (slot-value time-info slot-or-string)))
stream))))
Test:
> (print-table *standard-output*
'("|" name "||" calls "|" second "\\")
(list (make-instance 'time-info
:name "foo"
:calls 100
:second 10)
(make-instance 'time-info
:name "bar"
:calls 20
:second 20)))
|foo||100|10\
|bar||20|20\
First, you probably don't want the quote there, if you're using a macro (you do want it there if you're using a function, however). Second, do you want any padding between your separators and your values? Third, you're probably better off with a function, rather than a macro.
You also seem to be using "array" and "list" interchangeably. They're quite different things in Common Lisp. There are operations that work on generic sequences, but typically you would use one way of iterating over a list and another to iterate over an array.