What compiles into a method body?

2013-02-04 Thread Brian Marick
An individual Midje fact (test case, roughly) macroexpands into a form like 
this:

   (record-fact-existence!
 (letfn [(fun1 [] ...test code is here...)
 (fun2 [] (with-meta fun1 {...metadata...}))]
   (fun2))
   
Tabular facts in Midje are done via unification. A table with 8 rows turns into 
8 individual facts (each of that structure), surrounded by an outer level fact 
that runs each of them. This is wasteful of space but allows unusually 
expressive tests.

Something I've done recently seems to have pushed some tabular facts over the 
Java method-length limit:

 Caused by: java.lang.ClassFormatError: Invalid method Code length 81209 in 
 class file midje/t_checkers$eval24254$this_function_here_24253__24497

(This is actually surprising, since I don't see what I've done today that could 
push the size so *much* over 64K.)

I hope my two breaking tabular facts are atypically large, but I fear not. So, 
some questions / thoughts:

1. Do mutually recursive `letfn` functions get compiled into a single method? 
More generally, I hope that any nested function definitions turn into separate 
methods. Do they?

2. The metadata is rather large - can it somehow end up increasing the method 
bytecodes? What if it's constructed via merging literals, like this:

  (clojure.core/merge
   '{:midje/body-source ((+ 1 1) = 2), ...}
   {:midje/top-level-fact? true})))]

The guts of assertion checking also involves merging maps:

(check-one (clojure.core/merge
{:position
 :expected-result-form '2,
 :expected-result 2,
 :function-under-test (clojure.core/fn [] (+ 1 1))}
{:arrow '=, :call-form '1}
(hash-map-duplicates-ok :position (line-number-known 2

3. Midje goes to a lot of trouble to obey lexical scoping, so that you can 
write, for example:

(let [a 2]
  (fact
(let [b 2]
  (+ a b) = (* a b)))

Do closed-over lexical environments contribute unduly to method size? 

4. How can I get a look at what a big fact compiles into? (I suppose I need to 
AOT-compile a test namespace, but I've never had much luck with that.)


Looking for 1/2-time employment as a Clojure programmer
Latest book: /Functional Programming for the Object-Oriented Programmer/
https://leanpub.com/fp-oo

-- 
-- 
You received this message because you are subscribed to the Google
Groups Clojure group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
Clojure group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.




Re: What compiles into a method body?

2013-02-04 Thread Alan Malloy
Basically each lambda compiles into a method body, and the size of its 
lexical environment doesn't matter very much, only the amount of code 
inside the lambda's (fully macroexpanded) body. So in (fn a [x] (foo (letfn 
[(b [] (bar)) (c [] (baz))])), a only pays for foo, and b and c pay for 
bar and baz respectively. So the only cause of too-large methods is a 
single large lambda that doesn't delegate to other lambdas.

I checked out midje and tried macroexpanding the test that's giving you 
problems: it expands to 140KB of Clojure source! The culprit is indeed the 
creation of your metadata maps: the :midje/body-source and :midje/source 
keys are each around 40KB literals. I don't know the details of how quoted 
values get put into the bytecode, but it's possible that code is emitted to 
generate them at runtime; perhaps they go into a static initializer, but 
that probably has the same code size limits anyway.

On Monday, February 4, 2013 2:53:04 PM UTC-8, Brian Marick wrote:

 An individual Midje fact (test case, roughly) macroexpands into a form 
 like this: 

(record-fact-existence! 
  (letfn [(fun1 [] ...test code is here...) 
  (fun2 [] (with-meta fun1 {...metadata...}))] 
(fun2)) 
 
 Tabular facts in Midje are done via unification. A table with 8 rows turns 
 into 8 individual facts (each of that structure), surrounded by an outer 
 level fact that runs each of them. This is wasteful of space but allows 
 unusually expressive tests. 

 Something I've done recently seems to have pushed some tabular facts over 
 the Java method-length limit: 

  Caused by: java.lang.ClassFormatError: Invalid method Code length 81209 
 in class file midje/t_checkers$eval24254$this_function_here_24253__24497 

 (This is actually surprising, since I don't see what I've done today that 
 could push the size so *much* over 64K.) 

 I hope my two breaking tabular facts are atypically large, but I fear not. 
 So, some questions / thoughts: 

 1. Do mutually recursive `letfn` functions get compiled into a single 
 method? More generally, I hope that any nested function definitions turn 
 into separate methods. Do they? 

 2. The metadata is rather large - can it somehow end up increasing the 
 method bytecodes? What if it's constructed via merging literals, like this: 

   (clojure.core/merge 
'{:midje/body-source ((+ 1 1) = 2), ...} 
{:midje/top-level-fact? true})))] 

 The guts of assertion checking also involves merging maps: 

 (check-one (clojure.core/merge 
 {:position 
  :expected-result-form '2, 
  :expected-result 2, 
  :function-under-test (clojure.core/fn [] (+ 1 1))} 
 {:arrow '=, :call-form '1} 
 (hash-map-duplicates-ok :position (line-number-known 2 

 3. Midje goes to a lot of trouble to obey lexical scoping, so that you can 
 write, for example: 

 (let [a 2] 
   (fact 
 (let [b 2] 
   (+ a b) = (* a b))) 

 Do closed-over lexical environments contribute unduly to method size? 

 4. How can I get a look at what a big fact compiles into? (I suppose I 
 need to AOT-compile a test namespace, but I've never had much luck with 
 that.) 

  
 Looking for 1/2-time employment as a Clojure programmer 
 Latest book: /Functional Programming for the Object-Oriented Programmer/ 
 https://leanpub.com/fp-oo 



-- 
-- 
You received this message because you are subscribed to the Google
Groups Clojure group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
Clojure group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.




Re: What compiles into a method body?

2013-02-04 Thread Brian Marick
 I checked out midje and tried macroexpanding the test that's giving you 
 problems: it expands to 140KB of Clojure source! The culprit is indeed the 
 creation of your metadata maps: the :midje/body-source and :midje/source keys 
 are each around 40KB literals. 

You are my hero. Somehow the :midje/source gets stored as the 
post-macroexpansion version, instead of the pre-macroexpansion. And 
:midje/body-source should be a SHA1 hash anyway.


Looking for 1/2-time employment as a Clojure programmer
Latest book: /Functional Programming for the Object-Oriented Programmer/
https://leanpub.com/fp-oo

-- 
-- 
You received this message because you are subscribed to the Google
Groups Clojure group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
Clojure group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.