blob: e9e90d607f27d4b2c37db8221f2bc095ac0be9f2 [file] [log] [blame]
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head>
<meta charset='utf-8'/><meta http-equiv='X-UA-Compatible' content='IE=edge'/><meta name='viewport' content='width=device-width, initial-scale=1'/><meta name='keywords' content='groovy, chatgpt, one-liners'/><meta name='description' content='I asked ChatGPT if it could represent some short/one-liner programming-related dad jokes as short/one-liner Groovy scripts.'/><title>The Apache Groovy programming language - Blogs - ChatGPT meets Groovy one-liners</title><link href='../img/favicon.ico' type='image/x-ico' rel='icon'/><link rel='stylesheet' type='text/css' href='../css/bootstrap.css'/><link rel='stylesheet' type='text/css' href='../css/font-awesome.min.css'/><link rel='stylesheet' type='text/css' href='../css/style.css'/><link rel='stylesheet' type='text/css' href='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.css'/>
</head><body>
<div id='fork-me'>
<a href='https://github.com/apache/groovy'>
<img style='position: fixed; top: 20px; right: -58px; border: 0; z-index: 100; transform: rotate(45deg);' src='/img/horizontal-github-ribbon.png'/>
</a>
</div><div id='st-container' class='st-container st-effect-9'>
<nav class='st-menu st-effect-9' id='menu-12'>
<h2 class='icon icon-lab'>Socialize</h2><ul>
<li>
<a href='https://groovy-lang.org/mailing-lists.html' class='icon'><span class='fa fa-envelope'></span> Discuss on the mailing-list</a>
</li><li>
<a href='https://twitter.com/ApacheGroovy' class='icon'><span class='fa fa-twitter'></span> Groovy on Twitter</a>
</li><li>
<a href='https://groovy-lang.org/events.html' class='icon'><span class='fa fa-calendar'></span> Events and conferences</a>
</li><li>
<a href='https://github.com/apache/groovy' class='icon'><span class='fa fa-github'></span> Source code on GitHub</a>
</li><li>
<a href='https://groovy-lang.org/reporting-issues.html' class='icon'><span class='fa fa-bug'></span> Report issues in Jira</a>
</li><li>
<a href='http://stackoverflow.com/questions/tagged/groovy' class='icon'><span class='fa fa-stack-overflow'></span> Stack Overflow questions</a>
</li><li>
<a href='http://groovycommunity.com/' class='icon'><span class='fa fa-slack'></span> Slack Community</a>
</li>
</ul>
</nav><div class='st-pusher'>
<div class='st-content'>
<div class='st-content-inner'>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]--><div><div class='navbar navbar-default navbar-static-top' role='navigation'>
<div class='container'>
<div class='navbar-header'>
<button type='button' class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
<span class='sr-only'></span><span class='icon-bar'></span><span class='icon-bar'></span><span class='icon-bar'></span>
</button><a class='navbar-brand' href='../index.html'>
<i class='fa fa-star'></i> Apache Groovy
</a>
</div><div class='navbar-collapse collapse'>
<ul class='nav navbar-nav navbar-right'>
<li class=''><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li class=''><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li class=''><a href='/download.html'>Download</a></li><li class=''><a href='https://groovy-lang.org/support.html'>Support</a></li><li class=''><a href='/'>Contribute</a></li><li class=''><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li class=''><a href='/blog'>Blog posts</a></li><li class=''><a href='https://groovy.apache.org/events.html'></a></li><li>
<a data-effect='st-effect-9' class='st-trigger' href='#'>Socialize</a>
</li><li class=''>
<a href='../search.html'>
<i class='fa fa-search'></i>
</a>
</li>
</ul>
</div>
</div>
</div><div id='content' class='page-1'><div class='row'><div class='row-fluid'><div class='col-lg-3'><ul class='nav-sidebar'><li><a href='./'>Blog index</a></li><li class='active'><a href='#doc'>ChatGPT meets Groovy one-liners</a></li></ul></div><div class='col-lg-8 col-lg-pull-0'><a name='doc'></a><h1>ChatGPT meets Groovy one-liners</h1><p><span>Author: <i>Paul King</i></span><br/><span>Published: 2023-10-19 06:00PM</span></p><hr/><div class="paragraph">
<p>I asked ChatGPT if it could represent some short/one-liner programming-related dad
jokes as short/one-liner Groovy scripts. It&#8217;s responses were initially a little
underwhelming but after some coercing and some "post-production tweaking", this blog post contains the result. Be forewarned, that the quality of the puns
is mixed.</p>
</div>
<div class="paragraph">
<p>But before starting, Groovy makes an excellent language for interacting
with cloud-based Generative AI systems. You can check out the
following <a href="https://github.com/paulk-asert/demo">GitHub repo</a> that
shows how to talk to ChatGPT using Groovy and <a href="https://micronaut.io/">Micronaut</a>.
This example is in turn based on <a href="https://www.kousenit.com/">Ken Kousen</a>'s similar example in Java.
Check out his excellent <a href="https://www.youtube.com/watch?v=cEl3He6Tsmk">video on the topic</a>. Also check out <a href="https://glaforge.dev/">Guillaume Laforge</a>'s recent posts
about using Groovy to talk to Google&#8217;s <a href="https://bard.google.com/">Bard</a> via
its <a href="https://glaforge.dev/posts/2023/05/30/getting-started-with-the-palm-api-in-the-java-ecosystem/">PaLM 2 API</a> and using
<a href="https://glaforge.dev/posts/2023/09/25/discovering-langchain4j/">LangChain4J</a>.</p>
</div>
<div class="paragraph">
<p>Having said that, we just used the Web UI for this blog post.
Now, let&#8217;s have a look at some of those PUNch lines&#8230;&#8203;</p>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: Why do programmers always mix up Christmas</em> 🎄 <em>and Halloween</em> 🎃 <em>?</em></p>
</div>
<div class="paragraph">
<p><em>A: Because 25 Dec(imal) is the same as 31 Oct(al).</em></p>
</div>
</div>
</div>
<div class="paragraph">
<p>We can check that with the following Groovy script:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">def christmas = 25 // dec
def halloween = 031 // oct
assert christmas == halloween</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: Why is Groovy such a neat language?</em></p>
</div>
<div class="paragraph">
<p><em>A: Because it takes a bit from Go,
supports data science like R though with low ceremony,
mixes in a dose of Python and Perl, takes a decent chunk of Java
with a twist of C#, and finishes with some Ruby.</em></p>
</div>
</div>
</div>
<div class="paragraph">
<p>We can check that with the following Groovy script:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">assert 'Groovy' == 'Go'.take(1) + 'R'.toLowerCase() +
'Python'['Perl'.size()] * 2 +
'Java'['C#'.size()] + 'Ruby'[-1]</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: Why did the developer go broke?</em></p>
</div>
<div class="paragraph">
<p><em>A: Because his money cache, intellisense and null stocks were all empty</em> 💰.</p>
</div>
</div>
</div>
<div class="paragraph">
<p>We combine the <code>Expando</code> class with the Elvis operator and Groovy truth to check this fact:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">def money = intelli = new Expando()
def rich = money.cache ?: intelli.cents ?: null?.stocks
assert !rich</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: Why did the programmer think using Groovy was sweet?</em></p>
</div>
<div class="paragraph">
<p><em>A: Because creating a box of a-sorted chocolates was easy.</em></p>
</div>
</div>
</div>
<div class="paragraph">
<p>We create a "sortable" <code>Chocolate</code> record making use of default parameters and declarative augmentation. Then we create a <code>Box</code> class which delegates to <code>List</code>. We then create a box of chocolates and check it is "a-sorted" using the <code>toString()</code> method:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">@Sortable @ToString(ignoreNulls = true)
record Chocolate(int cocoa, String filling = null) {}
class Box { @Delegate List&lt;Chocolate&gt; contents }
var box = new Box(contents: [
new Chocolate(80),
new Chocolate(50, 'Caramel')
].sort())
assert box.toString() == '[Chocolate(50, Caramel), Chocolate(80)]'</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>If life gives you melons, &#8230;&#8203;</em></p>
</div>
<div class="paragraph">
<p><em>&#8230;&#8203;you may be dyslexic.</em> 🍈🍋</p>
</div>
</div>
</div>
<div class="paragraph">
<p>Let&#8217;s take the word 'melons' and jumble it around a bit using Groovy&#8217;s flexible indexing to see if we get 'lemons':</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">def dyslexic = 'melons'[2..0,3..-1] == 'lemons'
assert dyslexic</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>6:30 is the best time on a clock, hands down.</em> 🕡</p>
</div>
</div>
</div>
<div class="paragraph">
<p>Let&#8217;s create a <code>LocalTime</code> of 6:30 and check that the hands are pointing down. The minute hand travels 360 degrees in 60 minutes,
so 6 degrees per minute. We can check at 6:30 that it is pointing straight down (180°).
The hour hand travels 360° in 720 minutes (1/2 a day), so we divide the total minutes by 2. The total minutes is 60 times the number of hours plus the minutes for the current hour.
At 6:30, we know the hour hand will have travelled halfway between the 6 and 7 hour markers (15°) but for our test we&#8217;ll
just make sure it is point down (we&#8217;ll say within 20° of straight down which we&#8217;ll specify as a range). Here is the result:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">def (h, m) = LocalTime.of(6, 30)[HOUR_OF_DAY, MINUTE_OF_HOUR]
assert 6 * m == 180L
assert (h * 60 + m).intdiv(2) in 180L..200L</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: What&#8217;s the difference between ignorance and apathy?</em></p>
</div>
<div class="paragraph">
<p><em>A: I don’t know and I don’t care.</em></p>
</div>
</div>
</div>
<div class="paragraph">
<p>To show this with Groovy, we should first declare our
ignorance and apathy. Then we calculate the difference
and finally check our assertions:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">int ignorance, apathy
int know = care = ignorance - apathy
assert !know &amp;&amp; !care</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Can Groovy help me answer the question: Which came first, the chicken or the egg?</em></p>
</div>
</div>
</div>
<div class="paragraph">
<p>Sure, run this script:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">def (first, second) = ['egg', 'chicken'].shuffled()
println "Which came first, the $first or the $second? The eternal debate!"</code></pre>
</div>
</div>
<div class="paragraph">
<p>It really just asks the question in two different orders,
but I&#8217;m sure you get the idea.</p>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: Why are death, taxes, and immutability similar?</em></p>
</div>
<div class="paragraph">
<p><em>A: Because, in life and programming, they&#8217;re the only things you can&#8217;t change!</em></p>
</div>
</div>
</div>
<div class="paragraph">
<p>Since death and taxes are certain, we should store a quote
about them in an immutable structure.
A record offers shallow immutability and works well for this.
For deep immutability, consider using the <code>@Immutable</code> AST transform,
so here even though our quotes are stored in a mutable ArrayList,
there is no way to change it.
Here is an example:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">record Quote(String text, String author, int year) { }
var q1 = new Quote('Perfection is immutable. But for things imperfect, change is the way to perfect them.', 'Owen Feltham', 1840)
var q2 = new Quote('Nothing is certain except death and taxes.', 'Benjamin Franklin', 1789)
@Immutable
class FavoriteQuotes { List&lt;Quote&gt; list }
var favorites = new FavoriteQuotes([q1, q2])</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: Why are diamonds and dogs a person&#8217;s best friends?</em></p>
</div>
<div class="paragraph">
<p><em>A: Because diamonds are forever, and dogs are fur-ever!</em> 💎 🐕</p>
</div>
</div>
</div>
<div class="paragraph">
<p>Let&#8217;s use traits to check best friends for diamonds and dogs:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">trait HasBestFriend {
abstract String friend()
boolean isBestFriend(String candidate) {
friend() == candidate
}
}
class Diamond implements HasBestFriend {
String friend() { 'girl' }
}
class Dog implements HasBestFriend {
String friend() { 'man' }
}
assert ['man', 'girl'].collect{
[new Diamond().isBestFriend(it),
new Dog().isBestFriend(it)]
} == [[false, true], [true, false]]</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: Why did the Land Rover car get along so well with the Land Rover Explore smartphone?</em></p>
</div>
<div class="paragraph">
<p><em>A: Because they both knew the importance of "exploring" new territories,
whether off-road or online!!</em></p>
</div>
</div>
</div>
<div class="paragraph">
<p>Who knew that Cars and Smartphones had anything to do with each other,
but the trend seems to be companies want to get involved in both.
Similarly, if you have two classes that apparently have nothing to
do with one another, Groovy&#8217;s duck typing, or in this case
property handling might allow you to use them together more easily
than you think.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">import groovy.transform.*
record Smartphone(String make, String model, String color, int year) { }
record Car(String make, String model, String color, int year) { }
def s = new Smartphone('Landrover', 'Explore', 'Black', 2018)
def c = new Car(s.toMap())
assert c.toString() == 'Car[make=Landrover, model=Explore, color=Black, year=2018]'</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: Why did the Jedi use a mind trick on the stormtroopers when they couldn&#8217;t find the missing droids?</em></p>
</div>
<div class="paragraph">
<p><em>A: Because, as Obi-Wan said, "These aren&#8217;t the droids you&#8217;re looking for. You&#8217;re actually looking for your misplaced keys, and you&#8217;ll find them in the last place you look!"</em></p>
</div>
</div>
</div>
<div class="paragraph">
<p>Let&#8217;s first show how Groovy could help us find some droids that we are looking for.
Drones are hardest to find when there are several clone look-a-likes. We won&#8217;t mention the clone wars!</p>
</div>
<div class="paragraph">
<p>We&#8217;ll create a shuffled list of droids and their clones, and then search for the ones we are after:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">@AutoClone class Droid { String name }
def r2d2 = new Droid(name: 'R2-D2')
def c3po = new Droid(name: 'C-3PO')
def droids = [r2d2, c3po]
3.times {
droids &lt;&lt; r2d2.clone()
droids &lt;&lt; c3po.clone()
}
droids.shuffle()
droids.eachWithIndex { droid, index -&gt;
if (droid == r2d2) println "Droid $index is $r2d2.name"
if (droid == c3po) println "Droid $index is $c3po.name"
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>If we don&#8217;t want to output the index where we found the droid, we can use
an alternative expression to show that the droids we are after are found:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">assert droids.any{ it.is(r2d2) }
assert droids.any{ it.is(c3po) }</code></pre>
</div>
</div>
<div class="paragraph">
<p>Of course, Obi-Wan uses a Jedi mind trick which we can show here using
some Groovy metaprogramming, in this case a category class:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">class JediMindTrick {
static boolean is(Droid d, Droid other) { false }
}
use(JediMindTrick) {
assert !droids.any{ it.is(r2d2) }
assert !droids.any{ it.is(c3po) }
}</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: What happened when the pirates pet bird tried to say "Pieces of 7"?</em></p>
</div>
<div class="paragraph">
<p><em>A: It got a ParrotyError!"</em> 🦜</p>
</div>
</div>
</div>
<div class="paragraph">
<p>As shown here:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">try {
pet.say('Pieces of 7')
} catch(ParrotyError e) {
var plank = e.stackTrace
plank.walk()
}</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: Why did the functions stop calling each other?</em></p>
</div>
<div class="paragraph">
<p><em>A: Because they had constant arguments.</em></p>
</div>
</div>
</div>
<div class="paragraph">
<p>By definition, pure functions always return
the same result for the same inputs.
As such, a potential optimization for pure functions
is to cache the result for a given set of input values.
Groovy provides the <code>@Memoized</code> AST transform to do this
(and a <code>.memoized()</code> method call for Closures).</p>
</div>
<div class="paragraph">
<p>Let&#8217;s write a <code>StringUtil</code> class
that has an <em>almost</em> pure function. The return value of the <code>bothCases</code> method
is a pure function in terms of its inputs. It returns a list containing
the lowercase and uppercase values for the input string.
It also has a side effect of incrementing a counter whenever it is called;
this is just so we can understand what is going on.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">class StringUtil {
static int count = 0
@Memoized
static List&lt;String&gt; bothCases(String s) {
count++
[s.toLowerCase(), s.toUpperCase()]
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>We apply the <code>@Memoized</code> annotation to <code>bothCases</code> which enables the automatic
caching. The annotation has a number of optional annotation attributes for
configuring the caching behavior, but we&#8217;ll just use the defaults.</p>
</div>
<div class="paragraph">
<p>We can see that for constant arguments, 'Foo' called twice in our case,
that there is no need to invoke the <code>bothCases</code> method on the second call,
since we can use the cached value from the previous call.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">assert StringUtil.count == 0
assert StringUtil.bothCases('Foo') == ['foo', 'FOO']
assert StringUtil.count == 1
assert StringUtil.bothCases('Foo') == ['foo', 'FOO']
assert StringUtil.count == 1</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Binary: it&#8217;s easy as 1, 10, 11</em></p>
</div>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">println "Binary: It's as easy as ${(1..3).collect{ Integer.toBinaryString(it) }.join(', ')}"</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: On my first day of work at the sheep farm, I was asked to roundup 37 sheep?</em></p>
</div>
<div class="paragraph">
<p><em>A: I said 40!</em> 🐑 🐏</p>
</div>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">assert 40 == Math.round(37/10)*10</code></pre>
</div>
</div>
<hr>
<div class="sidebarblock">
<div class="content">
<div class="paragraph">
<p><em>Q: What code did the programmer write to celebrate Groovy&#8217;s 20th birthday?</em> 🎂</p>
</div>
<div class="paragraph">
<p><em>A: A hip-hip array</em></p>
</div>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="prettyprint highlight"><code data-lang="groovy">String[] celebrate = ['hip', 'hip']</code></pre>
</div>
</div>
<hr></div></div></div></div><footer id='footer'>
<div class='row'>
<div class='colset-3-footer'>
<div class='col-1'>
<h1>Groovy</h1><ul>
<li><a href='https://groovy-lang.org/learn.html'>Learn</a></li><li><a href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li><a href='/download.html'>Download</a></li><li><a href='https://groovy-lang.org/support.html'>Support</a></li><li><a href='/'>Contribute</a></li><li><a href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li><a href='/blog'>Blog posts</a></li><li><a href='https://groovy.apache.org/events.html'></a></li>
</ul>
</div><div class='col-2'>
<h1>About</h1><ul>
<li><a href='https://github.com/apache/groovy'>Source code</a></li><li><a href='https://groovy-lang.org/security.html'>Security</a></li><li><a href='https://groovy-lang.org/learn.html#books'>Books</a></li><li><a href='https://groovy-lang.org/thanks.html'>Thanks</a></li><li><a href='http://www.apache.org/foundation/sponsorship.html'>Sponsorship</a></li><li><a href='https://groovy-lang.org/faq.html'>FAQ</a></li><li><a href='https://groovy-lang.org/search.html'>Search</a></li>
</ul>
</div><div class='col-3'>
<h1>Socialize</h1><ul>
<li><a href='https://groovy-lang.org/mailing-lists.html'>Discuss on the mailing-list</a></li><li><a href='https://twitter.com/ApacheGroovy'>Groovy on Twitter</a></li><li><a href='https://groovy-lang.org/events.html'>Events and conferences</a></li><li><a href='https://github.com/apache/groovy'>Source code on GitHub</a></li><li><a href='https://groovy-lang.org/reporting-issues.html'>Report issues in Jira</a></li><li><a href='http://stackoverflow.com/questions/tagged/groovy'>Stack Overflow questions</a></li><li><a href='http://groovycommunity.com/'>Slack Community</a></li>
</ul>
</div><div class='col-right'>
<p>
The Groovy programming language is supported by the <a href='http://www.apache.org'>Apache Software Foundation</a> and the Groovy community.
</p><div text-align='right'>
<img src='../img/asf_logo.png' title='The Apache Software Foundation' alt='The Apache Software Foundation' style='width:60%'/>
</div><p>Apache&reg; and the Apache feather logo are either registered trademarks or trademarks of The Apache Software Foundation.</p>
</div>
</div><div class='clearfix'>&copy; 2003-2023 the Apache Groovy project &mdash; Groovy is Open Source: <a href='http://www.apache.org/licenses/LICENSE-2.0.html' alt='Apache 2 License'>license</a>, <a href='https://privacy.apache.org/policies/privacy-policy-public.html'>privacy policy</a>.</div>
</div>
</footer></div>
</div>
</div>
</div>
</div><script src='../js/vendor/jquery-1.10.2.min.js' defer></script><script src='../js/vendor/classie.js' defer></script><script src='../js/vendor/bootstrap.js' defer></script><script src='../js/vendor/sidebarEffects.js' defer></script><script src='../js/vendor/modernizr-2.6.2.min.js' defer></script><script src='../js/plugins.js' defer></script><script src='https://cdnjs.cloudflare.com/ajax/libs/prettify/r298/prettify.min.js'></script><script>document.addEventListener('DOMContentLoaded',prettyPrint)</script><script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-257558-10', 'auto');
ga('send', 'pageview');
</script>
</body></html>