Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interop seamlessly with racket #161

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

shawwn
Copy link
Member

@shawwn shawwn commented Feb 8, 2019

This PR illustrates a way to interop seamlessly between racket and arc. More details. Most of the tests pass.

(Not really intended to be merged. I'm doing something similar in https://github.com/laarc/laarc so I wanted to show how it could be done in anarki.)

arc> (sequence->list "foo")
'(#\f #\o #\o)

arc> (sequence->list (in-range 10 0 -0.5))
'(10
  9.5
  9.0
  8.5
  8.0
  7.5
  7.0
  6.5
  6.0
  5.5
  5.0
  4.5
  4.0
  3.5
  3.0
  2.5
  2.0
  1.5
  1.0
  0.5)

arc> (each n (in-range 10 0 -0.5) (out n (expt n 2)))
'((10 100)
  (9.5 90.25)
  (9.0 81.0)
  (8.5 72.25)
  (8.0 64.0)
  (7.5 56.25)
  (7.0 49.0)
  (6.5 42.25)
  (6.0 36.0)
  (5.5 30.25)
  (5.0 25.0)
  (4.5 20.25)
  (4.0 16.0)
  (3.5 12.25)
  (3.0 9.0)
  (2.5 6.25)
  (2.0 4.0)
  (1.5 2.25)
  (1.0 1.0)
  (0.5 0.25))

