Lisplog Blogging in Lisp en Bill St. Clair Lisplog Quote <p>"Micro-optimization is usually premature. Macro-optimization is essential." -- Bill St. Clair</p> Fri, 21 Feb 2020 15:09:44 GMT Why Elm? <p>An AngularJS user in the Fediverse asked me to sell Elm to him. This is my first attempt.</p> <p>I have never used AngularJS or any other JS framework besides jQuery, so I'm operating from a quick perusal of the <a href=''>Angular web site</a>, making my comparisons mostly invalid.</p> <h3>Caveats</h3> <p>First off, you're going to miss a lot of Angular's features with Elm. Elm has a functional flavor to it, not the templatey feel of Angular. Your entire site, except for the initial HTML file that loads the elm-generated JS, is Elm. And Elm wants total control. With a couple of exceptions:</p> <ol> <li>You may load Elm into only a single DIV of your larger site, and use regular HTML/JS for the rest of it. This, so I've read, can enable incremental conversion to Elm, but I've never done it.</li> <li>Elm supports two ways to get to your own JavaScript code: <ol style='list-style-type: lower-alpha; margin-top: 1em; padding-left: 1em;'> <li>Ports - a send/receive messaging mechanism to get JS data out of and back into Elm. Fully type-safe, since incoming JS data goes through decoders to bring it into Elm, and errors there must be handled by the functional Elm code.</li> <li>Custom HTML Elements - Since Elm's virtual DOM can support ANY HTML element, you can roll your own, to do anything you can dream up.</li> </ol> </ol> <p>Elm's package manager is centered on open source, stored at GitHub. I've proposed a simple mechanism to open this up, so that you can easily define your own package plugins, or not use Microsoft's repository system, but Evan Czaplicki, Elm's creator, has not been open to the idea.</p> <p>A normal way around this for organizations that need their code to be proprietary is a mono-repo for your code, and just include ths whole thing in every project. Elm's compiler is so fast, and so good at rebuilding only what needs to be rebuilt, and including in the output only what is actually used, that this isn't as bad a solution as it sounds.</p> <p>Or, if you have Haskell chops, you could fork the Elm source, and add the package manager plugin yourself.</p> <h3>Advantages</h3> <p>Now that I've highlighted some caveats, on to Elm's advantages.</p> <p>No run-time errors. Ever. If it compiles, the JS generated by the Elm compiler will contain logic errors that you need to find and fix, but it will NEVER mistakenly reference a null value or call an undefined function. This is remarkable to experience, for one accustomed to the runtime-error-prone JS environment. And it really works.</p> <p>The same kind of type-checking that C programmers have had for decades, but without the necessity to say everything twice, and with a much friendlier compiler, and no way to get around the type safety (except in your own custom JS, as mentioned above). JS linters may give you a lot of this, but they don't do a complete job. The Elm compiler does.</p> <p>As a long-time lisper, accustomed to run-time type checking, but compile-time laxity, I was surprised to find that I really like this. It takes a little longer to get your code to compile, but once you do, you know it won't fail because of a runtime error.</p> <p>Refactorings, even large refactorings, are a joy, and usually "just work" once you get the refactored code to compile. In Elm, I'm not afraid to make radical changes to pieces of my code. This causes a large list of compiler errors early in the process, but you fix them one at a time until it builds, and then it usually works.</p> <p>The generated JavaScript is not difficult to read, and is pretty good, performant code. You definitely lost a little in efficiency over hand-coded JS, but you can always move inner loops into ports if necessary, and it is rarely necessary.</p> <p>Elm webapps behave pretty much like native apps. With all the JS frameworks available, this isn't as much of a sell as it used to be.</p> <h3>Conclusion</h3> <p>I wish I knew how to express my sheer <i><b>joy</b></i> when I program in Elm. If I had Shakespeare's skill, I'd write a sonnet. "Shall I compare thee to a summer's day?"</p> Fri, 19 Jul 2019 10:28:57 GMT OAuth2 and Mastodon <p>This page explains my thinking of doing <a href=''>OAuth2</a> authorization for an <a href=''>Elm</a> web frontend talking to a <a href=''>Mastodon</a> API backend.</p> <h3>The OAuth2 Authorization Code Grant Flow Dance</h3> <p>OAuth2 has a number of authentication methods. For end users, using web apps, the Authorization Code Grant Flow is common. It lets the user authenticate with the service she's using, such that the client code never sees her userid or password, then the client code fetches a token it can use to authenticate API requests. If the user notices the client misbehaving, she can go to the service's web site, and remove permission for that client to access her account.</p> <p>There are three computers involved:</p> <ol> <li>The user's computer, often via a web browser.</li> <li>The client application's web server, accessed via the <code>redirectUri</code>.</li> <li>The API service's web server, accessed via the <code>authorizationUri</code>, the <code>tokenUri</code>, and the <code>apiUri</code>.</li> </ol> <p>Examples:</p> <table style='margin-left: 3em;'> <tr><th>redirectUri: </th><td></td></tr> <tr><th>authorizationUri: </th><td></td></tr> <tr><th>tokenUri: </th><td></td></tr> <tr><th>apiUri: </th><td></td></tr> </table> <p>Mastodon requires <code>/oauth/authorize</code> and <code>/oauth/token</code> as the <a href=''>OAuth 2 endpoints</a> and <code>/api/v1</code> as the base of the <a href=''>REST API</a> URLs.</p> <p>There is some information that the API service uses to identify the client application:</p> <ol> <li>The application name</li> <li>The application Url (optional)</li> <li>The <code>clientId</code></li> <li>The <code>clientSecret</code></li> </ol> <p>The <code>clientId</code> can be present in the webapp, on the users's computer, since without the <code>clientSecret</code>, it cannot be used for anything. The <code>clientSecret</code> is kept secret, on the redirect server.</p> <p>Steps in the dance: <ol> <li>The user clicks a "Login" button in her web browser.</li> <li>The webapp redirects to the <code>authorizationUri</code>, passing the <code>clientId</code>, the <code>redirectUri</code>, <code>scope</code> descriptors, saying what the client app will be allowed to do, and a <code>state</code> string.</li> <li>The <code>authorizationUri</code> puts up a form, requesting userid and password.</li> <li>If the user logs in successfully, the <code>authorizationUri</code> server forwards the user's browser to the <code>redirectUri</code>, passing the <code>clientId</code>, <code>state</code>, and an authentication <code>code</code> that it generates and remembers. <li>The <code>redirectUri</code> server posts the <code>clientId</code>, <code>clientSecret</code>, and authentication <code>code</code> to the <code>tokenUri</code>, and receives back a <code>token</code>.</li> <li>The <code>redirectUri</code> server then uses the state to put up a web page for the user to interact with the server via the API.</li> <li>When the user does something that requires an API call, the <code>token</code> is passed along, for authentication and identification of the user.</li> </ol></p> <h3>The Status Quo</h3> <p>First, I'll explain from where I started, back in December of 2017, before adding Mastodon to the mix.</p> <p>I published the <a href=''>billstclair/elm-oauth-middleware</a> in the public Elm repository. It works with Google, Facebook, GitHub, Gab Legacy, and likely any other proper implementation of the OAuth2 Authorization Code Grant Flow (but I only tested those four). It is running at <code></code>. It contains both server and client code.</p> <p>The elm-oauth-middleware server expects application definitions to be mostly static, with the <code>tokenUri</code>, <code>clientId</code>, and <code>clientSecret</code> stored in a JSON file on the server, which is queried periodically, and reloaded if it changes. This allows hot changes to the applications, with a text editor on the server.</p> <p>I store the <code>clientId</code>, and <code>redirectUri</code> in the Elm client code, compiled to JavaScript in the browser, again loaded from a JSON file that ships with the client application. It redirects to the <code>authorizationUri</code>, passing the <code>clientId</code>, <code>redirectUri</code>, <code>scope</code>, and some <code>state</code>. The authorization server (Google, Facebook, GitHub, Gab, etc.) prompts the user for ID and password, and redirects to the <code>redirectUri</code> with an authorization <code>code</code> and some Base64 encoded JSON state. That server posts the <code>code</code> to the <code>tokenUri</code> to get a token, which it returns to the client code, by using the <code>state</code> to go to a URL on a <code>redirectBackHost</code> that are validated from its configuration file. Validation of that <code>redirectBackHost</code> is my invention.</p> <p>This is a non-standard use of the Authorization Code Grant Flow. Usually, the token stays on the server, and it uses it to make API calls and then populate HTML for the client browser. Since my clients are all in Elm, and work with no HTML generation by a server, other than an initial static HTML file that loads the Elm JavaScript, that client needs to have the token, and make calls itself to the <code>apiUri</code>. The client typically stores the token in JavaScript <code>localStorage</code>, so that it doesn't have to request it again every time the user goes to its web site.</p> <h3>Enter Mastodon</h3> <p>Normal Mastodon servers have a <a href=''>Your Applications</a> page, linked from <code>&lt;/&gt;&nbsp;Development</code> in the left column of the <a href=''>preferences</a> page. This allows you to create a standard, static, OAuth2 application, giving an <code>Application name</code>, <code>Application website</code>, <code>Redirect URI</code> list, and allowed <code>Scopes</code>, and receiving a <code>clientId</code>, <code>clientSecret</code>, and <code>token</code>.</p> <p>This is fine if your application is targeted to a small set of Mastodon servers, but the nature of Mastodon is hundreds of federated servers, so the API has a <a href=''><code>POST&nbsp;/api/v1/apps</code></a> call to create a new <code>client_id</code> and <code>client_secret</code>.</p> <p>My idea for using this is to have the Elm app send its own URL as the Redirect URI, use <code>POST&nbsp;/api/v1/apps</code> to get a client ID and secret, then redirect to <code>https://&lt;mastodon-host&gt;/oauth/authorize</code>, so the user can log in, get back the authorization code when restarted as the <code>redirectUri</code>, then <code>POST</code> to <code>https://&lt;mastodon-host&gt;/oauth/token</code> to turn that code into a token. The only thing I don't now yet is whether that final POST will pass <a hrerf=''>CORS</a> muster. The API calls have to, but that one doesn't. If it doesn't, then that part of the dance needs to be moved to a server, with the <code>clientId</code> and <code>clientSecret</code> passed in the state, so that the server doesn't need any state itself.</p> <p>This is likely an unusual use of the <code>POST&nbsp;/api/v1/apps</code> call. I think it's expected that the <code>redirectUri</code> host will save the association of the <code>&lt;mastodon-host&gt;</code> and a <code>clientId/clientSecret</code> pair, so that it doesn't need to request a new one unless a new user specifies a never-before-seen server. I plan to cache the <code>clientId/clientSecret</code> pair (and the most recently issued <code>token</code>) in <code>localStorage</code> on the user's machine, but store no state on the server, even if I need one to get around CORS.</p> Mon, 01 Jul 2019 22:15:18 GMT ZAP Meme <p> <a href=''></a> is a meme maker written in Elm.</p> <p>It's open source, with a <a href=''>link</a> at the bottom of the page. It uses <code>&lt;svg></code> to layout the meme, and converts to JPEG or PNG for saving.</p> <p>It stores meme and images (as <code>data://</code> URLs) in your browser's <code>localStorage</code> database. They may be exported as JSON and imported into another browser.</p> <p style='text-align: center;'><img src='./images/theres-a-bear-in-my-coffee.jpg' width='499' height='696' alt='kludges'/></p> Thu, 18 Apr 2019 05:25:32 GMT stackoverflow April Fools 2019 <div class='js-cursor-container'> <p>For April Fools Day, 2019, <a href=''>stackoverflow</a> put fairy dust on every question page, and had a definite retro look.</p> <p>I saved the fairy dust, and the ASCII art HTML comment at <a href='20190401/'></a>.</p> <p>Also see <a href=''>Announcing the Stack Overflow Time Machine</a>.</p> <script type='text/javascript' src='20190401/fairy-dust.js'></script> </div> Mon, 01 Apr 2019 07:57:03 GMT Image Sizing in elm-ui <p>I've been using Matthew Griffith's wonderful <a href=''>elm-ui</a> package to make the user interface for <a href=''>GabDecker</a>, a TweetDeck-like web app I'm building for <a href=''></a>. I've had inline images since the beginning, scaled to fit the column width. Today I made clicking on one of those image open a dialog with the full-size version, scaled down if necessary to fit the available space.</p> <p><code>elm-ui</code> eschews CSS in the source code, usually making it much easier to get what you want. But in this case, I was NOT getting what I wanted, so I wrote my own CSS, and learned about the <code>object-fit</code> property in the process.</p> <p>Here's the code that's now running:<br/> <blockquote><pre>imageDialog : String -> Model -> Element Msg imageDialog url model = let maxw = 9 * model.windowWidth // 10 maxws = String.fromInt maxw ++ "px" maxh = 9 * model.windowHeight // 10 maxhs = String.fromInt (9 * model.windowHeight // 10) ++ "px" in column -- This is black magic. -- It took much play with the CSS to get it right. [ centerX , centerY ] [ standardButton "" CloseDialog &lt;| (Html.img [ "object-fit" "contain" , "max-width" maxws , "max-height" maxhs , "border" "2px solid black" , "width" "auto" , "height" "auto" , Attributes.src url ] [] |> Element.html ) ] </pre></blockquote> Thu, 06 Dec 2018 03:53:36 GMT Quote <p>"APL is like a diamond. It has a beautiful crystal structure; all of its parts are related in a uniform and elegant way. But if you try to extend this structure in any way - even by adding another diamond - you get an ugly kludge. LISP, on the other hand, is like a ball of mud. You can add any amount of mud to it and it still looks like a ball of mud." -- Joel Moses</p> Wed, 19 Sep 2018 10:04:00 GMT How do I run elm-test in Elm 0.19? <p>This is a FAQ, so I'm making a blog post to answer it.</p> <p><code>elm-test</code> is still in beta for Elm 0.19. To install it:</p> <blockquote><pre>$ npm install -g elm-test@beta</pre></blockquote> <p>The <code>tests</code> directory no longer needs an <code>elm-package.json</code> file (nor does it need <code>elm.json</code>).</p> <p>Your top-level <code>elm.json</code> file needs to include the following:</p> <blockquote><pre> "test-dependencies": { "elm-explorations/test": "1.0.0 <= v < 2.0.0" }</pre></blockquote> <p>The easiest way to get it there is with <code>elm-test</code> itself. The following will create a <code>tests</code> directory, containing an example test file, <code>Example.elm</code>, and will add <code>test-dependencies</code> to <code>elm.json</code>.</p> <blockquote><pre>$ elm-test init</pre></blockquote> <p>That's it. There is not yet support for running <code>elm-test</code> in the browser, nor is there support for testing DOM output.</p> <p><a href=''>billstclair/elm-localstorage</a> is configured with a <code>tests</code> directory to test its JSON encoders and decoders: <a href=''></a>. To run the tests:</p> <blockquote><pre>$ git clone $ cd elm-localstorage $ elm-test</pre></blockquote> Mon, 17 Sep 2018 00:24:33 GMT CLtL2 on Kludges <p>Lest we believe we're building amazing systems as programmers, always remember that <i>Common Lisp the Language, Second Edition</i>, by Guy L. Steele, Jr. (CLtL2), has an index entry for "kludges", saying that they appear on pages 1 to 971. Page 972 is the "References" section.</p> <p style='text-align: center;'><img src='./images/cltl2-kludges.jpg' width='499' height='696' alt='kludges'/></p> Sat, 15 Sep 2018 11:26:58 GMT iPhone Xs Pricing <style> table.iphonetable { margin: 0em 0.5em 0.5em 0.5em; background: whitesmoke; border-collapse: collapse; font-size: 100%; } table.iphonetable th, table.iphonetable td { border: 1px silver solid; padding: 0.2em; } table.iphonetable th { background: gainsboro; text-align: center; } table.iphonetable td { text-align: right; } .r { text-align: right; } </style> <p>From the <a href=''></a> pages.</p> <p>Watch the <a href=''>12 September keynote</a>.</p> <p>Prices (in parentheses) are per month for Apple financing.</p> <table class='iphonetable'> <tr> <th>Model</th><th>64 gig</th><th>128 gig</th><th>256 gig</th><th>512 gig</th><th>Order</th><th>Ship</th> </tr><tr> <th style='text-align: right;'>iPhone Xs:</th><td>$999<br/>($49.91)</td><td>&nbsp;</td><td>$1149<br/>($56.16)</td><td>$1349<br/>($64.50)</td><td rowspan='2'>9/14</td><td rowspan='2'>9/21</td> </tr><tr> <th style='text-align: right;'>Xs Max:</th><td>$1099<br/>($54.08)</td><td>&nbsp;</td><td>$1249<br/>($60.33)</td><td>$1449<br/>($68.66)</td> </tr><tr> <th style='text-align: right;'>Xr:</th><td>$749<br/>($37.41)</td><td>$799<br/>($39.50)</td><td>$899<br/>($43.66)</td><td>&nbsp;</td><td>10/19</td><td>10/26</td> </tr><tr> <th style='text-align: right;'>iOS 12:</th><td colspan='5' style='text-align: center;'>Free</td><td>9/17</td> </tr><tr> <th style='text-align: right;'>macOS<br/>Mojave</th><td colspan='5' style='text-align: center;'>Free</td><td>9/24</td> </tr> </table> Wed, 12 Sep 2018 19:22:55 GMT