brendan
2017-11-27 21:50:31

Could anyone give me a hand with a confusion about requiring & phases?


notjack
2017-11-27 21:53:07

:wave:


brendan
2017-11-27 21:54:58

I have a macro defined by make-require-transformer in one module, and a certain special definition in a second. In a third module I want to require this definition with the special require modifier during phase 1. I’m using (require <module1> (for-syntax (<special require thing> <module2>))), but I get <special require thing>: not a require sub-form


brendan
2017-11-27 21:55:23

It seems like nesting the require modifier in for-syntax breaks it?


samth
2017-11-27 21:56:22

@brendan btw, once you’re using make-require-transformer, then you shouldn’t feel like you need to use the #beginners channel :slightly_smiling_face:


notjack
2017-11-27 21:56:29

just checking: by “a macro defined by make-require-transformer” I assume you did (define-syntax id <make-require-transformer-call>) instead of (define id <make-require-transformer-call> right?


brendan
2017-11-27 21:57:23

@samth well that code was written by lexi.lambda


brendan
2017-11-27 21:58:07

@notjack yes


notjack
2017-11-27 21:58:51

@brendan require transformers are a little weird in that you can’t do (require <module1> (<require-transformer-defined-in-module1> stuff ...)), you have to import the transformer with a whole separate require statement


notjack
2017-11-27 21:59:04

is that causing your problem maybe?


brendan
2017-11-27 21:59:35

Weird, because later on I do exactly that, just not in for-syntax


notjack
2017-11-27 21:59:54

(at least, I think you can’t… I know you can’t do something similar with provide transformers)


notjack
2017-11-27 22:00:07

hmm. maybe you can then and I’m just wrong :p


samth
2017-11-27 22:00:25

@notjack you used to not be able to, but I fixed it


notjack
2017-11-27 22:00:25

my next guess is you need to import the transformer for-syntax in order to use it for-syntax


notjack
2017-11-27 22:00:31

@samth ah gotcha


brendan
2017-11-27 22:00:47

I was importing it for-syntax


brendan
2017-11-27 22:01:12

And when I extract the requiring of that module to another require statement it continues to fail


notjack
2017-11-27 22:01:29

could you share a small snippet of the failing code?


brendan
2017-11-27 22:04:02

So here’s the failing require


brendan
2017-11-27 22:04:06
(require (for-syntax racket/base
                     racket/match
                     syntax/kerncase
                     threading
                     hackett/private/typecheck
                     hackett/private/type-reqprov
                     (unmangle-types-in #:no-introduce hackett/private/prim/type))
         racket/promise
         syntax/parse/define
         hackett/private/type-reqprov
         (only-in hackett/private/base τ⇒! τ⇐!)
         (only-in (unmangle-types-in #:no-introduce hackett/private/kernel) String [#%app @%app])
         (only-in hackett/private/prim/base show unsafe-run-io!))

notjack
2017-11-27 22:04:54

unmangle-types-in is defined in which module?


brendan
2017-11-27 22:04:54

If I strip out the irrelevant stuff then it’s (require (for-syntax hackett/private/type-reqprov (unmangle-types-in #:no-introduce hackett/private/prim/type)) hackett/private/type-reqprov ...)


notjack
2017-11-27 22:05:01

ah


samth
2017-11-27 22:05:21

@lexi.lambda might have useful thoughts on this question


lexi.lambda
2017-11-27 22:05:23

@lexi.lambda has joined the channel


notjack
2017-11-27 22:05:24

that seems to me like it should work


brendan
2017-11-27 22:06:32

It fails with hackett-lib/hackett/private/toplevel.rkt:68:21: unmangle-types-in: not a require sub-form


brendan
2017-11-27 22:08:47

I changed it to (require hackett/private/type-reqprov (for-syntax (unmangle-types-in #:no-introduce hackett/private/prim/type)) hackett/private/type-reqprov ...)


brendan
2017-11-27 22:09:07

And now I get a more helpful error


notjack
2017-11-27 22:09:48

what happens if you do:

(require hackett/private/type-reqprov
         (unmangle-types-in #:no-introduce hackett/private/prim/type (for-syntax hackett/private/prim/type)))

notjack
2017-11-27 22:10:07

that is, move the for-syntax bit inside the types in so you only have one non-for-syntax use of the transformer?


brendan
2017-11-27 22:11:15
module: identifier already imported for syntax from a different source in:
  Unit
  (rename hackett/private/prim/type Unit #%hackett-type:Unit)

brendan
2017-11-27 22:11:50

Unit is defined in that module, but no other modules I require have it


notjack
2017-11-27 22:12:21

whoops I used hackett/private/prim/type as the imported module in both normal and for-syntax, but your snippet imports hackett/private/kernel in normal phase (and with only-in)


brendan
2017-11-27 22:12:38

Oh sorry I assumed that was a typo


brendan
2017-11-27 22:12:55

This occurred with (unmangle-types-in #:no-introduce (for-syntax hackett/private/prim/type))


notjack
2017-11-27 22:13:07

ah


notjack
2017-11-27 22:14:45

I’m lost ¯_(ツ)_/¯


notjack
2017-11-27 22:14:58

I think the issue is hackett-specific


brendan
2017-11-27 22:16:22

Probably something like that


brendan
2017-11-27 22:17:21

I tried to just rename the identifier explicitly rather than using lexi.lambda’s magic require form, but now the identifier is unbound


brendan
2017-11-27 22:18:08

/facedesk I mixed up the orig-id and bind-id


brendan
2017-11-27 22:21:47

Okay, thanks for the help @notjack! I rewrote it and now my problem is now entirely hackett specific


notjack
2017-11-27 22:23:37

glad to help!


lexi.lambda
2017-11-27 22:30:32

Is the issue resolved? I’m not at my desk right now, but I can take a look in an hour or two if you’re still having trouble.


notjack
2017-11-27 22:34:37

@brendan ping


brendan
2017-11-27 22:35:17

The issue is not resolved, a friend asked me to help her with her code (what a coincidence!)


brendan
2017-11-27 22:36:01

It seems like it boils down to me not being sure how to import a type (IO) for syntax


lexi.lambda
2017-11-27 23:27:57

@brendan I’m curious why you want to import a type for-syntax


brendan
2017-11-27 23:30:23

To give better error reporting if somebody does (#:<- x {1 :: Nil}). In my implementation I get the type of the expression given to #:<- and match it against (τ:app τ_fn τ_arg), but then I want to check τ=? τ_fn IO


brendan
2017-11-27 23:30:52

It works without this check, but the error messages are user unfriendly


lexi.lambda
2017-11-27 23:31:43

so, type bindings are not actually phase 1 bindings. there’s a chance you want (parse-type #'IO).


brendan
2017-11-27 23:32:26

Oh, right. I saw you do that with String at some point but didn’t realize that’s what you were doing


brendan
2017-11-27 23:32:38

So should IO be required for phase 1 still?


lexi.lambda
2017-11-27 23:32:54

no, IO is a phase 0 binding. are you familiar with syntax-local-value?


brendan
2017-11-27 23:34:07

Nope. Sorry


lexi.lambda
2017-11-27 23:34:54

The basic idea is that there are two sorts of bindings in Racket: runtime bindings and syntax bindings. These correspond to define and define-syntax, respectively.


lexi.lambda
2017-11-27 23:35:31

There are also various other binding forms, each with runtime and syntax variants. define-values / define-syntaxes, let / let-syntax, let-values / let-syntaxes, etc.


lexi.lambda
2017-11-27 23:36:22

Each binding additionally has a phase. Phase 0 is runtime, phase 1 is compile-time for phase 0, phase 2 is compile-time for phase 1, etc. But then here’s the question: when you define a macro with define-syntax, are you defining a binding at phase 0 or phase 1?


lexi.lambda
2017-11-27 23:37:15

The answer is subtle. Consider a normal definition. In (define <id> <rhs>), you define a phase 0 binding named <id>, and it’s bound to the result of <rhs>, evaluated at phase 0.


lexi.lambda
2017-11-27 23:38:00

You can shift this up to phase 1 by wrapping the whole thing in begin-for-syntax, so (begin-for-syntax (define <id> <rhs>)) defines <id> at phase 1, and it binds it to the result of <rhs>, evaluated at phase 1.


lexi.lambda
2017-11-27 23:38:28

But what about (define-syntax <id> <rhs>)? Well, this is a bit interesting: it defines <id> at phase 0, but it binds it to the result of <rhs>, evaluated at phase 1.


lexi.lambda
2017-11-27 23:38:56

So in a syntax binding, the binding is at phase 0, but the binding’s value is at phase 1.


lexi.lambda
2017-11-27 23:39:19

Why is any of this relevant? Well, syntax-local-value lets you get the phase 1 value associated with a phase 0 binding.


brendan
2017-11-27 23:39:39

Is this at runtime or compile time/what phase does this happen at?


lexi.lambda
2017-11-27 23:39:56

It’s at phase 1. So you can write (define-syntax x 'hello), and evaluating (syntax-local-value #'x) produces 'hello at phase 1.


brendan
2017-11-27 23:40:17

Isn’t the binding only available at phase 0?


lexi.lambda
2017-11-27 23:41:13

In a sense, it is. syntax-local-value feels a bit like a reflective operation. But remember that the RHS really is a phase 1 value, evaluated at compile-time, so the value is available at phase 1.


brendan
2017-11-27 23:41:23

Okay, that makes sense


lexi.lambda
2017-11-27 23:42:13

This is really important because it means define-syntax essentially bridges the gap between phase 0 and phase 1. It means you can bind things in the phase 0 namespace, which is what people usually expect, but get information out of them at phase 1.


lexi.lambda
2017-11-27 23:43:11

You can think of this as how the macroexpander works, in a sense… it recursively walks the program and finds all the identifiers where syntax-local-value produces a function, then it calls that function with some syntax. That’s a macro transformation.


brendan
2017-11-27 23:43:19

Just to be clear, I can do (define-syntax x 'hello) (print x) and it will print 'hello at runtime?


lexi.lambda
2017-11-27 23:43:31

No, it won’t. You’ll get bad syntax.


lexi.lambda
2017-11-27 23:44:14

Why is that? Well, when the macroexpander encounters a syntax binding used as an expression, it assumes it is a macro and expects it to be bound to a phase 1 function.


lexi.lambda
2017-11-27 23:44:43

In this case, however, x isn’t bound to a phase 1 function, it’s bound to a symbol. So the macroexpander just barfs.


brendan
2017-11-27 23:45:04

Oh! I think it just clicked


lexi.lambda
2017-11-27 23:45:29

But that doesn’t mean such a definition is useless! Why? Well, you could write a macro that is given x as a subform, then it could call syntax-local-value on the provided identifier, and it could get the information out.


lexi.lambda
2017-11-27 23:46:45

Like this: (define-syntax x 3) (define-syntax (mac stx) (syntax-parse stx [(_ id:id) #`(quote #,(syntax-local-value #'x))])) (mac x) ; => 3


lexi.lambda
2017-11-27 23:46:54

(I think that should work; I didn’t test it.)


lexi.lambda
2017-11-27 23:47:23

Oops, I forgot a #'.


lexi.lambda
2017-11-27 23:47:39

Okay, fixed. Now it should work.


brendan
2017-11-27 23:47:58

_: wildcard not allowed as expression?


lexi.lambda
2017-11-27 23:48:08

That means you need to import syntax/parse. :)


brendan
2017-11-27 23:48:09

The idea seems to make sense though


brendan
2017-11-27 23:48:14

oh, right


lexi.lambda
2017-11-27 23:48:15

(require (for-syntax syntax/parse))


brendan
2017-11-27 23:48:31

Yes, it works


lexi.lambda
2017-11-27 23:48:34

It’s a bad error message. I wonder if there’s a way to make it better.


lexi.lambda
2017-11-27 23:48:47

Anyway, this is used all the time in Racket. For example, do you know about match expanders?


brendan
2017-11-27 23:49:02

Like, the implementation of match?


lexi.lambda
2017-11-27 23:49:32

Like define-match-expander.


brendan
2017-11-27 23:49:37

no


lexi.lambda
2017-11-27 23:49:58

Hmm… what about syntax classes? Are you familiar with those?


brendan
2017-11-27 23:50:12

Yes


brendan
2017-11-27 23:50:28

Checking the docs, match expanders seem to expand the functionality of match


lexi.lambda
2017-11-27 23:51:24

Yeah. Both of them are implemented by using define-syntax to store some phase 1 information, and then match and syntax-parse use syntax-local-value to look up the associated information at expansion-time.


lexi.lambda
2017-11-27 23:52:22

You’ll note that if you use define-syntax-class to define some syntax class, then try and use that syntax class outside of a syntax-parse pattern, you’ll get illegal use of syntax, the same error as if you try and use x as an expression after (define-syntax x 3).


brendan
2017-11-27 23:57:03

I think I understand


lexi.lambda
2017-11-27 23:59:24

Consider playing with define-syntax and syntax-local-value a bit. One cool, simple use of it that I found in one of Robby’s talks is using it to define two forms: define-enum and enum-case. You should be able to use them like this: (define-enum animal [cat dog fish]) (enum-case animal 'cat [cat 'meow] [dog 'woof] [fish 'blub]) …and enum-case should raise a compile-time error if not all the enum cases are covered.


lexi.lambda
2017-11-27 23:59:53

I should add that to my Racket macro exercises repo. :)


brendan
2017-11-28 00:00:04

oh dang you have a Racket macro exercises repo?


lexi.lambda
2017-11-28 00:00:17

There’s only 3 exercises currently, haha. I’m trying to collect more!


brendan
2017-11-28 00:00:19

I should go through that/an analogue



brendan
2017-11-28 00:04:25

So I changed my code to use parse-type, but the type-inferer seems to infer a different type than (parse-type #'IO) for (println "Hello, world!") The definition I’m using is [(_ . (#:<- ~! id:id expr:expr)) (match-let-values ([(e- τ_e) (τ⇒! #'expr)]) (match τ_e [(τ:app τ_fn τ_arg) #:when (τ=? τ_fn (parse-type #'IO)) (let ([e-/exec (τ⇐! (quasisyntax/loc this-syntax (@%app unsafe-run-io! #,e-)) τ_arg)]) #`(force #,e-/exec))] [_ #`(format "error: ~a is not an (IO _)" #,(τ->string (apply-current-subst τ_e)))]))] Am I misunderstanding how τ=? works?


lexi.lambda
2017-11-28 00:06:12

Probably. τ=? is not very smart. It does literal type comparison, so if something is (IO Unit), it will not be τ=? to IO. Furthermore, and more insidiously, some unsolved type variable will not be τ=? to IO, even if they could unify. Even worse, (forall [a] a) and (forall [b] b) are not τ=?.


brendan
2017-11-28 00:06:48

Well the weird thing is what gets printed out is ; <pkgs>/hackett-lib/hackett/private/toplevel.rkt:96.60: parse-type: expected ; type ; at: IO ; in: IO


lexi.lambda
2017-11-28 00:07:02

Have you imported IO at phase 0?


brendan
2017-11-28 00:07:12

I believe so


lexi.lambda
2017-11-28 00:07:28

Hmm.


brendan
2017-11-28 00:10:35

So, I thought I had imported it because when I tried to explicitly require it from the module it’s defined in I got the error module: identifier already imported from a different source in: IO (rename hackett/private/prim/type IO #%hackett-type:IO) (rename hackett/private/prim/type IO IO) but now I realize this might be because one of the modules required for syntax could be exporting it without me realizing it


brendan
2017-11-28 00:10:44

it doesn’t seem like it though


brendan
2017-11-28 00:11:05

In fact, I can’t find where I’ve apparently already imported it from, but when I try to require it I get that error


lexi.lambda
2017-11-28 00:11:14

Oh! You might be accidentally trying to use the value constructor IO. :)


brendan
2017-11-28 00:11:23

How could I distinguish those?


lexi.lambda
2017-11-28 00:11:52

IO is defined as (data (IO a) (IO {Real-World -> (Tuple Real-World a)})).


lexi.lambda
2017-11-28 00:12:40

I recently made Hackett multi-namespace with a bunch of terrible hacks. So you can probably get at the type using #%hackett-type:IO… but I wouldn’t recommend it. The #%hackett-type: prefix is supposed to be an implementation detail. unmangle-types-in is the preferred interface.


lexi.lambda
2017-11-28 00:13:47

You probably want to (require (unmangle-types-in #:prefix t: hackett/private/prim/type)), then use (parse-type #'t:IO) or something like that.


lexi.lambda
2017-11-28 00:14:42

That imports all the type-level stuff with a t: prefix.


lexi.lambda
2017-11-28 00:15:43

Working with the two namespaces in single-namespaced Racket kind of sucks.


brendan
2017-11-28 00:16:25

It might be nice to have punning be unidiomatic in Hackett?


brendan
2017-11-28 00:16:57

It confuses beginners sometimes


lexi.lambda
2017-11-28 00:17:17

I think Hackett is, currently, somewhat unabashedly not for beginners. :)


brendan
2017-11-28 00:17:53

That’s entirely true


brendan
2017-11-28 00:18:09

So, the error has changed to ; <pkgs>/hackett-lib/hackett/private/toplevel.rkt:97.60: parse-type: expected ; type ; at: t:IO ; in: t:IO


brendan
2017-11-28 00:18:41

Which makes me think that punning was not the issue


lexi.lambda
2017-11-28 00:18:52

Could you post all your requires?


brendan
2017-11-28 00:19:07
(require (for-syntax racket/base
                     racket/match
                     syntax/kerncase
                     threading
                     hackett/private/typecheck)
         racket/promise
         syntax/parse/define
         hackett/private/type-reqprov
         (only-in hackett/private/base τ⇒! τ⇐!)
         (only-in (unmangle-types-in #:no-introduce hackett/private/kernel) String [#%app @%app])
         (only-in (unmangle-types-in #:prefix t: hackett/private/prim/type) IO)
         (only-in hackett/private/prim/base show unsafe-run-io!))

lexi.lambda
2017-11-28 00:19:39

The (only-in .... IO) is the problem. That’s importing only IO, not t:IO.


brendan
2017-11-28 00:19:48

wow, that’s obvious in hindsight


lexi.lambda
2017-11-28 00:19:55

Yes, but only in hindsight. :)


lexi.lambda
2017-11-28 00:20:38

Working with the two namespaces in normal Racket is legitimately confusing, and I hope it isn’t something normal users ever have to do.