; use racket's sort
arc> (|sort| '(a b "foo" 21) (compare > string))
'("foo" b a 21)

arc> (|sort| '(a b "foo" 21) (compare < string))
'(21 a b "foo")

arc> (sequence->list (in-producer (thunk (bytes-ref (crypto-random-bytes 1) 0)) (%do [< _ 42])))
'(56 119)

arc> (sequence->list (in-producer (thunk (bytes-ref (crypto-random-bytes 1) 0)) (%do [< _ 42])))
'(192 163 45 163 79 184 218 105 67 67 240 228)

arc> (sequence->list (in-producer (thunk (bytes-ref (crypto-random-bytes 1) 0)) (%do [< _ 42])))
'(189)

arc> (sequence->list (in-producer (thunk (bytes-ref (crypto-random-bytes 1) 0)) (%do [< _ 42])))
'(141 148 243)

arc> (define (1+ (n 0)) (+ n 1))

arc> (1+ 21)
22

arc> (1+)
1

arc> (1+:1+:1+ 21)
24

@akkartik
Copy link
Member

akkartik commented Feb 8, 2019

The description at http://arclanguage.org/item?id=21163 is super useful to understanding the intent of this PR. Thanks!

Copy link
Member

@rocketnia rocketnia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some extensive feedback at various levels of severity. Most importantly, see the "this seems incorrect" comment where I talk about how Anarki's existing $ and unquote approach already addresses these needs with what I think of as a better design.

@@ -1,10 +1,8 @@
#lang racket/base
#lang racket/load
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it impossible to provide things which can be required by other Racket modules. This makes it hard to load more than one instance of the Anarki runtime in a Racket program; the only way to load this code more than once is by using something dynamic like eval or dynamic-require. And if that's the approach you think we should take, then you should keep in mind this would currently cause us to have a different ar-atomic-sema semaphore and a different ar-tagged structure type in each Anarki runtime.


(read-accept-bar-quote #f)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This side effect potentially clobbers other uses of read in a larger Racket program that happens to load Anarki.

@@ -41,8 +41,112 @@
(define main-namespace
(namespace-anchor->namespace main-namespace-anchor))

(define lang* (make-parameter 'arc))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why make this a parameter? Why not pass it as an explicit argument of ac?

(let ((x (assoc s ac-global-names)))
(if ;#f
x
(cadr x) (string->symbol (string-append "" (symbol->string s)))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the way you intended to indent this if call, all right, but I want to call attention to it in case it was an accident.

(and (symbol? x) (> (string-length (symbol->string x)) 0) (eqv? #\| (string-ref (symbol->string x) 0))))

(define (id-literal x)
(let ((s (substring (symbol->string x) 1 (- (string-length (symbol->string x)) 1))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file uses square brackets for let bindings and cond clauses, as per the usual Racket style. If you want to change that, why, and why not change it everywhere else in the file too?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice you're shaving off the last character of the identifier here, probably because you're assuming it's a | character, but you never actually check for that; you only check for | at the beginning (in id-literal?).

Why use the syntax |foo| anyway? It looks like you may have been somehow inspired by the existing meaning of |foo|, but the fact this syntax clobbers that one is more of a downside than an upside.

@@ -933,7 +1071,8 @@
((async-channel? x) 'channel)
((evt? x) 'event)
[(keyword? x) 'keyword]
[#t (err "Type: unknown type" x)]))
; [#t (err "Type: unknown type" x)]))
[#t (vector-ref (struct->vector x) 0)]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair implementation for treating Racket as though it's Arc, but for as inconsistent as Arc is about type-related behavior, struct->vector is possibly even more unreliable in Racket. I recently brought up racket/racket#2454 and while they were fixing it, they tweaked several struct->vector results across the language.

I think if you want a catch-all case for exotic Racket values, returning 'unknown would probably be better. Anarki doesn't promise future-compatibility, but it's easy for programmers to overlook that and expect future-compatibility anyway. If a programmer interactively discovers the type of something is 'unknown, I bet that'll raise their awareness of the instability, and they might be able to prepare themselves better for it to become "known" as something else in a future Anarki release.

@@ -1603,10 +1742,9 @@ Arc 3.1 documentation: https://arclanguage.github.io/ref.
val))

(define (bound? arcname)
(with-handlers ([exn:fail:syntax? (lambda (e) #t)]
(with-handlers ([exn:fail:syntax? (lambda (e) (if (eqv? arcname '_) #f 'syntax))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've had trouble with the _ syntax in Racket interacting with Arc too, lol. :)

I think a more seamless approach to bound? here (which isn't to say a very easy or accessible one) might first involve running all Arc code during a Racket syntax transformer, so that we could call syntax-local-value to determine whether something was bound to Racket syntax. Then, to make the interaction with _ make sense, we would refactor ac so that instead of recursively compiling a whole expression, it would expand to a Racket expression that contained nested occurrences of a Racket macro that expanded its body as Arc code again. That way, when _ is used as a local variable, the Racket macroexpander will understand this, and syntax-local-variable won't look up the module-level meaning anymore.

Relying on Racket's macroexpander like that would railroad the development of the Anarki macro system; I suspect it couldn't do certain things until a new Racket release allowed it to. For all the things Racket's macroexpander can do that ac can't, I think of it as a downgrade in the long term. Racket's macroexpander design is full of dynamic scoping (e.g. the fact syntax-local-value can only be used within the dynamic extent of a macro call) and leaky abstractions (e.g. the fact that Racket macros return s-expressions which their caller can pull apart) and ac is closer to a design path that avoids these things.

[exn:fail:contract:variable? (lambda (e) #f)])
(namespace-variable-value (ac-global-name arcname))
#t))
(namespace-variable-value (ac-global-name arcname))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you change bound? to return something other than a Boolean, I think it will conform to Racket conventions better to remove the question mark from the name. If you're trying to keep it the way it is for backwards compatibility, then I recommend making a second procedure and leaving this one as a predicate.

(if acons.l
(do (f car.l) (recur cdr.l))
(generator? l) (let x (l) (unless (void? x) (f x) (recur l)))
(sequence? l) (walk (sequence->generator l) f))))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation on this seems inconsistent. The shortest case is split across two lines while the longer ones are crammed onto one. At first glance I thought this was an if with four subforms, when it really has six subforms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right, isn't the point of walk that it's a stub for people to defextend? I think if you're going to extend it for generators and sequences, those would be better placed in defextend declarations.

`(,(ac fn env) ,@(map (lambda (x) (ac x env)) args)))]
((and (id-literal? fn)
(void? (id-literal fn)))
(map (lambda (x) (ac x env)) args))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems incorrect. The point of ac is to compile an Arc expression into a Racket expression. When the thing we're calling is a Racket macro, not all the subforms are necessarily supposed to be expressions; some are patterns or variable bindings or require specifications, etc.

By treating them as expressions, you're changing the meaning of ac so that sometimes it processes things that aren't really expressions. So ac ends up having to be a compiler and an s-expression template DSL at the same time, in different contexts.

I think Anarki's existing approach, using $ to get into Racket and unquote to return to Arc, is already a better s-expression template DSL design than this is.

@rocketnia
Copy link
Member

Here's a link to the Arc Forum thread since some discussion has happened there: http://arclanguage.org/item?id=21162

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants