[racket-users] Re: Efficient & "nice" communication mechanism between Racket and other languages

2017-09-07 Thread Jack Firth
On Thursday, September 7, 2017 at 9:11:47 AM UTC-7, Brian Adkins wrote:
> I'm considering having a group of programmers create micro-services in 
> various programming languages to be glued together into a single application. 
> I would like a communication mechanism with the following characteristics:
> 
> * More efficient than HTTP

Unfortunately Racket doesn't have an HTTP/2 implementation, or I would 
recommend that. Could you elaborate on your efficiency requirements? It's 
possible a proxy that handled HTTP/2 connections could work for you and I 
suspect that would be much simpler than trying to make your own APIs atop a 
lower-level messaging protocol like ZeroMQ.

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] weak references and quoted values

2017-09-07 Thread Matthew Flatt
That's correct. Quoted values get GCed when code gets GCed, such as
when you run a module to completion in a namespace and don't retain the
namespace, or when a quoted value is sent to `eval` and not part of a
definition.

At Thu, 7 Sep 2017 17:31:26 -0700, Alexis King wrote:
> My understanding is that quoted values are effectively interned, so
> they’ll never be garbage collected as long as the code containing the
> quoted expression is loaded. Here’s a program that hints at this:
> 
>   (define (make-quoted-value)
> '(1 . 2))
> 
>   (eq? (make-quoted-value)
>(make-quoted-value))
>   ; => #t
> 
> So I think the answer is that quoted values are never garbage collected
> at all in normal, non-reflective uses. This is all just guessing,
> though; I’m sure Matthew could give a more authoritative explanation.
> 
> > On Sep 7, 2017, at 14:53, Stephen Chang  wrote:
> > 
> > Ran into some unexpected behavior today. Quoted values do not seem to
> > be garbage collected when there are only weak references. Is this
> > correct behavior?
> > 
> > 
> > Here's an example program:
> > 
> > #lang racket/base
> > 
> > (define key1 (list 1))
> > (define key2 '(1))
> > 
> > (define hash1 (make-weak-hash (list (cons key1 1
> > (define hash2 (make-weak-hash (list (cons key2 1
> > 
> > hash1
> > hash2
> > 
> > (set! key1 null)
> > (set! key2 null)
> > 
> > (collect-garbage) ; I tried a range of gc pressure here with no difference
> > 
> > hash1
> > hash2
> > 
> > 
> > I expected it to produce:
> > 
> > '#hash(((1) . 1))
> > '#hash(((1) . 1))
> > '#hash()
> > '#hash()
> > 
> > But instead got:
> > 
> > '#hash(((1) . 1))
> > '#hash(((1) . 1))
> > '#hash()
> > '#hash(((1) . 1))
> > 
> > 
> > Searching through the docs I found the following line [1] that maybe
> > explains the behavior (but I'm not sure)?
> > 
> > "Values produced by quote remain reachable when the quote expression
> > itself is reachable."
> > 
> > Is this saying that since the quoted value is reachable via the weak
> > reference, it is considered reachable?
> > 
> > [1] 
> http://docs.racket-lang.org/reference/eval-model.html?#%28part._gc-model%29
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to racket-users+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] weak references and quoted values

2017-09-07 Thread Alexis King
My understanding is that quoted values are effectively interned, so
they’ll never be garbage collected as long as the code containing the
quoted expression is loaded. Here’s a program that hints at this:

  (define (make-quoted-value)
'(1 . 2))

  (eq? (make-quoted-value)
   (make-quoted-value))
  ; => #t

So I think the answer is that quoted values are never garbage collected
at all in normal, non-reflective uses. This is all just guessing,
though; I’m sure Matthew could give a more authoritative explanation.

> On Sep 7, 2017, at 14:53, Stephen Chang  wrote:
> 
> Ran into some unexpected behavior today. Quoted values do not seem to
> be garbage collected when there are only weak references. Is this
> correct behavior?
> 
> 
> Here's an example program:
> 
> #lang racket/base
> 
> (define key1 (list 1))
> (define key2 '(1))
> 
> (define hash1 (make-weak-hash (list (cons key1 1
> (define hash2 (make-weak-hash (list (cons key2 1
> 
> hash1
> hash2
> 
> (set! key1 null)
> (set! key2 null)
> 
> (collect-garbage) ; I tried a range of gc pressure here with no difference
> 
> hash1
> hash2
> 
> 
> I expected it to produce:
> 
> '#hash(((1) . 1))
> '#hash(((1) . 1))
> '#hash()
> '#hash()
> 
> But instead got:
> 
> '#hash(((1) . 1))
> '#hash(((1) . 1))
> '#hash()
> '#hash(((1) . 1))
> 
> 
> Searching through the docs I found the following line [1] that maybe
> explains the behavior (but I'm not sure)?
> 
> "Values produced by quote remain reachable when the quote expression
> itself is reachable."
> 
> Is this saying that since the quoted value is reachable via the weak
> reference, it is considered reachable?
> 
> [1] 
> http://docs.racket-lang.org/reference/eval-model.html?#%28part._gc-model%29

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


[racket-users] weak references and quoted values

2017-09-07 Thread Stephen Chang
Ran into some unexpected behavior today. Quoted values do not seem to
be garbage collected when there are only weak references. Is this
correct behavior?


Here's an example program:

#lang racket/base

(define key1 (list 1))
(define key2 '(1))

(define hash1 (make-weak-hash (list (cons key1 1
(define hash2 (make-weak-hash (list (cons key2 1

hash1
hash2

(set! key1 null)
(set! key2 null)

(collect-garbage) ; I tried a range of gc pressure here with no difference

hash1
hash2


I expected it to produce:

'#hash(((1) . 1))
'#hash(((1) . 1))
'#hash()
'#hash()

But instead got:

'#hash(((1) . 1))
'#hash(((1) . 1))
'#hash()
'#hash(((1) . 1))


Searching through the docs I found the following line [1] that maybe
explains the behavior (but I'm not sure)?

"Values produced by quote remain reachable when the quote expression
itself is reachable."

Is this saying that since the quoted value is reachable via the weak
reference, it is considered reachable?

[1] http://docs.racket-lang.org/reference/eval-model.html?#%28part._gc-model%29

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Long division algorithm in HtDP way

2017-09-07 Thread Matthias Felleisen

I couldn’t help myself. So here is 90% of what John proposed in HtDP’s ISL+ 
language. Exercise for the reader: fix the failing tests. 


#lang htdp/isl+

;; long division

;; Trace = [cons N [cons N [Listof [List N N]]]

;; N N -> Trace 
;; compute the trace for the division of x and y 

;; example, worked 
;; 5432 : 12 = 452
;;-48
;;--
;;  63
;; -60
;;  --
;;   32
;;  -24
;;   --
;;8
;;=

(check-expect (long 5432 12) (list 452 8 (list 54 48) (list 32 24)))
(define (long x y)
  (local ((define x-as-digits (number->digits x))
  (define result  (long-helper (list (first x-as-digits)) (rest 
x-as-digits) y))
  (define quotient(digits->number (first result
(list quotient (second result


;; [Listof Digit] [Listof Digit] N -> Trace 
;; compute the trace of a long division of (digits->number (append fst-digits 
rest-digits)) by y
(check-expect (long-helper '(5) '(4 3 2) 12) (list 452 8 (list 54 48) (list 32 
24)))
(define (long-helper fst-digits rest-digits y)
  (local ((define fst-number (digits->number fst-digits)))
(cond
[(< fst-number y)
 (if (empty? rest-digits)
 (list '() fst-number)
 (long-helper (snoc fst-digits (first rest-digits)) (rest rest-digits) 
y))]
[(= fst-number y) (list (list 1) 0)]
[else (local ((define next-digit (subtract-often-enough fst-number y))
  (define remainder  (- fst-number (* next-digit y)))
  (define result (long-helper (list remainder) rest-digits 
y))
  (define quotient   (first result))
  (define remainder2 (second result)))
(list (cons next-digit quotient) remainder2))])))

;; N N -> [List N N]
;; subtract y from x until x < y
(check-expect (subtract-often-enough 54 12) 4)
(define (subtract-often-enough x y)
  (cond
[(< x y) 0]
[else (+ (subtract-often-enough (- x y) y) 1)]))

;; [Listof X] X -> [Listof X]
;; add x to end of l
(check-expect (snoc '() 1) '(1))
(check-expect (snoc '(1) 2) '(1 2))
(define (snoc l x)
  (append l (list x)))

;; [listof Digit] -> N
;; render the given sequence of digits as a number
(check-expect (digits->number '(1 2 3)) 123)
(define (digits->number digits)
  (string->number (implode (map number->string digits

;; N -> [Listof Digit]
;; split number into sequence of digits
(check-expect (number->digits 123) '(1 2 3))
(define (number->digits x)
  (map string->number (explode (number->string x

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Re: Racket Web servlet performance benchmarked and compared

2017-09-07 Thread Jon Zeppieri
On Thu, Sep 7, 2017 at 4:52 PM, dbohdan  wrote:
>
> In both cases the ordering is still "single" > "many" > "places" > 
> "many-places". Though "many" and "places" are pretty close in the first case, 
> "many" consistently comes out ahead if you retest.

This is really interesting. I wonder how costly the inter-place
communication is, relative to the cost of actually generating and
sending the response.

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Long division algorithm in HtDP way

2017-09-07 Thread Matthias Felleisen

Let me support John’s email with a hint: 

#lang htdp/isl+

;; long division

;; Number Number -> (cons Number (cons Number [Listof Number]))
;; compute the list of steps needed to divide x by y

;; work thru an example
;; given  5432, 12
;; wanted [list 452 8) ... optional: (list 54 48) (list 63 60) (list 32 24)]
;; because 
;; (quotient 5432 12) = 12
;; (remainder 5432 12) = 8
;; IGNORE the trace part 

(define (long x y)
  (cond
[(< x y) (list 0 x)]
[(= y x) (list 1 0)]
[else (local ((define r (long (- x y) y))
  (define quotient  (first r))
  (define remainder (second r)))
(list (+ quotient 1) remainder))]))

(long 5432 12)




> On Sep 7, 2017, at 2:09 PM, 'John Clements' via Racket Users 
>  wrote:
> 
> 
>> On Sep 7, 2017, at 9:28 AM, jaroslaw.mo...@gmail.com wrote:
>> 
>> 
>> Dear Racketeers!
>> 
>> I would like to write the function divide in racket, for performing the long 
>> division, which would print the whole computational process, as the 
>> elementary school pupils do. For example, the call (divide 5432, 12) should 
>> print this:
>> 
>> 5432 : 12 = 452
>> -48
>> --
>> 63
>> -60
>> --
>>  32
>> -24
>>  --
>>   8
>>   =
> 
> Sounds like fun! 
> 
> If I were you, I would separate this into two problems.
> 
> 1) given the divisor and the divisee, produce a “trace” of the division.
> 2) given a “trace”, produce a (single multi-line) string representing it.
> 
> Most of the thinking will be in developing the data definition for the 
> “trace. ” Once this is done, the rest should be pretty straightforward.
> 
> John Clements
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to racket-users+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Re: Racket Web servlet performance benchmarked and compared

2017-09-07 Thread dbohdan
On Tuesday, September 5, 2017 at 12:41:04 PM UTC+3, Jay McCarthy wrote:
> Is the benchmarking client core the same core as the server core?
> Could that help explain why single threaded performance is best?

The not-quite-yes-or-no answer is that they were limited to separate virtual 
cores inside a VirtualBox VM. When a VirtualBox VM has N virtual cores on a 
physical CPU with M cores, it can use roughly up to N/M of the CPU's resources. 
In that benchmark the applications, which had access to two virtual cores, had 
to themselves 50% of the four-core physical CPU, and the load generator had 25% 
with one virtual core.

I've run three variants of the benchmark to see if running in a VM had a 
noticeable effect on single-threaded vs. multi-threaded performance and to 
address your question about whether "many" underperformed because of a virtual 
network.

First, I got rid of the VM. Both the applications and the load generator ran in 
containers on the same machine, but not in a VM. This meant they were limited 
to different physical cores. The results were similar to those in a VM. The 
numbers were lower overall due to the slightly weaker hardware.

==
results/caddy.txt:Requests per second:2758.06 [#/sec] (mean)
results/compojure.txt:Requests per second:2670.11 [#/sec] (mean)
results/custom-many-places.txt:Requests per second:4326.27 [#/sec] (mean)
results/custom-many.txt:Requests per second:4655.04 [#/sec] (mean)
results/custom-places.txt:Requests per second:4584.75 [#/sec] (mean)
results/custom-single.txt:Requests per second:5191.93 [#/sec] (mean)
results/flask.txt:Requests per second:.25 [#/sec] (mean)
results/guile.txt:Requests per second:1933.10 [#/sec] (mean)
results/plug.txt:Requests per second:3346.99 [#/sec] (mean)
results/scgi.txt:Requests per second:2092.03 [#/sec] (mean)
results/sinatra.txt:Requests per second:293.60 [#/sec] (mean)
results/stateful.txt:Requests per second:532.61 [#/sec] (mean)
results/stateless.txt:Requests per second:625.02 [#/sec] (mean)
==

Second, I ran the benchmark over a gigabit local network. Yesterday I pushed a 
script for this (`remote-benchmark.exp`) to the repository. The applications 
ran on one machine (in a Docker container with access to two virtual cores). 
The load generator ran on another (my laptop).

==
remote-results/caddy.txt-Requests per second:3119.23 [#/sec] (mean)
remote-results/compojure.txt-Requests per second:4009.71 [#/sec] (mean)
remote-results/custom-many-places.txt-Requests per second:4409.48 [#/sec] 
(mean)
remote-results/custom-many.txt-Requests per second:5499.20 [#/sec] (mean)
remote-results/custom-places.txt-Requests per second:5072.63 [#/sec] (mean)
remote-results/custom-single.txt-Requests per second:6246.09 [#/sec] (mean)
remote-results/flask.txt-Requests per second:1106.43 [#/sec] (mean)
remote-results/guile.txt-Requests per second:2062.53 [#/sec] (mean)
remote-results/plug.txt-Requests per second:4034.74 [#/sec] (mean)
remote-results/scgi.txt-Requests per second:2046.91 [#/sec] (mean)
remote-results/sinatra.txt-Requests per second:288.52 [#/sec] (mean)
remote-results/stateful.txt-Requests per second:542.27 [#/sec] (mean)
remote-results/stateless.txt-Requests per second:614.18 [#/sec] (mean)
==

In both cases the ordering is still "single" > "many" > "places" > 
"many-places". Though "many" and "places" are pretty close in the first case, 
"many" consistently comes out ahead if you retest.

Last, I ran the benchmark over the Internet with two machines about 1.89×10^-10 
light years apart. The applications ran on a very humble VPS. Due to its 
humbleness I had to reduce the number of concurrent connections to 25. 
"places", "many-places", and racket-scgi ran out of memory with as few as 10 
concurrent connections (racket-scgi seemingly due to nginx), so I decided to 
exclude them rather than reduce the number of connections further.

==
> env CONNECTIONS=25 ./remote-benchmark.exp vps
remote-results/caddy.txt:Requests per second:231.37 [#/sec] (mean)
remote-results/compojure.txt:Requests per second:242.41 [#/sec] (mean)
remote-results/custom-many.txt:Requests per second:250.35 [#/sec] (mean)
remote-results/custom-single.txt:Requests per second:255.21 [#/sec] (mean)
remote-results/flask.txt:Requests per second:235.26 [#/sec] (mean)
remote-results/guile.txt:Requests per second:242.38 [#/sec] (mean)
remote-results/plug.txt:Requests per second:244.98 [#/sec] (mean)
remote-results/sinatra.txt:Requests per second:239.78 [#/sec] (mean)
remote-results/stateful.txt:Requests per second:239.60 [#/sec] (mean)
remote-results/stateless.txt:Requests per second:238.71 [#/sec] (mean)
==

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to 

Re: [racket-users] Rackterm error

2017-09-07 Thread James

On Sep 7, 2017, at 1:53 AM, William G Hatch wrote:

> On Tue, Sep 05, 2017 at 05:07:41PM -0400, James wrote:
>> I'm trying out Rackterm for the purpose of running commands put together 
>> from a GUI but it looks like the thread is crashing as soon as I try to 
>> create a terminal canvas.  Is it something I am not doing right or is there 
>> a bug?  I am able to run rackterm/xterm without error and that contains very 
>> similar code.
> 
> 
>> 
>> Here's my code:
>> 
>> #lang racket
>> 
>> (require racket/gui
>>rackterm/private/terminal-canvas
>>)
>> 
>> (define frame (new frame% [label "Example"]
>>  (width 800)
>>  (height 800)
>>  ))
>> 
>> (send frame show #t)
>> 
>> (define my-term (new terminal-canvas%
>>   [parent frame]
>>   [font-size 14]
>>   [font-name "Courier"]
>>   [term-var "rackterm"]
>>   [command-and-args '("ls -lh")]
>>   [set-title-callback (lambda (title) (send frame set-label 
>> title))]
>>   [horiz-margin 2]
>>   [vert-margin 2]
>>))
>> 
>> 
>> The terminal window appears and then immediately closes with the following 
>> error message:
>> 
>> "The evaluation thread is no longer running, so no evaluation can take place 
>> until the next execution.
>> 
>> Exited successfully."
>> 
> 
> Well, this is caused by my not having actually cleaned up the code for it to 
> be used in any sort of general way, rather than specifically only by my one 
> xterm (hence terminal canvas is still in the private directory).  But there 
> is a `handle-subproc-ended` method that I put a TODO in to make it 
> configurable, but currently just kills the canvas, because usually you want 
> an xterm to close when the shell inside it exits.  But you could make a 
> subclass that overrides that method, or even better, improve my bad code and 
> send a pull request.  Or eventually I'll fix things up and release them 
> properly.
> 
> Also '("ls -lh") should be '("ls" "-lh").


Thanks.  I got it to work by creating a subclass.  Unfortunately, it will take 
more work to make it more useful than MrLib Terminal since what I really want 
is something which will support running commands interactively created by GUI 
elements.  At this point, I can only run one command and then create a new 
terminal object in order to run an additional command.  I will probably get 
back to this later. 

James


-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Efficient & "nice" communication mechanism between Racket and other languages

2017-09-07 Thread David Storrs
I haven't used ZeroMQ but I've had excellent success with protocol
buffers.  It's a simple way to encode whatever data you want into a binary
blob that you can easily send over a TCP channel.  Depending on what you're
transferring you might even be able to fit it inside the MTU so that it
only takes one packet.  Better yet, the Racket package for it
(murphy/protobuf) includes a reflection submodule
(murphy/protobuf/reflection) that allows you to introspect all the details
of a message so you can dynamically discover what you've just gotten.



https://developers.google.com/protocol-buffers/

Racket package docs:
http://planet.racket-lang.org/package-source/murphy/protobuf.plt/1/1/planet-docs/main/index.html

Racket package:
http://planet.racket-lang.org/display.ss?package=protobuf.plt=murphy=2


On Thu, Sep 7, 2017 at 12:11 PM, Brian Adkins  wrote:

> I'm considering having a group of programmers create micro-services in
> various programming languages to be glued together into a single
> application. I would like a communication mechanism with the following
> characteristics:
>
> * Already supported by Racket, or relatively trivial to add
> * More efficient than HTTP
> * Nicer to program than raw sockets
>
> Someone suggested ZeroMQ, so I searched for support in Racket and found 4
> packages, but 3 of them have failing tests, and I *think* two of them
> comprise a single logical package, so one of the two has failing tests.
>
> Has anyone had success with any of the ZeroMQ packages?
>
> Is there another communication mechanism I should consider instead?
>
> Thanks,
> Brian
>
> --
> You received this message because you are subscribed to the Google Groups
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to racket-users+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


[racket-users] Long division algorithm in HtDP way

2017-09-07 Thread jaroslaw . modry

Dear Racketeers!

I would like to write the function divide in racket, for performing the long 
division, which would print the whole computational process, as the elementary 
school pupils do. For example, the call (divide 5432, 12) should print this:

 5432 : 12 = 452
-48
 --
  63
 -60
  --
   32
  -24
   --
8
=


Finally, I wrote this function in python, but I'm not happy about it because 
I've been "tinkering around" and I feel that it can be much better written by 
following the principles described in HtDP. The problem is I don't know how to 
do that, though I read the book and solved a lot of problems from it. I simply 
don't know how to "derive" clean and nice program for this function.

My (ugly) python code looks like this:

def divide(x, y):
if x < y:
return 0, x
x = str(x)
q = ""
r = ""
workings = ""
firsttime = True
for i in range(1, len(x) + 1):
r = r + "0"
num = int(r) + int(x[i-1])
sum = 0
m = 0
while sum + y <= num:
m += 1
sum += y
r = str(num - sum)
q = q + str(m)
ls = len(str(sum))
if sum == 0 and firsttime:
continue
firsttime = False
workings += (i-ls)* " " + "-" + str(sum) + "\n"
workings += (i + 1 -ls) * " " + ls * "-" + "\n"
workings += (i + 1 - len(r)) * " " + r + ("" if i >= len(x) else x[i]) 
+ "\n"

workings += (len(x) - len(r) + 1) * " " + len(r) * "=" + "\n"
print(" " + x + " : " + str(y) + " = " + str(int(q)) + "\n" + workings)


I'm wondering how to write nice and clean version of the above program in the 
proper HtDP way. Can someone here show how to solve this problem strictly 
following the HtDP methodology?

Nice regards,
Jaroslaw Modry

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.