tag:blogger.com,1999:blog-78725882980950209912024-03-05T15:27:19.824+01:00Los pájaros de hogañoátoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.comBlogger152125tag:blogger.com,1999:blog-7872588298095020991.post-8841489686060130922023-08-07T19:42:00.007+02:002023-08-08T08:46:00.895+02:00Obra reunida<p>Aprovechando el período vacacional, cuando hay algo más de tiempo libre para ese tipo de tareas cuya realización siempre se posterga, he decidido crear una página que recopile la mayor parte de mis trabajos, tanto los inéditos como lo ya publicados con mi nombre o bajo el seudónimo <em>átopos</em>. Está alojada como una <em>página</em> de GitHub:</p>
<p style="text-align:center"><a href="https://luissanjuan.github.io/ObraReunidaLSP/">Obra reunida</a></p>
<p>La tarea es engorrosa, incluso molesta. Para empezar, muchos de estos trabajos fueron escritos antes de mi conversión a LaTeX u otros formatos abiertos y permanecían perdidos en algún dispositivo de almacenamiento obsoleto o su copia impresa arrumbada en algún rincón olvidado. Pero, sobre todo, tengo que vencer mi resistencia a convertirme en el «prota» de la historia —eso de anunciarse a sí mismo no suena demasiado bien—. No obstante, viendo «cómo se pasa la vida» —complete el lector los otros dos versos de Manrique— parece razonable —«lógico» diría un <em>vulcano</em>— tener la casa limpia y ordenada; para uno mismo, en primer lugar —siempre es mejor tener las cosas propias bien a la mano—, y, sobre todo, para los más allegados, o incluso para quien quiera que, aun desconocido, haya seguido alguno de estos derroteros.</p>
<p>En la versión inicial de la página, mantengo, sin alteración ni en contenido ni en forma, el resultado publicado en otros lugares, de los cuales se da cumplida noticia en la descripción del documento que corresponde. Las únicas y mínimas excepciones son, en primer lugar, <em>Reseña de LaTeX para las Humanidades</em> y <em>El qué y el porqué de LaTeX</em> que aquí aparecen sin la maquetación que requería su publicación en la revista TeXemplares y, en segundo lugar, <em>LaTeX para las Humanidades</em>, donde el email de las página primera ha sido tachado, habida cuenta de que dejó de funcionar hace unos cuantos años.</p>
<p>Por lo que respecta a los tres textos hasta ahora inéditos que se presentan en esta versión inicial, <em>El brote</em> y <em>Música y poesía</em> son copias inalteradas del pdf original. Por su parte, <em>Veinticuatro poemas</em> reúne aquellos que puse en este blog a lo largo de los últimos años, y quizá alguno más. El pdf final lo he producido con ocasión de esta publicación.</p>
<p>El resto de documentos, que aparece etiquetado como «en preparación», es también esencialmente inédito pero requiere de transcripciones más o menos completas del texto original a los formatos actuales, algo que procuraré ir haciendo a medida que las circunstancias y el ajetreo cotidiano lo permitan.</p>
<p id="nota1">Como no podía ser menos, el diseño sobrio de la página, muy lejos de las convenciones actuales, emula un documento de LaTeX gracias al magnífico estilo <a href="https://latex.now.sh/">LaTeX.css</a>.átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-63831693236709423382021-05-14T15:29:00.002+02:002021-05-14T16:44:02.328+02:00Un ejemplo de uso de la programación informática en el trabajo del aula<p>Para bien o para mal ---tengo mi opinión al respecto, que no viene al caso---, la cantidad de tareas administrativas o burocráticas que los docentes tienen que realizar anualmente es mucho mayor de lo que la gente que no está en esta profesión podría imaginar. Es durante los preliminares y finales de los cursos cuando estas obligaciones aumentan hasta el punto de llegar a ser extenuantes.</p>
<p>Todo lo que sea reducir la carga de trabajo mecánico en las actividades de este tipo redunda en beneficio de la enseñanza, porque permite concentrar esfuerzos y tiempo en los aspectos creativos de la docencia.</p>
<p>Un ejemplo son los exámenes. En mi centro de trabajo se trata de pruebas de acceso que deben ser evaluadas siguiendo unos determinados protocolos. Se trata básicamente de aplicar en la examen los criterios de evaluación de la programación del curso al que el aspirante solicita el acceso con el fin de determinar su grado de competencia. Hoy en día, este tipo de evaluaciones se realiza mediante rúbricas que miden el nivel del aspirante en relación con los contenidos evaluables del curso correspondiente.</p>
<p>Lo que esto supone administrativamente para el profesor que examina o el departamento al que pertenece es como mínimo lo siguiente:</p>
<ol>
<li>Crear rúbricas para cada uno de los exámenes.</li>
<li>Rellenar dichas rúbricas durante el examen.</li>
<li>Calcular <i>in situ</i> los resultados de la evaluación con las ponderaciones que correspondan.</li>
<li>Generar un informe con los resultados obtenidos.</li>
</ol>
<p>Hasta ahora estas operaciones se vienen realizando en la mayor parte de los centros a mano. Las rúbricas, previamente elaboradas con un procesador de textos por los jefes de departamento a partir de la programación actual, se marcan durante el examen. Las calificaciones parciales y finales se calculan tras el término del examen, ya sea con papel y lápiz, con calculadora o, en el más sofistacado de los casos (infrecuente, sin duda) mediante una hoja de cálculo creada <i>ex profeso</i> para cada rúbrica. Finalmente, el informe de los resultados se rellena a mano sobre modelos creados con procesadores de texto.</p>
<p>Es demasiado trabajo innecesario, con varios pasos intermedios donde los errores pueden colarse inadvertidamente. Me pareció que era hora de automatizar el proceso en alguna medida.</p>
<p>Mi primer objetivo fue resolver las fases 2 y 3 de la lista anterior de tareas necesarias.</p>
<p>La actividad de cumplimentación de las rúbricas y realización de los cálculos de la evaluación, debería poder realizarse con un dispostivo de fácil acceso en el lugar del examen, digamos, un teléfono móvil. La tecnología que inmediatamente se presenta como la ideal es la de una página web estática, esto es, que no requiere acceso a servidores web, pero diseñada con la capacidad de adaptarse dinámicamente a cada situación concreta. Una solución que debe implementarse mediante HTML, CSS y JavaScript. El profesor accede a esa página previamente almacenada en su móvil, rellena los formularios que corresponden a las rúbricas del examen y la página computa automáticamente los resultados adecuadamente ponderados. Enviar el formulario significa, en este contexto (donde no se envían a un servidor web), guardar las anotaciones para su posterior procesamiento.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2VnGsh4iHNmjjJTfqMLcvjPDS65YAiQX0diReFu1-Hqju8plVz2JRLmV1Yw9RINitt8BO2ZRCNvdJ0bQjVjmLOzi9UBrVJIkz4mYXVkFeUQLeNlMt9zS_OJU0F0qPj-xQ-Nk6QzNR4vE/s1852/rubricas.png" style="display: block; padding: 1em 0; text-align: center;"><img alt="" border="0" width="320" data-original-height="1048" data-original-width="1852" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2VnGsh4iHNmjjJTfqMLcvjPDS65YAiQX0diReFu1-Hqju8plVz2JRLmV1Yw9RINitt8BO2ZRCNvdJ0bQjVjmLOzi9UBrVJIkz4mYXVkFeUQLeNlMt9zS_OJU0F0qPj-xQ-Nk6QzNR4vE/s320/rubricas.png"/></a></div>
<p>La generación del informe con los resultados del examen puede resolverse mediante un programa que transforme el resultado guardado previamente a través de la interfaz web en un documento pdf. Para ello, una buena opción es echar mano de un lenguaje de propósito general que proceda a analizar (<i>parsing</i>) el resultado y produzca una cadena de texto o fichero fácilmente convertible a pdf.
Mi solución concreta ha sido utilizar el lenguaje de programación <i>Racket</i> para convertir el resultado guardado desde la interfaz web como fichero <code>json</code> a <code>markdown</code>, que luego el programa <i>Pandoc</i> se encarga de transformar directamente a <code>pdf</code>.</p>
<p>Crear una página web estática para cada tipo de examen es también demasiado repetitivo y mecánico. Una solución es especificar examenes con un determinado formato, idealmente uno que pueda entender con facilidad cualquier profesor o jefe de departamento. Una especificación de esta clase, más sencilla incluso que markdown, ya que apenas etiqueta el texto y usa sólo los espacios de indentación para marcar las secciones, podría tener el siguiente aspecto:</p>
<pre>
<code>
Título del examen
Título de rúbrica 1|Ratio
Titulo de la Dimensión 1
Nivel 1
Nivel 2
...
Título de la Dimension 2
Nivel 1
Nivel 2
...
...
Título de rúbrica 2|Ratio
...
</code>
</pre>
<p>Una vez establecido un formato como el anterior, podemos diseñar un programa que transforme ficheros con esta estructura a una página <code>html</code> que se atenga a la forma concreta que espera la interfaz web diseñada previamente. De nuevo, he usado <i>Racket</i> para implementar dicha operación.</p>
<p>Finalmente, es posible también automatizar, al menos en alguna medida, la propia generación de rúbricas a partir de la programación de la asignatura. La única condición necesaria es que la programación sea fácilmente procesable. Afortunadamente, la programación de mi asignatura está escrita en <i>Markdown</i> y sus diferentes secciones siguen un esquema muy formalizado de exposición. Cumplida esta condición, es relativamente sencillo diseñar un programa semejante a los anteriormente descritos que transforme los contenidos evaluables de la programación en un examen con el formato indicado más arriba.</p>
<p>Todo este grupo de pequeños programas puede más tarde agruparse para una ejecución secuencial a través, por ejemplo, de un script del shell.</p>
<p>Acabaríamos con un diseño, basado en sucesivas transformaciones del mismo contenido, ejecutadas por transformadores escritos en distintos lenguajes de programación:</p>
<pre>
formato lenguaje del transformador/aplicación
------- -------------------------------------
Programación
| markdown Racket
|
|
Examen/Rúbricas
| txt Racket
|
|
PáginaWeb html JavaScript
|
|
|
Resultado json Racket
|
|
|
Informe markdown Pandoc
|
|
|
Informe pdf
</pre>
<p>Es probable que en el futuro inmediato, cuando se pase de la simple alfabetización en nuevas tecnologías a la impartición real de materias especialidades en este ámbito, cualquier profesional de nivel universitario tenga los suficientes conocimientos técnicos en programación como para diseñar sus propios programas exactamente a la medida de sus necesidades, demanda que muy difícilmente las aplicaciones de propósito general pueden cubrir. En ese momento, toda la cantidad de trabajo innecesario o monótono que realizan estos profesionales podrán realizarlos las máquinas de acuerdo con lo que su diseño indique.</p>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-64189385515286450242019-08-05T12:55:00.003+02:002019-08-05T13:02:15.698+02:00From HtDP to Racket. New version<p>I have completely rewritten <em>From HtDP to Racket</em> based on a new approach that gives, I hope, more opportunity to introduce Racket. The new version is on its own git repo:</p>
<p><a href="https://gitlab.com/lsanjuan/from-htdp-to-racket-devel">From HtDP to Racket</a></p>
<p>This repo contains the documentation, which is more detailed than in previous posts. It is available under the <a href="https://gitlab.com/lsanjuan/from-htdp-to-racket-devel/tree/master/doc"><code>doc/</code> directory</a> within the repo. The main <em>html</em> doc is <code>htdp2racket.html</code>.</p>
<p>If you don't want to fork the repo, you can still look at the documentation by downloading the doc directory and opening the html file referred above on your web browser.</p>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-81992124794130191102019-04-17T18:55:00.003+02:002019-04-27T09:02:52.619+02:00From HtDP to Racket. Racket (6): exceptions, http requests, json<div id="toc" style="font-size:90%">
<p><em>TOC of the series</em></p>
<ol style="list-style-type: decimal">
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in.html">ISBN Extraction in ISL+ with regular expressions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_17.html">Racket: <code>#lang</code>, local definitions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_70.html">Racket: <code>only-in</code>, <code>rackunit</code>, test submodules</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction.html">Racket: <code>for</code>, <code>match</code>, sequences</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_17.html">Racket: <code>provide</code>, contracts</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_24.html">Racket: I/O, optional and keyword arguments, point-free style</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-addons.html">Racket: exception handling, http requests, JSON</a></li>
</ol>
</div>
</hr>
<p>I finish this series with a couple of new modules (each on its own file) that minimally accomplish the rest of the sub-tasks mentioned in the first post of the series.</p>
<p>Let's recall those tasks this time with a reference to the responsible module.</p>
<ol style="list-style-type: decimal">
<li>Read the text contained in the pdf pages</li>
<li>Search for the ISBN on those pages</li>
<li>Make a query for the ISBN to a remote server that provides information about books.</li>
<li>Parse the response of the server to represent it as a Racket data type.</li>
</ol>
<p>The first and second tasks above are carried out by "pdf-read.rkt" with the aid of "isbn.rkt".</p>
<p>The third and forth tasks, in turn, are solved by "book-info.rkt".</p>
<p>"pdf-isbn" does the same thing as "isbn" but over pdf files. It uses the latter along with the <code>pdf-read</code> package, a Racket interface to the popular <code>libpoppler</code> library, available by default on Linux and, to my knowledge, on MacOS systems. You probably need to install <code>pdf-read</code> before running the code. Don't worry, installing packages from DrRacket is very easy and self-explanatory.</p>
<p>As for relevant Racket constructs used in "pdf-read" it is worth to mention <code>error</code>, that raises an exception when the pdf file doesn't exist. For testing exceptions you should use <code>check-exn</code> from <code>rackunit</code>.</p>
<p>Another interesting divergence between Racket and *SL languages has to do with <code>or</code> and conditionals in general. In Racket everything there except<code>#false</code> is treated as <code>#true</code>. This fact allows us to use something like:</p>
<pre><code>(or (extract/format 'isbn-13) (extract/format 'isbn-10)))</code></pre>
<p>The module "book-info" requests information to a remote provider (via functions belonging to the <code>net/url</code> package) to get bibliographic information about a book and parses that information in two phases: 1. parses the JSON response via <code>read-json</code> (from the <code>json</code> package), and 2. parses the output of <code>read-json</code> into the structure <code>book-info</code>. For the second phase I have resorted to the package <code>json-pointer</code> that you may need to install on your system.</p>
<p>Note that only the Open Library provider is supported. Other providers like Google Books, The Library of Congress, etc. could be also supported in a similar way.</p>
<p>A few new frequently used Racket constructs applied by "book-info" are <code>define-values</code>, which allows to bind multiple identifiers at once, and <code>with-handlers</code>, that allows to handle an exception as wished.</p>
</br>
<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; pdf-isbn.rkt</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #008800; font-weight: bold">#lang racket/base</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/contract</span>)
<span style="color: #888888">; ----------------------------------------------------------</span>
(<span style="color: #0066BB; font-weight: bold">provide</span>
(<span style="color: #0066BB; font-weight: bold">contract-out</span>
<span style="color: #888888">; extracts all isbn's from the given pdf document</span>
<span style="color: #888888">; raises exception when the pdf file does not exist</span>
[<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf/list</span> (<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">pdf-document?</span> (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">isbn?</span>))]
<span style="color: #888888">; extracts the first isbn from the given pdf document, if any</span>
<span style="color: #888888">; favors isbn-13 over isbn-10</span>
<span style="color: #888888">; raises exception when the pdf file does not exist</span>
[<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf</span> (<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">pdf-document?</span> (<span style="color: #0066BB; font-weight: bold">or/c</span> <span style="color: #996633">isbn?</span> <span style="color: #003366; font-weight: bold">#f</span>))]))
<span style="color: #888888">; ----------------------------------------------------------</span>
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/function</span>
<span style="color: #996633">curry</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/port</span>
<span style="color: #996633">call-with-input-string</span>))
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">pdf-read</span>)
(<span style="color: #008800; font-weight: bold">require </span><span style="background-color: #fff0f0">"isbn.rkt"</span>)
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">rackunit</span>)
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/function</span>
<span style="color: #996633">thunk</span>))
<span style="color: #888888">; examples for tests</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pdf-with-isbn</span> <span style="background-color: #fff0f0">"test-isbn-examples.pdf"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pdf-with-isbn-10-only</span> <span style="background-color: #fff0f0">"test-with-isbn-10-only.pdf"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pdf-without-isbn</span> <span style="background-color: #fff0f0">"test-without-isbn.pdf"</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; PDF-Document -> [List-of ISBN]</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-exn</span> <span style="color: #996633">exn:fail?</span> (<span style="color: #0066BB; font-weight: bold">thunk</span> (<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf</span> <span style="background-color: #fff0f0">"hi.pdf"</span>)))
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf/list</span> <span style="color: #996633">pdf-without-isbn</span>)
<span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf/list</span> <span style="color: #996633">pdf-with-isbn</span>)
(<span style="color: #007020">list </span><span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"0201896834"</span>
<span style="background-color: #fff0f0">"9780201896831"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"9780201896831"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf/list</span> <span style="color: #996633">f</span>)
(<span style="color: #0066BB; font-weight: bold">check-input</span> <span style="color: #996633">f</span> <span style="color: #AA6600">'extract-isbn-from-pdf/list</span>)
(<span style="color: #0066BB; font-weight: bold">for/fold</span> ([<span style="color: #0066BB; font-weight: bold">isbns</span> <span style="color: #333333">'</span>()])
([<span style="color: #0066BB; font-weight: bold">pg</span><span style="color: #333333">#</span> (<span style="color: #0066BB; font-weight: bold">in-range</span> (<span style="color: #0066BB; font-weight: bold">pdf-count-pages</span> <span style="color: #996633">f</span>))])
(<span style="color: #007020">append </span><span style="color: #996633">isbns</span>
(<span style="color: #0066BB; font-weight: bold">call-with-input-string</span> (<span style="color: #0066BB; font-weight: bold">page-text</span> (<span style="color: #0066BB; font-weight: bold">pdf-page</span> <span style="color: #996633">f</span> <span style="color: #996633">pg</span><span style="color: #333333">#</span>))
<span style="color: #996633">isbn-find/list</span>))))
<span style="color: #888888">; PDF-Document -> [Maybe ISBN]</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-exn</span> <span style="color: #996633">exn:fail?</span> (<span style="color: #0066BB; font-weight: bold">thunk</span> (<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf</span> <span style="background-color: #fff0f0">"hi.pdf"</span>)))
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf</span> <span style="color: #996633">pdf-with-isbn</span>)
<span style="background-color: #fff0f0">"9781593274917"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf</span> <span style="color: #996633">pdf-with-isbn-10-only</span>)
<span style="background-color: #fff0f0">"0262062186"</span>)
(<span style="color: #0066BB; font-weight: bold">check-false</span>
(<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf</span> <span style="color: #996633">pdf-without-isbn</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">extract-isbn-from-pdf</span> <span style="color: #996633">f</span>)
(<span style="color: #0066BB; font-weight: bold">check-input</span> <span style="color: #996633">f</span> <span style="color: #AA6600">'extract-isbn-from-pdf</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">extract/format</span> <span style="color: #996633">format</span>)
(<span style="color: #0066BB; font-weight: bold">for/or</span> ([<span style="color: #0066BB; font-weight: bold">pg</span><span style="color: #333333">#</span> (<span style="color: #0066BB; font-weight: bold">in-range</span> (<span style="color: #0066BB; font-weight: bold">pdf-count-pages</span> <span style="color: #996633">f</span>))])
(<span style="color: #0066BB; font-weight: bold">call-with-input-string</span> (<span style="color: #0066BB; font-weight: bold">page-text</span> (<span style="color: #0066BB; font-weight: bold">pdf-page</span> <span style="color: #996633">f</span> <span style="color: #996633">pg</span><span style="color: #333333">#</span>))
(<span style="color: #0066BB; font-weight: bold">curry</span> <span style="color: #996633">isbn-find</span> <span style="color: #008800; font-weight: bold">#:format</span> <span style="color: #996633">format</span>))))
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">extract/format</span> <span style="color: #AA6600">'isbn-13</span>) (<span style="color: #0066BB; font-weight: bold">extract/format</span> <span style="color: #AA6600">'isbn-10</span>)))
<span style="color: #888888">; PDF-Document Symbol -> PDF-Document</span>
<span style="color: #888888">; effect: report error for src if f doesn't exist</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-exn</span> <span style="color: #996633">exn:fail?</span> (<span style="color: #0066BB; font-weight: bold">thunk</span> (<span style="color: #0066BB; font-weight: bold">check-input</span> <span style="background-color: #fff0f0">"not-avail.pdf"</span>)))
(<span style="color: #0066BB; font-weight: bold">check-pred</span> <span style="color: #996633">file-exists?</span> (<span style="color: #0066BB; font-weight: bold">check-input</span> <span style="color: #996633">pdf-with-isbn</span> <span style="color: #AA6600">'fun</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">check-input</span> <span style="color: #996633">f</span> <span style="color: #996633">src</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">pdf-document?</span> <span style="color: #996633">symbol?</span> <span style="color: #996633">pdf-document?</span>)
(<span style="color: #008800; font-weight: bold">unless </span>(<span style="color: #007020">file-exists? </span><span style="color: #996633">f</span>)
(<span style="color: #007020">error </span><span style="color: #996633">src</span> <span style="background-color: #fff0f0">"~s not found"</span> <span style="color: #996633">f</span>))
<span style="color: #996633">f</span>)
</pre></div>
<p><br/></p>
<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; book-info.rkt</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #008800; font-weight: bold">#lang racket/base</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/contract</span>)
<span style="color: #888888">; ----------------------------------------------------------</span>
(<span style="color: #0066BB; font-weight: bold">provide</span>
(<span style="color: #0066BB; font-weight: bold">contract-out</span>
<span style="color: #888888">; record of book information</span>
[<span style="color: #0066BB; font-weight: bold">struct</span> <span style="color: #996633">book-info</span> ([<span style="color: #0066BB; font-weight: bold">isbn</span> <span style="color: #996633">isbn?</span>]
[<span style="color: #0066BB; font-weight: bold">authors</span> (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">string?</span>)]
[<span style="color: #0066BB; font-weight: bold">date</span> <span style="color: #996633">string?</span>]
[<span style="color: #0066BB; font-weight: bold">title</span> <span style="color: #996633">string?</span>]
[<span style="color: #0066BB; font-weight: bold">places</span> (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">string?</span>)]
[<span style="color: #0066BB; font-weight: bold">publishers</span> (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">string?</span>)])]
<span style="color: #888888">; gets information about a book of given isbn from given provider </span>
<span style="color: #888888">; produces a book-info with only the isbn filled if no info avail</span>
[<span style="color: #0066BB; font-weight: bold">book-retrieve-info</span> (<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">isbn?</span> <span style="color: #996633">provider?</span> <span style="color: #996633">book-info?</span>)]
<span style="color: #888888">; determines whether the given is a provider of book information</span>
[<span style="color: #0066BB; font-weight: bold">provider?</span> <span style="color: #996633">predicate/c</span>]))
<span style="color: #888888">; ----------------------------------------------------------</span>
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/function</span>
<span style="color: #996633">curry</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/match</span>
<span style="color: #996633">match</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/string</span>
<span style="color: #996633">string-replace</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">json</span>
<span style="color: #996633">read-json</span>
<span style="color: #996633">jsexpr?</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">json-pointer</span>
<span style="color: #996633">json-pointer?</span>
<span style="color: #996633">json-pointer-expression?</span>
<span style="color: #996633">json-pointer-value</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">net/url</span>
<span style="color: #996633">call/input-url</span>
<span style="color: #996633">get-pure-port</span>
<span style="color: #996633">string->url</span>
<span style="color: #996633">url?</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="background-color: #fff0f0">"isbn.rkt"</span>
<span style="color: #996633">isbn?</span>))
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">rackunit</span>)
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/function</span>
<span style="color: #996633">thunk</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Data Types</span>
(<span style="color: #0066BB; font-weight: bold">struct</span> <span style="color: #996633">book-info</span> [<span style="color: #0066BB; font-weight: bold">isbn</span> <span style="color: #996633">authors</span> <span style="color: #996633">date</span> <span style="color: #996633">title</span> <span style="color: #996633">places</span> <span style="color: #996633">publishers</span>]
<span style="color: #008800; font-weight: bold">#:transparent)</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">book-info-template</span>
(<span style="color: #0066BB; font-weight: bold">book-info</span> <span style="background-color: #fff0f0">""</span> <span style="color: #333333">'</span>() <span style="background-color: #fff0f0">""</span> <span style="background-color: #fff0f0">""</span> <span style="color: #333333">'</span>() <span style="color: #333333">'</span>()))
<span style="color: #888888">; Providers:</span>
<span style="color: #888888">; ol: openlibrary</span>
<span style="color: #888888">; gb: google-books (not implemented)</span>
<span style="color: #888888">; ...</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">provider?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'ol</span>) (<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'gb</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Book Info Builder</span>
<span style="color: #888888">; ISBN Provider -> Book-Info</span>
<span style="color: #888888">; TODO: test</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">book-retrieve-info</span> <span style="color: #996633">isbn</span> <span style="color: #996633">provider</span>)
(<span style="color: #008800; font-weight: bold">define-values </span>(<span style="color: #0066BB; font-weight: bold">request</span> <span style="color: #996633">reader</span> <span style="color: #996633">parser</span>)
(<span style="color: #0066BB; font-weight: bold">match</span> <span style="color: #996633">provider</span>
[<span style="color: #AA6600">'ol</span> (<span style="color: #007020">values </span>(<span style="color: #0066BB; font-weight: bold">book-query</span> <span style="color: #996633">isbn</span> <span style="color: #996633">uri-ol</span>)
<span style="color: #996633">read-json</span>
(<span style="color: #0066BB; font-weight: bold">curry</span> <span style="color: #996633">parse/ol</span> <span style="color: #996633">isbn</span>))]
[<span style="color: #0066BB; font-weight: bold">_</span> (<span style="color: #007020">error </span><span style="background-color: #fff0f0">"Not implemented"</span>)]))
(<span style="color: #0066BB; font-weight: bold">call/input-url</span> <span style="color: #996633">request</span>
<span style="color: #996633">get-pure-port</span>
(<span style="color: #0066BB; font-weight: bold">compose1</span> <span style="color: #996633">parser</span> <span style="color: #996633">reader</span>)))
<span style="color: #888888">; ISBN String -> Url</span>
<span style="color: #888888">; produces the url from given template and isbn</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">book-query</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"http://example.com?id:$$isbn$$"</span>)
(<span style="color: #0066BB; font-weight: bold">string->url</span> <span style="background-color: #fff0f0">"http://example.com?id:0262062186"</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">book-query</span> <span style="color: #996633">isbn</span> <span style="color: #996633">url</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">isbn?</span> <span style="color: #996633">string?</span> <span style="color: #996633">url?</span>)
(<span style="color: #0066BB; font-weight: bold">string->url</span> (<span style="color: #0066BB; font-weight: bold">string-replace</span> <span style="color: #996633">url</span> <span style="background-color: #fff0f0">"$$isbn$$"</span> <span style="color: #996633">isbn</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Providers</span>
<span style="color: #888888">; - Open Library (ol)</span>
<span style="color: #888888">; query template</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">uri-ol</span>
(<span style="color: #0066BB; font-weight: bold">string-append</span>
<span style="background-color: #fff0f0">"http://openlibrary.org/api/books?bibkeys=ISBN:$$isbn$$"</span>
<span style="background-color: #fff0f0">"&format=json"</span>
<span style="background-color: #fff0f0">"&jscmd=data"</span>))
<span style="color: #888888">; parser</span>
<span style="color: #888888">; ISBN JSExpr -> Book-Info</span>
<span style="color: #888888">; parses the given jsexpr to get book info from OL</span>
<span style="color: #888888">; produces a book info with only the isbn filled when OL knows</span>
<span style="color: #888888">; nothing about it</span>
<span style="color: #888888">; TODO: test</span>
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">parse/ol</span> <span style="color: #996633">isbn</span> <span style="color: #996633">jsexpr</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">isbn?</span> <span style="color: #996633">jsexpr?</span> <span style="color: #996633">book-info?</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">build-book-info</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">base-point</span>
(<span style="color: #007020">symbol->string </span>(<span style="color: #0066BB; font-weight: bold">hash-iterate-key</span> <span style="color: #996633">jsexpr</span> <span style="color: #0000DD; font-weight: bold">0</span>)))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">authors</span>
(<span style="color: #0066BB; font-weight: bold">json-pointer-value/index</span> (<span style="color: #007020">cons </span><span style="color: #996633">base-point</span> <span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"authors"</span>))
<span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"name"</span>)
<span style="color: #996633">jsexpr</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">date</span>
(<span style="color: #0066BB; font-weight: bold">json-pointer-value</span> (<span style="color: #007020">cons </span><span style="color: #996633">base-point</span> <span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"publish_date"</span>))
<span style="color: #996633">jsexpr</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">title</span>
(<span style="color: #0066BB; font-weight: bold">json-pointer-value</span> (<span style="color: #007020">cons </span><span style="color: #996633">base-point</span> <span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"title"</span>))
<span style="color: #996633">jsexpr</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">places</span>
(<span style="color: #0066BB; font-weight: bold">json-pointer-value/index</span> (<span style="color: #007020">cons </span><span style="color: #996633">base-point</span> <span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"publish_places"</span>))
<span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"name"</span>)
<span style="color: #996633">jsexpr</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">publishers</span>
(<span style="color: #0066BB; font-weight: bold">json-pointer-value/index</span> (<span style="color: #007020">cons </span><span style="color: #996633">base-point</span> <span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"publishers"</span>))
<span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"name"</span>)
<span style="color: #996633">jsexpr</span>))
(<span style="color: #0066BB; font-weight: bold">book-info</span> <span style="color: #996633">isbn</span> <span style="color: #996633">authors</span> <span style="color: #996633">date</span> <span style="color: #996633">title</span> <span style="color: #996633">places</span> <span style="color: #996633">publishers</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">build-book-info/not-avail</span>)
(<span style="color: #0066BB; font-weight: bold">struct-copy</span> <span style="color: #996633">book-info</span> <span style="color: #996633">book-info-template</span> [<span style="color: #0066BB; font-weight: bold">isbn</span> <span style="color: #996633">isbn</span>]))
(<span style="color: #0066BB; font-weight: bold">match</span> <span style="color: #996633">jsexpr</span>
[(<span style="color: #0066BB; font-weight: bold">?</span> <span style="color: #996633">hash-empty?</span>) (<span style="color: #0066BB; font-weight: bold">build-book-info/not-avail</span>)]
[<span style="color: #0066BB; font-weight: bold">_</span> (<span style="color: #0066BB; font-weight: bold">build-book-info</span>)]))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Helpers (extending json-pointer)</span>
<span style="color: #888888">; [JSON-Pointer | JSON-Pointer-Expr] JSExpr -> JSExpr</span>
<span style="color: #888888">; wrapper to produce #f when json-pointer raises an exception</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">json-pointer-value/false</span> <span style="background-color: #fff0f0">"/a"</span> (<span style="color: #007020">hash </span><span style="color: #AA6600">'a</span> <span style="color: #0000DD; font-weight: bold">1</span>))
(<span style="color: #0066BB; font-weight: bold">json-pointer-value</span> <span style="background-color: #fff0f0">"/a"</span> (<span style="color: #007020">hash </span><span style="color: #AA6600">'a</span> <span style="color: #0000DD; font-weight: bold">1</span>)))
(<span style="color: #0066BB; font-weight: bold">check-false</span>
(<span style="color: #0066BB; font-weight: bold">json-pointer-value/false</span> <span style="background-color: #fff0f0">"/a"</span> (<span style="color: #007020">hash </span><span style="color: #AA6600">'b</span> <span style="color: #0000DD; font-weight: bold">2</span>))))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">json-pointer-value/false</span> <span style="color: #996633">jp</span> <span style="color: #996633">jsexpr</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> (<span style="color: #0066BB; font-weight: bold">or/c</span> <span style="color: #996633">json-pointer?</span> <span style="color: #996633">json-pointer-expression?</span>)
<span style="color: #996633">jsexpr?</span>
<span style="color: #996633">jsexpr?</span>)
(<span style="color: #008800; font-weight: bold">with-handlers </span>([<span style="color: #0066BB; font-weight: bold">exn:fail?</span> (<span style="color: #008800; font-weight: bold">lambda </span>(<span style="color: #0066BB; font-weight: bold">e</span>) <span style="color: #003366; font-weight: bold">#f</span>)])
(<span style="color: #0066BB; font-weight: bold">json-pointer-value</span> <span style="color: #996633">jp</span> <span style="color: #996633">jsexpr</span>)))
<span style="color: #888888">; JSON-Pointer-Expr JSON-Pointer-Expr JSExpr -> JSExpr</span>
<span style="color: #888888">; gets all the values at /pre/n/post in jsexpr for all legal n's</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
<span style="color: #888888">;json example</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">jse-ex</span>
<span style="color: #333333">'#</span><span style="color: #996633">hasheq</span>((<span style="color: #0066BB; font-weight: bold">a</span>
<span style="color: #333333">.</span>
<span style="color: #333333">#</span><span style="color: #996633">hasheq</span>((<span style="color: #0066BB; font-weight: bold">b</span>
<span style="color: #333333">.</span>
(<span style="color: #333333">#</span><span style="color: #996633">hasheq</span>((<span style="color: #0066BB; font-weight: bold">x</span> <span style="color: #333333">.</span> <span style="color: #0000DD; font-weight: bold">1</span>) (<span style="color: #0066BB; font-weight: bold">y</span> <span style="color: #333333">.</span> <span style="color: #0000DD; font-weight: bold">2</span>))
<span style="color: #333333">#</span><span style="color: #996633">hasheq</span>((<span style="color: #0066BB; font-weight: bold">x</span> <span style="color: #333333">.</span> <span style="color: #0000DD; font-weight: bold">3</span>) (<span style="color: #0066BB; font-weight: bold">y</span> <span style="color: #333333">.</span> <span style="color: #0000DD; font-weight: bold">4</span>))))
(<span style="color: #0066BB; font-weight: bold">c</span> <span style="color: #333333">.</span> (<span style="color: #333333">#</span><span style="color: #996633">hasheq</span>((<span style="color: #0066BB; font-weight: bold">y</span> <span style="color: #333333">.</span> <span style="color: #0000DD; font-weight: bold">0</span>))))))))
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">json-pointer-value/index</span> <span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"a"</span> <span style="background-color: #fff0f0">"b"</span>) <span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"y"</span>) <span style="color: #996633">jse-ex</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">4</span>))
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">json-pointer-value/index</span> <span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"a"</span> <span style="background-color: #fff0f0">"c"</span>) <span style="color: #333333">'</span>(<span style="background-color: #fff0f0">"y"</span>) <span style="color: #996633">jse-ex</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">json-pointer-value/index</span> <span style="color: #996633">pre</span> <span style="color: #996633">post</span> <span style="color: #996633">jsexpr</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> (<span style="color: #0066BB; font-weight: bold">or/c</span> <span style="color: #996633">json-pointer?</span> <span style="color: #996633">json-pointer-expression?</span>)
(<span style="color: #0066BB; font-weight: bold">or/c</span> <span style="color: #996633">json-pointer?</span> <span style="color: #996633">json-pointer-expression?</span>)
<span style="color: #996633">jsexpr?</span>
<span style="color: #996633">jsexpr?</span>)
(<span style="color: #0066BB; font-weight: bold">for*/list</span> ([<span style="color: #0066BB; font-weight: bold">n</span> (<span style="color: #0066BB; font-weight: bold">in-naturals</span>)]
[<span style="color: #0066BB; font-weight: bold">v</span> (<span style="color: #0066BB; font-weight: bold">in-value</span> (<span style="color: #0066BB; font-weight: bold">json-pointer-value/false</span>
(<span style="color: #007020">append </span><span style="color: #996633">pre</span>
(<span style="color: #007020">list </span>(<span style="color: #007020">number->string </span><span style="color: #996633">n</span>))
<span style="color: #996633">post</span>)
<span style="color: #996633">jsexpr</span>))]
<span style="color: #008800; font-weight: bold">#:break</span> (<span style="color: #007020">not </span><span style="color: #996633">v</span>))
<span style="color: #996633">v</span>))
</pre></div>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-26020152015401227772019-04-17T16:46:00.005+02:002019-04-27T09:12:01.198+02:00From HtDP to Racket. Racket (5): I/0, optional and keyword args, point-free style<div id="toc" style="font-size:90%">
<p><em>TOC of the series</em></p>
<ol style="list-style-type: decimal">
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in.html">ISBN Extraction in ISL+ with regular expressions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_17.html">Racket: <code>#lang</code>, local definitions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_70.html">Racket: <code>only-in</code>, <code>rackunit</code>, test submodules</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction.html">Racket: <code>for</code>, <code>match</code>, sequences</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_17.html">Racket: <code>provide</code>, contracts</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_24.html">Racket: I/O, optional and keyword arguments, point-free style</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-addons.html">Racket: exception handling, http requests, JSON</a></li>
</ol>
</div>
</hr>
<p>As a final step, we are going to refactor a bit the prior code to show other useful Racket features.</p>
<p>First, we can generalize the exported functions to take any input in general and not only strings. This can be useful for clients that, for instance, prefer to pass as argument a text file.</p>
<p>The client then would call <code>find-isbn</code> or <code>find-isbn-list</code> as follows (or in a similar way):</p>
<pre><code>(with-input-from-file a-file-name isbn-find/list)</code></pre>
<p>but the client could still pass strings with</p>
<pre><code>(with-input-from-string a-string isbn-find/list)</code></pre>
<p>Those <code>with-input</code> functions are provided by <code>racket/port</code>.</p>
<p>For more information about them see <a href="https://docs.racket-lang.org/guide/i_o.html">Input and Output [The Racket Guide]</a>, as well as the <a href="https://docs.racket-lang.org/reference/port-lib.html">documentation for the functions mentioned</a>.</p>
<p>Another commonly used feature is defining functions with optional arguments and/or with named (aka. keyword) arguments.</p>
<p>In the current refactoring the <code>find</code> functions can receive one or two optional arguments, one of them named. Note that contracts for this kind of functions are special: they use the construct <code>->*</code>.</p>
<p>For more information about function definitions with optional or keyword arguments see <a href="https://docs.racket-lang.org/guide/lambda.html">Functions (Procedures) [The Racket Guide]</a>.</p>
<p>Finally, as a cosmetic touch, I have refactored the two last helpers <code>isbn-match*</code> and <code>isbn-normalize</code> to show a programming style called point-free style that you may find sometimes in many functional programming languages. In this style arguments are implicit and everything on the surface is just functions and function composition. To write in this way we need <code>curry</code> and <code>curryr</code> from <code>racket/function</code> as well as <code>compose</code> or <code>compose1</code>, a variant of <code>compose</code> for functions returning single values.</p>
<p>For more information about point-free style the Haskell wiki page on <a href="https://wiki.haskell.org/Pointfree">Pointfree</a> should be helpful. For information about <code>curry</code> and other functional stuff, see <a href="https://docs.racket-lang.org/reference/procedures.html">Procedures [The Racket Reference]</a></p>
</br>
<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #008800; font-weight: bold">#lang racket/base</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/contract</span>)
<span style="color: #888888">; ----------------------------------------------------------</span>
(<span style="color: #0066BB; font-weight: bold">provide</span>
(<span style="color: #0066BB; font-weight: bold">contract-out</span>
<span style="color: #888888">; extracts all isbns from the given input</span>
[<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> (<span style="color: #0066BB; font-weight: bold">->*</span> () (<span style="color: #0066BB; font-weight: bold">input-port?</span>) (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">isbn?</span>))]
<span style="color: #888888">; extracts the first isbn (of given format) from given input, if any</span>
<span style="color: #888888">; default format: 'isbn-13</span>
[<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">->*</span> () (<span style="color: #007020">input-port? </span><span style="color: #008800; font-weight: bold">#:format</span> <span style="color: #996633">isbn-format?</span>)
(<span style="color: #0066BB; font-weight: bold">or/c</span> <span style="color: #996633">isbn?</span> <span style="color: #003366; font-weight: bold">#f</span>))]
<span style="color: #888888">; determines whether the given value is a valid isbn</span>
[<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">predicate/c</span>]
<span style="color: #888888">; determines whether the give value is an isbn format</span>
[<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #996633">predicate/c</span>]))
<span style="color: #888888">; ----------------------------------------------------------</span>
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/function</span>
<span style="color: #996633">curry</span>
<span style="color: #996633">curryr</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/match</span>
<span style="color: #996633">match</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/sequence</span>
<span style="color: #996633">sequence/c</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/string</span>
<span style="color: #996633">string-normalize-spaces</span>
<span style="color: #996633">string-replace</span>))
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">rackunit</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Patterns and Regexes</span>
<span style="color: #888888">; [See ISBN International User Manual 7e. Sect. 5]</span>
<span style="color: #888888">; - Pattern Components</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-sep</span> <span style="background-color: #fff0f0">"[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-id</span> <span style="background-color: #fff0f0">"ISBN(-1[03])?:? "</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-13)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-10)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"97[89][ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">"\\d{1,5}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registrant</span> <span style="background-color: #fff0f0">"\\d{1,7}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-publication</span> <span style="background-color: #fff0f0">"\\d{1,6}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-check</span> <span style="background-color: #fff0f0">"\\d"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-check</span> <span style="background-color: #fff0f0">"[X\\d]"</span>)
<span style="color: #888888">; - Look ahead to ISBN groups</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
<span style="color: #888888">; - Main patterns</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span>
<span style="color: #996633">pat-isbn-13-look-ahead</span>
<span style="color: #996633">pat-isbn-prefix</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-13-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-id</span>
<span style="color: #996633">pat-isbn-10-look-ahead</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-10-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/prefix</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"\\d{10}"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"\\d{13}"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"\\d{9}[X\\d]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/prefix</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10</span>))
<span style="color: #888888">; - Regexes</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-id</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-id</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-sep</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-sep</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Predicates</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">""</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #0000DD; font-weight: bold">1</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0-262-06218-6"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="background-color: #fff0f0">"isbn-10"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-13-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-10-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-13</span>) (<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-10</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Validation</span>
<span style="color: #888888">; ISBN-13-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-13 string a valid isbn-13</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896831"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896834"</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">isbn-13-string?</span> <span style="color: #996633">boolean?</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>))
(<span style="color: #0066BB; font-weight: bold">in-cycle</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span>))
<span style="color: #0000DD; font-weight: bold">10</span>))
<span style="color: #888888">; ISBN-10-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-10 string a valid isbn-10</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026256114X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262561141"</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">isbn-10-string?</span> <span style="color: #996633">boolean?</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>))
(<span style="color: #0066BB; font-weight: bold">in-range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>))
<span style="color: #888888">; [Sequence-of N] [Sequence-of N] N -> Boolean</span>
<span style="color: #888888">; abstract checksum algorithm for isbn validation</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">7</span>)
(<span style="color: #0066BB; font-weight: bold">in-cycle</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span>))
<span style="color: #0000DD; font-weight: bold">10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">10</span>)
(<span style="color: #0066BB; font-weight: bold">in-range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #996633">multiplicands</span> <span style="color: #996633">multipliers</span> <span style="color: #996633">mod</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> (<span style="color: #0066BB; font-weight: bold">sequence/c</span> <span style="color: #996633">natural-number/c</span>)
(<span style="color: #0066BB; font-weight: bold">sequence/c</span> <span style="color: #996633">natural-number/c</span>)
<span style="color: #996633">natural-number/c</span>
<span style="color: #996633">boolean?</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">sum</span>
(<span style="color: #0066BB; font-weight: bold">for/sum</span> ([<span style="color: #0066BB; font-weight: bold">x</span> <span style="color: #996633">multiplicands</span>]
[<span style="color: #0066BB; font-weight: bold">y</span> <span style="color: #996633">multipliers</span>])
(<span style="color: #007020">* </span><span style="color: #996633">x</span> <span style="color: #996633">y</span>)))
(<span style="color: #007020">zero? </span>(<span style="color: #007020">modulo </span><span style="color: #996633">sum</span> <span style="color: #996633">mod</span>)))
<span style="color: #888888">; ISBN-String -> [List-of N]</span>
<span style="color: #888888">; translates str into the numbers their isbn letters represent</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="background-color: #fff0f0">"026256114X"</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">10</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">isbn-string?</span> (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">natural-number/c</span>))
(<span style="color: #0066BB; font-weight: bold">for/list</span> ([<span style="color: #0066BB; font-weight: bold">char</span> (<span style="color: #0066BB; font-weight: bold">in-string</span> <span style="color: #996633">str</span>)])
(<span style="color: #0066BB; font-weight: bold">match</span> <span style="color: #996633">char</span>
[<span style="color: #0044DD">#\X</span> <span style="color: #0000DD; font-weight: bold">10</span>]
[<span style="color: #0066BB; font-weight: bold">_</span> (<span style="color: #007020">string->number </span>(<span style="color: #007020">string </span><span style="color: #996633">char</span>))])))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Extraction</span>
<span style="color: #888888">; [Input-Port (current-input-port)] -> [List-of ISBN]</span>
<span style="color: #888888">; extracts all isbns from in</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/port</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">with-input-from-string</span> <span style="background-color: #fff0f0">""</span> <span style="color: #996633">isbn-find/list</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">with-input-from-string</span> <span style="background-color: #fff0f0">"none"</span> <span style="color: #996633">isbn-find/list</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #007020">with-input-from-file </span><span style="background-color: #fff0f0">"test-isbn-examples"</span> <span style="color: #996633">isbn-find/list</span>)
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's a sep's</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"0201896834"</span>
<span style="background-color: #fff0f0">"9780201896831"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"9780201896831"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> [<span style="color: #0066BB; font-weight: bold">in</span> (<span style="color: #0066BB; font-weight: bold">current-input-port</span>)])
(<span style="color: #0066BB; font-weight: bold">for*/list</span> ([<span style="color: #0066BB; font-weight: bold">line</span> (<span style="color: #0066BB; font-weight: bold">in-lines</span> <span style="color: #996633">in</span>)]
[<span style="color: #0066BB; font-weight: bold">candidate</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">line</span>))]
[<span style="color: #0066BB; font-weight: bold">isbn-str</span> (<span style="color: #0066BB; font-weight: bold">in-value</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">candidate</span>))]
<span style="color: #008800; font-weight: bold">#:when</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">isbn-str</span>))
<span style="color: #996633">isbn-str</span>))
<span style="color: #888888">; [Input-Port (current-input-port)] [ISBN-Format 'isbn-13]</span>
<span style="color: #888888">; -> [Maybe ISBN]</span>
<span style="color: #888888">; extracts the first isbn of given format from in, if any</span>
<span style="color: #888888">; default format: 'isbn-13</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">with-input-from-string</span> <span style="background-color: #fff0f0">""</span> <span style="color: #996633">isbn-find</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">with-input-from-string</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="color: #996633">isbn-find</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">with-input-from-string</span> <span style="background-color: #fff0f0">"0262062186"</span>
(<span style="color: #0066BB; font-weight: bold">curry</span> <span style="color: #996633">isbn-find</span> <span style="color: #008800; font-weight: bold">#:format</span> <span style="color: #AA6600">'isbn-13</span>)))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">with-input-from-string</span> <span style="background-color: #fff0f0">"9781593274917"</span>
(<span style="color: #0066BB; font-weight: bold">curry</span> <span style="color: #996633">isbn-find</span> <span style="color: #008800; font-weight: bold">#:format</span> <span style="color: #AA6600">'isbn-10</span>)))
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #007020">with-input-from-file </span><span style="background-color: #fff0f0">"test-isbn-examples"</span>
(<span style="color: #0066BB; font-weight: bold">curry</span> <span style="color: #996633">isbn-find</span> <span style="color: #008800; font-weight: bold">#:format</span> <span style="color: #AA6600">'isbn-10</span>))
<span style="background-color: #fff0f0">"0262062186"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #007020">with-input-from-file </span><span style="background-color: #fff0f0">"test-isbn-examples"</span>
(<span style="color: #0066BB; font-weight: bold">curry</span> <span style="color: #996633">isbn-find</span> <span style="color: #008800; font-weight: bold">#:format</span> <span style="color: #AA6600">'isbn-13</span>))
<span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find</span> [<span style="color: #0066BB; font-weight: bold">in</span> (<span style="color: #0066BB; font-weight: bold">current-input-port</span>)]
<span style="color: #008800; font-weight: bold">#:format</span> [<span style="color: #0066BB; font-weight: bold">isbn-format</span> <span style="color: #AA6600">'isbn-13</span>])
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">p?</span>
(<span style="color: #0066BB; font-weight: bold">match</span> <span style="color: #996633">isbn-format</span>
[<span style="color: #AA6600">'isbn-13</span> <span style="color: #996633">isbn-13?</span>]
[<span style="color: #AA6600">'isbn-10</span> <span style="color: #996633">isbn-10?</span>]))
(<span style="color: #0066BB; font-weight: bold">for*/or</span> ([<span style="color: #0066BB; font-weight: bold">line</span> (<span style="color: #0066BB; font-weight: bold">in-lines</span> <span style="color: #996633">in</span>)]
[<span style="color: #0066BB; font-weight: bold">candidate</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">line</span>))]
[<span style="color: #0066BB; font-weight: bold">isbn-str</span> (<span style="color: #0066BB; font-weight: bold">in-value</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">candidate</span>))]
<span style="color: #008800; font-weight: bold">#:when</span> (<span style="color: #0066BB; font-weight: bold">p?</span> <span style="color: #996633">isbn-str</span>))
<span style="color: #996633">isbn-str</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Helpers</span>
<span style="color: #888888">; String -> [List-of String]</span>
<span style="color: #888888">; matches substrings in the given string looking like isbn tags</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/file</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">"abc\nd"</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">isbn-match*</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>))
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized (all matched)</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's and sep's (all matched)</span>
<span style="background-color: #fff0f0">"ISBN 0-262-06218-6"</span>
<span style="background-color: #fff0f0">"ISBN: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-10 0 262 06218-6"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0-201-89683-4"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0-201-89683-1"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0201896831"</span>
<span style="color: #888888">;not isbn strings (usually impossible in real-world)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"9780201896834"</span> <span style="color: #888888">;partially matched</span>
<span style="color: #888888">;isbn strings, but isbn invalid (all matched)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"1593274913"</span>
<span style="background-color: #fff0f0">"9781593274912"</span> <span style="background-color: #fff0f0">"0201896833"</span> <span style="background-color: #fff0f0">"9780201896834"</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> <span style="color: #996633">isbn-match*</span>
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">string?</span> (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">string?</span>))
(<span style="color: #0066BB; font-weight: bold">compose1</span> (<span style="color: #0066BB; font-weight: bold">curry</span> <span style="color: #996633">regexp-match*</span> <span style="color: #996633">re-isbn</span>)
<span style="color: #996633">string-normalize-spaces</span>))
<span style="color: #888888">; String -> String</span>
<span style="color: #888888">; removes the isbn-id and, then, the isbn separators from str</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"123-45 67"</span>) <span style="background-color: #fff0f0">"1234567"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-14: hi"</span>) <span style="background-color: #fff0f0">"ISBN14:hi"</span>))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> <span style="color: #996633">isbn-normalize</span>
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">string?</span> <span style="color: #996633">string?</span>)
(<span style="color: #0066BB; font-weight: bold">compose1</span> (<span style="color: #0066BB; font-weight: bold">curryr</span> <span style="color: #996633">string-replace</span> <span style="color: #996633">re-isbn-sep</span> <span style="background-color: #fff0f0">""</span>)
(<span style="color: #0066BB; font-weight: bold">curryr</span> <span style="color: #996633">string-replace</span> <span style="color: #996633">re-isbn-id</span> <span style="background-color: #fff0f0">""</span>)))
</pre></div>
<div>
<p>Next article in the series: <a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-addons.html">Racket: exception handling, http requests, JSON</a></p>
</div>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-37523648756210749302019-04-17T15:01:00.001+02:002019-04-27T09:10:47.022+02:00From HtDP to Racket. Racket (4): provide, contracts<div id="toc" style="font-size:90%">
<p><em>TOC of the series</em></p>
<ol style="list-style-type: decimal">
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in.html">ISBN Extraction in ISL+ with regular expressions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_17.html">Racket: <code>#lang</code>, local definitions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_70.html">Racket: <code>only-in</code>, <code>rackunit</code>, test submodules</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction.html">Racket: <code>for</code>, <code>match</code>, sequences</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_17.html">Racket: <code>provide</code>, contracts</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_24.html">Racket: I/O, optional and keyword arguments, point-free style</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-addons.html">Racket: exception handling, http requests, JSON</a></li>
</ol>
</div>
</hr>
<p>The next step involves a substantial transformation and the first touch on a prominent Racket feature: contracts.</p>
<p>When you create code that may be used as a library by other client code, you probably don't want to make public everything there. It is more likely that only certain parts are written for client use while the rest is only for the implementation. In order to specify the functions, predicates or whatever you want to export for public use, you have to add an initial section with <code>provide</code> that states and documents from the very beginning the public interface.</p>
<p>Furthermore, you surely wish to clearly manifest what your exported functions expect as input and what they guarantee as the type of the output. Signatures are for that, but signatures don't receive actual checking of any kind. Contracts are precisely what you wish for. So in code that you write for others include contracts about everything you are going to export.</p>
<p>Besides, you may wish to use contracts locally in all functions, including the private ones. In such a case you can use <code>define/contract</code>.</p>
<p>In the code below the first part is now the <code>provide</code> section with contracts for all exported functions. The rest of the functions are now defined with <code>define/contract</code> just for more practicing on function contracts.</p>
<p>Finally, since predicates and contracts are sufficient documentation for the code at hand I have
also removed the Data Types section in prior versions.</p>
<p>Contracts can be overwhelming at first sight. Take your time and consult the section on <a href="https://docs.racket-lang.org/guide/contracts.html">Contracts [The Racket Guide]</a> and go to the <a href="https://docs.racket-lang.org/reference/contracts.html">Contracts [The Racket Reference]</a> for further details.</p>
<p> As for provide see <a href="https://docs.racket-lang.org/guide/module-provide.html">Exports: provide [The Racket Guide]</a> and <a href="https://docs.racket-lang.org/reference/require.html">Importing and Exporting [The Racket Reference]</a>
<p>As an aside, a new require has been added in order to use the contract <code>sequence/c</code> provided by <code>racket/sequence</code> in the definition of <code>isbn-checksumf</code>.</p>
</br>
<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; isbn-racket.v4.rkt</span>
<span style="color: #888888">; - provide</span>
<span style="color: #888888">; - contracts: contract-out, define/contract, ...</span>
<span style="color: #888888">; - racket/contract</span>
<span style="color: #888888">; - racket/sequence</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #008800; font-weight: bold">#lang racket/base</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/contract</span>)
<span style="color: #888888">; ----------------------------------------------------------</span>
(<span style="color: #0066BB; font-weight: bold">provide</span>
(<span style="color: #0066BB; font-weight: bold">contract-out</span>
<span style="color: #888888">; extracts all isbns from the given input</span>
[<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> (<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">string?</span> (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">isbn?</span>))]
<span style="color: #888888">; extracts the first isbn (of given format) from given input, if any</span>
<span style="color: #888888">; default format: 'isbn-13</span>
[<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">string?</span> <span style="color: #996633">isbn-format?</span> (<span style="color: #0066BB; font-weight: bold">or/c</span> <span style="color: #996633">isbn?</span> <span style="color: #003366; font-weight: bold">#f</span>))]
<span style="color: #888888">; determines whether the given value is a valid isbn</span>
[<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">predicate/c</span>]
<span style="color: #888888">; determines whether the give value is an isbn format</span>
[<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #996633">predicate/c</span>]))
<span style="color: #888888">; ----------------------------------------------------------</span>
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/match</span>
<span style="color: #996633">match</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/sequence</span>
<span style="color: #996633">sequence/c</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/string</span>
<span style="color: #996633">string-normalize-spaces</span>
<span style="color: #996633">string-replace</span>
<span style="color: #996633">string-split</span>))
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">rackunit</span>)
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/file</span>
<span style="color: #996633">file->string</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Patterns and Regexes</span>
<span style="color: #888888">; [See ISBN International User Manual 7e. Sect. 5]</span>
<span style="color: #888888">; - Pattern Components</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-sep</span> <span style="background-color: #fff0f0">"[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-id</span> <span style="background-color: #fff0f0">"ISBN(-1[03])?:? "</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-13)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-10)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"97[89][ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">"\\d{1,5}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registrant</span> <span style="background-color: #fff0f0">"\\d{1,7}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-publication</span> <span style="background-color: #fff0f0">"\\d{1,6}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-check</span> <span style="background-color: #fff0f0">"\\d"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-check</span> <span style="background-color: #fff0f0">"[X\\d]"</span>)
<span style="color: #888888">; - Look ahead to ISBN groups</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
<span style="color: #888888">; - Main patterns</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span>
<span style="color: #996633">pat-isbn-13-look-ahead</span>
<span style="color: #996633">pat-isbn-prefix</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-13-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-id</span>
<span style="color: #996633">pat-isbn-10-look-ahead</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-10-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/prefix</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"\\d{10}"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"\\d{13}"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"\\d{9}[X\\d]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/prefix</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10</span>))
<span style="color: #888888">; - Regexes</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-id</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-id</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-sep</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-sep</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Predicates</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">""</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #0000DD; font-weight: bold">1</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0-262-06218-6"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="background-color: #fff0f0">"isbn-10"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-13-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-10-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-13</span>) (<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-10</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Validation</span>
<span style="color: #888888">; ISBN-13-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-13 string a valid isbn-13</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896831"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896834"</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">isbn-13-string?</span> <span style="color: #996633">boolean?</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>))
(<span style="color: #0066BB; font-weight: bold">in-cycle</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span>))
<span style="color: #0000DD; font-weight: bold">10</span>))
<span style="color: #888888">; ISBN-10-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-10 string a valid isbn-10</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026256114X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262561141"</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">isbn-10-string?</span> <span style="color: #996633">boolean?</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>))
(<span style="color: #0066BB; font-weight: bold">in-range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>))
<span style="color: #888888">; [Sequence-of N] [Sequence-of N] N -> Boolean</span>
<span style="color: #888888">; abstract checksum algorithm for isbn validation</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">7</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span>)
<span style="color: #0000DD; font-weight: bold">10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">10</span>)
(<span style="color: #0066BB; font-weight: bold">in-range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #996633">multiplicands</span> <span style="color: #996633">multipliers</span> <span style="color: #996633">mod</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> (<span style="color: #0066BB; font-weight: bold">sequence/c</span> <span style="color: #996633">natural-number/c</span>)
(<span style="color: #0066BB; font-weight: bold">sequence/c</span> <span style="color: #996633">natural-number/c</span>)
<span style="color: #996633">natural-number/c</span>
<span style="color: #996633">boolean?</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">sum</span>
(<span style="color: #0066BB; font-weight: bold">for/sum</span> [(<span style="color: #0066BB; font-weight: bold">x</span> <span style="color: #996633">multiplicands</span>)
(<span style="color: #0066BB; font-weight: bold">y</span> <span style="color: #996633">multipliers</span>)]
(<span style="color: #007020">* </span><span style="color: #996633">x</span> <span style="color: #996633">y</span>)))
(<span style="color: #007020">zero? </span>(<span style="color: #007020">modulo </span><span style="color: #996633">sum</span> <span style="color: #996633">mod</span>)))
<span style="color: #888888">; ISBN-String -> [List-of N]</span>
<span style="color: #888888">; translates str into the numbers their isbn letters represent</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="background-color: #fff0f0">"026256114X"</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">10</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">isbn-string?</span> (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">natural-number/c</span>))
(<span style="color: #0066BB; font-weight: bold">for/list</span> ([<span style="color: #0066BB; font-weight: bold">char</span> (<span style="color: #0066BB; font-weight: bold">in-string</span> <span style="color: #996633">str</span>)])
(<span style="color: #0066BB; font-weight: bold">match</span> <span style="color: #996633">char</span>
[<span style="color: #0044DD">#\X</span> <span style="color: #0000DD; font-weight: bold">10</span>]
[<span style="color: #0066BB; font-weight: bold">_</span> (<span style="color: #007020">string->number </span>(<span style="color: #007020">string </span><span style="color: #996633">char</span>))])))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Extraction</span>
<span style="color: #888888">; String -> [List-of ISBN]</span>
<span style="color: #888888">; extracts all isbns from str</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="background-color: #fff0f0">"none"</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>))
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's a sep's</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"0201896834"</span>
<span style="background-color: #fff0f0">"9780201896831"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"9780201896831"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">for*/list</span> ([<span style="color: #0066BB; font-weight: bold">line</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">string-split</span> <span style="color: #996633">str</span> <span style="background-color: #fff0f0">"\n"</span>))]
[<span style="color: #0066BB; font-weight: bold">candidate</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">line</span>))]
[<span style="color: #0066BB; font-weight: bold">isbn-str</span> (<span style="color: #0066BB; font-weight: bold">in-value</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">candidate</span>))]
<span style="color: #008800; font-weight: bold">#:when</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">isbn-str</span>))
<span style="color: #996633">isbn-str</span>))
<span style="color: #888888">; String ISBN-Format -> [Maybe ISBN]</span>
<span style="color: #888888">; extracts the first isbn of given format from str, if any</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">""</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">""</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">"9781593274917"</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>)
<span style="color: #AA6600">'isbn-13</span>)
<span style="background-color: #fff0f0">"9781593274917"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>)
<span style="color: #AA6600">'isbn-10</span>)
<span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="color: #996633">str</span> <span style="color: #996633">format</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">p?</span>
(<span style="color: #0066BB; font-weight: bold">match</span> <span style="color: #996633">format</span>
[<span style="color: #AA6600">'isbn-13</span> <span style="color: #996633">isbn-13?</span>]
[<span style="color: #AA6600">'isbn-10</span> <span style="color: #996633">isbn-10?</span>]))
(<span style="color: #0066BB; font-weight: bold">for*/or</span> ([<span style="color: #0066BB; font-weight: bold">line</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">string-split</span> <span style="color: #996633">str</span> <span style="background-color: #fff0f0">"\n"</span>))]
[<span style="color: #0066BB; font-weight: bold">candidate</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">line</span>))]
[<span style="color: #0066BB; font-weight: bold">isbn-str</span> (<span style="color: #0066BB; font-weight: bold">in-value</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">candidate</span>))]
<span style="color: #008800; font-weight: bold">#:when</span> (<span style="color: #0066BB; font-weight: bold">p?</span> <span style="color: #996633">isbn-str</span>))
<span style="color: #996633">isbn-str</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Helpers</span>
<span style="color: #888888">; String -> [List-of String]</span>
<span style="color: #888888">; matches substrings in the given string looking like isbn tags</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">"abc\nd"</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">isbn-match*</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>))
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized (all matched)</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's and sep's (all matched)</span>
<span style="background-color: #fff0f0">"ISBN 0-262-06218-6"</span>
<span style="background-color: #fff0f0">"ISBN: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-10 0 262 06218-6"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0-201-89683-4"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0-201-89683-1"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0201896831"</span>
<span style="color: #888888">;not isbn strings (usually impossible in real-world)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"9780201896834"</span> <span style="color: #888888">;partially matched</span>
<span style="color: #888888">;isbn strings, but isbn invalid (all matched)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"1593274913"</span>
<span style="background-color: #fff0f0">"9781593274912"</span> <span style="background-color: #fff0f0">"0201896833"</span> <span style="background-color: #fff0f0">"9780201896834"</span>)))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">string?</span> (<span style="color: #0066BB; font-weight: bold">listof</span> <span style="color: #996633">string?</span>))
(<span style="color: #0066BB; font-weight: bold">regexp-match*</span> <span style="color: #996633">re-isbn</span> (<span style="color: #0066BB; font-weight: bold">string-normalize-spaces</span> <span style="color: #996633">str</span>)))
<span style="color: #888888">; String -> String</span>
<span style="color: #888888">; removes the isbn-id and, then, the isbn separators from str</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"123-45 67"</span>) <span style="background-color: #fff0f0">"1234567"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-14: hi"</span>) <span style="background-color: #fff0f0">"ISBN14:hi"</span>))
(<span style="color: #0066BB; font-weight: bold">define/contract</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">-></span> <span style="color: #996633">string?</span> <span style="color: #996633">string?</span>)
(<span style="color: #0066BB; font-weight: bold">string-replace</span>
(<span style="color: #0066BB; font-weight: bold">string-replace</span> <span style="color: #996633">str</span> <span style="color: #996633">re-isbn-id</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #996633">re-isbn-sep</span> <span style="background-color: #fff0f0">""</span>))
</pre></div>
<div>
<p>Next article in the series: <a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_24.html">Racket: I/O, optional and keyword arguments</a></p>
</div>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-81177758985721004612019-04-17T13:20:00.004+02:002019-04-27T09:09:01.019+02:00From HtDP to Racket. Racket (3): for, match, sequences<div id="toc" style="font-size:90%">
<p><em>TOC of the series</em></p>
<ol style="list-style-type: decimal">
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in.html">ISBN Extraction in ISL+ with regular expressions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_17.html">Racket: <code>#lang</code>, local definitions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_70.html">Racket: <code>only-in</code>, <code>rackunit</code>, test submodules</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction.html">Racket: <code>for</code>, <code>match</code>, sequences</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_17.html">Racket: <code>provide</code>, contracts</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_24.html">Racket: I/O, optional and keyword arguments, point-free style</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-addons.html">Racket: exception handling, http requests, JSON</a></li>
</ol>
</div>
</hr>
<p>In the next step we will apply <code>for</code> and <code>match</code> instead of higher order functions over lists and <code>cond</code>. This is optional, the code in the previous version is perfectly valid Racket code. But many Racketeers tend to prefer <code>for</code> for iteration and <code>match</code> over conditionals.</p>
<p>Iteration with <code>for</code> along with sequences (<code>in-list</code>, <code>in-range</code>, and the like) is in principle more efficient, more concise and adds great flexibility given the abundance of pre-defined constructs. For many programmers it is also more readable.</p>
<p>Regarding <code>match</code> its expressiveness is unparalleled. Once you meet it, you can't live without it.</p>
<p>For those reasons the use of <code>for</code> is encouraged in the <a href="https://docs.racket-lang.org/style/Choosing_the_Right_Construct.html">The Racket Style Guide</a> and pattern matching is so powerful that you must know it. They are even introduced as <a href="https://htdp.org/2018-01-06/Book/i3-4.html">an Intermezzo in HtDP/2e</a>.</p>
<p>For more information about <code>for</code> see <a href="https://docs.racket-lang.org/guide/for.html">Iterations and Comprehensions [The Racket Guide]</a> and <a href="https://docs.racket-lang.org/reference/for.html">Iterations and Comprehensions [The Racket Reference]</a>. For details about <code>match</code> see <a href="https://docs.racket-lang.org/guide/match.html">Pattern Matching [The Racket Guide]</a> and <a href="https://docs.racket-lang.org/reference/match.html">Pattern Matching [The Racket Reference]</a>
<p>The new version with <code>for</code> a <code>match</code> looks as shown below.</p>
<p>Don't forget to take a look at the Racket documentation for all the new constructs introduced:</p>
<ul>
<li><code>in-list</code></li>
<li><code>in-cycle</code></li>
<li><code>in-range</code></li>
<li><code>in-value</code></li>
<li><code>for/list</code></li>
<li><code>for*/list</code></li>
<li><code>for/sum</code></li>
<li><code>match</code></li>
</ul>
<p>By the way, with <code>for</code> and <code>match</code> the functions provided by <code>racket/list</code> are no longer used, so the require of that module has been deleted.</p>
</br>
<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; isbn-racket.v3.rkt</span>
<span style="color: #888888">; - for</span>
<span style="color: #888888">; - match</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #008800; font-weight: bold">#lang racket/base</span>
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/match</span>
<span style="color: #996633">match</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/list</span>
<span style="color: #996633">empty?</span>
<span style="color: #996633">first</span>
<span style="color: #996633">range</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/string</span>
<span style="color: #996633">string-normalize-spaces</span>
<span style="color: #996633">string-replace</span>
<span style="color: #996633">string-split</span>))
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">rackunit</span>)
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/file</span>
<span style="color: #996633">file->string</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Data Types</span>
<span style="color: #888888">; An ISBN is one of:</span>
<span style="color: #888888">; - ISBN-13</span>
<span style="color: #888888">; - ISBN-10</span>
<span style="color: #888888">; An ISBN-String is one of:</span>
<span style="color: #888888">; - ISBN-13-String</span>
<span style="color: #888888">; - ISBN-10-String</span>
<span style="color: #888888">; An ISBN-13 is an valid ISBN-13-String</span>
<span style="color: #888888">; An ISBN-10 is an valid ISBN-10-String</span>
<span style="color: #888888">; where valid means that the numbers in that string</span>
<span style="color: #888888">; fullfil a certain mathematical computation. (See</span>
<span style="color: #888888">; ISBN User Manual for information about this computation)</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-13-ex</span> <span style="background-color: #fff0f0">"9781593274917"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-ex</span> <span style="background-color: #fff0f0">"0262062186"</span>)
<span style="color: #888888">; An ISBN-13-String is a String consisting of 13 digits,</span>
<span style="color: #888888">; where a digit is one of '0', '1', ..., '9'.</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-13-str-ex</span> <span style="background-color: #fff0f0">"1234567890123"</span>)
<span style="color: #888888">; An ISBN-10-String is a String consisting of 9 digits,</span>
<span style="color: #888888">; and a last letter that can be a digit or 'X'.</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-str-ex-1</span> <span style="background-color: #fff0f0">"1234567890"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-str-ex-2</span> <span style="background-color: #fff0f0">"123456789X"</span>)
<span style="color: #888888">; An ISBN-Format is one of:</span>
<span style="color: #888888">; - 'isbn-13</span>
<span style="color: #888888">; - 'isbn-10</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Patterns and Regexes</span>
<span style="color: #888888">; [See ISBN International User Manual 7e. Sect. 5]</span>
<span style="color: #888888">; - Pattern Components</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-sep</span> <span style="background-color: #fff0f0">"[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-id</span> <span style="background-color: #fff0f0">"ISBN(-1[03])?:? "</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-13)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-10)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"97[89][ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">"\\d{1,5}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registrant</span> <span style="background-color: #fff0f0">"\\d{1,7}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-publication</span> <span style="background-color: #fff0f0">"\\d{1,6}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-check</span> <span style="background-color: #fff0f0">"\\d"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-check</span> <span style="background-color: #fff0f0">"[X\\d]"</span>)
<span style="color: #888888">; - Look ahead to ISBN groups</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
<span style="color: #888888">; - Main patterns</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span>
<span style="color: #996633">pat-isbn-13-look-ahead</span>
<span style="color: #996633">pat-isbn-prefix</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-13-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-id</span>
<span style="color: #996633">pat-isbn-10-look-ahead</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-10-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/prefix</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"\\d{10}"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"\\d{13}"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"\\d{9}[X\\d]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/prefix</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10</span>))
<span style="color: #888888">; - Regexes</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-id</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-id</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-sep</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-sep</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Predicates</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">""</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #0000DD; font-weight: bold">1</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0-262-06218-6"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="background-color: #fff0f0">"isbn-10"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-13-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-10-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-13</span>) (<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-10</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Validation</span>
<span style="color: #888888">; ISBN-13-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-13 string a valid isbn-13</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896831"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896834"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>))
(<span style="color: #0066BB; font-weight: bold">in-cycle</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span>))
<span style="color: #0000DD; font-weight: bold">10</span>))
<span style="color: #888888">; ISBN-10-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-10 string a valid isbn-10</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026256114X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262561141"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>))
(<span style="color: #0066BB; font-weight: bold">in-range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>))
<span style="color: #888888">; [Sequence-of N] [Sequence-of N] N -> Boolean</span>
<span style="color: #888888">; abstract checksum algorithm for isbn validation</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">7</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span>)
<span style="color: #0000DD; font-weight: bold">10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">10</span>)
(<span style="color: #0066BB; font-weight: bold">in-range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #996633">multiplicands</span> <span style="color: #996633">multipliers</span> <span style="color: #996633">mod</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">sum</span>
(<span style="color: #0066BB; font-weight: bold">for/sum</span> [(<span style="color: #0066BB; font-weight: bold">x</span> <span style="color: #996633">multiplicands</span>)
(<span style="color: #0066BB; font-weight: bold">y</span> <span style="color: #996633">multipliers</span>)]
(<span style="color: #007020">* </span><span style="color: #996633">x</span> <span style="color: #996633">y</span>)))
(<span style="color: #007020">zero? </span>(<span style="color: #007020">modulo </span><span style="color: #996633">sum</span> <span style="color: #996633">mod</span>)))
<span style="color: #888888">; ISBN-String -> [List-of N]</span>
<span style="color: #888888">; translates str into the numbers their isbn letters represent</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="background-color: #fff0f0">"026256114X"</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">10</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">for/list</span> ([<span style="color: #0066BB; font-weight: bold">char</span> (<span style="color: #0066BB; font-weight: bold">in-string</span> <span style="color: #996633">str</span>)])
(<span style="color: #0066BB; font-weight: bold">match</span> <span style="color: #996633">char</span>
[<span style="color: #0044DD">#\X</span> <span style="color: #0000DD; font-weight: bold">10</span>]
[<span style="color: #0066BB; font-weight: bold">_</span> (<span style="color: #007020">string->number </span>(<span style="color: #007020">string </span><span style="color: #996633">char</span>))])))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Extraction</span>
<span style="color: #888888">; String -> [List-of ISBN]</span>
<span style="color: #888888">; extracts all isbns from str</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="background-color: #fff0f0">"none"</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>))
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's a sep's</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"0201896834"</span>
<span style="background-color: #fff0f0">"9780201896831"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"9780201896831"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">for*/list</span> ([<span style="color: #0066BB; font-weight: bold">line</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">string-split</span> <span style="color: #996633">str</span> <span style="background-color: #fff0f0">"\n"</span>))]
[<span style="color: #0066BB; font-weight: bold">candidate</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">line</span>))]
[<span style="color: #0066BB; font-weight: bold">isbn-str</span> (<span style="color: #0066BB; font-weight: bold">in-value</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">candidate</span>))]
<span style="color: #008800; font-weight: bold">#:when</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">isbn-str</span>))
<span style="color: #996633">isbn-str</span>))
<span style="color: #888888">; String ISBN-Format -> [Maybe ISBN]</span>
<span style="color: #888888">; extracts the first isbn of given format from str, if any</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">""</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">""</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">"9781593274917"</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>)
<span style="color: #AA6600">'isbn-13</span>)
<span style="background-color: #fff0f0">"9781593274917"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>)
<span style="color: #AA6600">'isbn-10</span>)
<span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="color: #996633">str</span> <span style="color: #996633">format</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">p?</span>
(<span style="color: #0066BB; font-weight: bold">match</span> <span style="color: #996633">format</span>
[<span style="color: #AA6600">'isbn-13</span> <span style="color: #996633">isbn-13?</span>]
[<span style="color: #AA6600">'isbn-10</span> <span style="color: #996633">isbn-10?</span>]))
(<span style="color: #0066BB; font-weight: bold">for*/or</span> ([<span style="color: #0066BB; font-weight: bold">line</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">string-split</span> <span style="color: #996633">str</span> <span style="background-color: #fff0f0">"\n"</span>))]
[<span style="color: #0066BB; font-weight: bold">candidate</span> (<span style="color: #0066BB; font-weight: bold">in-list</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">line</span>))]
[<span style="color: #0066BB; font-weight: bold">isbn-str</span> (<span style="color: #0066BB; font-weight: bold">in-value</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">candidate</span>))]
<span style="color: #008800; font-weight: bold">#:when</span> (<span style="color: #0066BB; font-weight: bold">p?</span> <span style="color: #996633">isbn-str</span>))
<span style="color: #996633">isbn-str</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Helpers</span>
<span style="color: #888888">; String -> [List-of String]</span>
<span style="color: #888888">; matches substrings in the given string looking like isbn tags</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">"abc\nd"</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">isbn-match*</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>))
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized (all matched)</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's and sep's (all matched)</span>
<span style="background-color: #fff0f0">"ISBN 0-262-06218-6"</span>
<span style="background-color: #fff0f0">"ISBN: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-10 0 262 06218-6"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0-201-89683-4"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0-201-89683-1"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0201896831"</span>
<span style="color: #888888">;not isbn strings (usually impossible in real-world)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"9780201896834"</span> <span style="color: #888888">;partially matched</span>
<span style="color: #888888">;isbn strings, but isbn invalid (all matched)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"1593274913"</span>
<span style="background-color: #fff0f0">"9781593274912"</span> <span style="background-color: #fff0f0">"0201896833"</span> <span style="background-color: #fff0f0">"9780201896834"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">regexp-match*</span> <span style="color: #996633">re-isbn</span> (<span style="color: #0066BB; font-weight: bold">string-normalize-spaces</span> <span style="color: #996633">str</span>)))
<span style="color: #888888">; String -> String</span>
<span style="color: #888888">; removes the isbn-id and, then, the isbn separators from str</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"123-45 67"</span>) <span style="background-color: #fff0f0">"1234567"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-14: hi"</span>) <span style="background-color: #fff0f0">"ISBN14:hi"</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">string-replace</span>
(<span style="color: #0066BB; font-weight: bold">string-replace</span> <span style="color: #996633">str</span> <span style="color: #996633">re-isbn-id</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #996633">re-isbn-sep</span> <span style="background-color: #fff0f0">""</span>))
</pre></div>
<div>
<p>Next article in the series: <a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_17.html">Racket: <code>provide</code>, contracts</a></p>
</div>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-69236845820211650642019-04-17T10:33:00.001+02:002019-04-27T09:07:02.374+02:00From HtDP to Racket. Racket (2): only-in, rackunit, test submodules<div id="toc" style="font-size:90%">
<p><em>TOC of the series</em></p>
<ol style="list-style-type: decimal">
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in.html">ISBN Extraction in ISL+ with regular expressions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_17.html">Racket: <code>#lang</code>, local definitions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_70.html">Racket: <code>only-in</code>, <code>rackunit</code>, test submodules</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction.html">Racket: <code>for</code>, <code>match</code>, sequences</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_17.html">Racket: <code>provide</code>, contracts</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_24.html">Racket: I/O, optional and keyword arguments, point-free style</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-addons.html">Racket: exception handling, http requests, JSON</a></li>
</ol>
</div>
</hr>
<p>The next transformation affects tests. You can stick with <code>check-expect</code> via <code>check-engine/racket-tests</code>, but it is much more common to rely on the functionality of <code>rackunit</code>.</p>
<p><code>rackunit</code> has its own set of functions. For our tests so far we need <code>check-true</code> and <code>check-false</code>, for tests whose expected result is <code>#t</code> or <code>#f</code>, and <code>check-equal?</code> for the rest of the cases.</p>
<p>Tests can go in its own file, at the end of the file, or, as you are accustomed to, intermixed with function definitions. If you, as many Racket programmers, prefer the latter behavior, you need to put tests in a submodule within the file, which is a module itself. To do so place your tests inside <code>(module+ test ...)</code> one per unit tested. It is common to introduce this form in the initial require section, too, with <code>(require rackunit)</code> and whatever will be shared by other test submodules below.</p>
<p>For details about modules and <code>module+</code>, see the <a href="https://docs.racket-lang.org/guide/modules.html">Modules [The Racket Guide]</a>, and specifically <a href="https://docs.racket-lang.org/guide/Module_Syntax.html#%28part._main-and-test%29">Main and Test Submodules [The Racket Guide]</a>.</p>
<p>Another transformation that we carry out now is to be more specific about the functions per loaded module we are going to use. <code>only-in</code> in <code>require</code> supports this, a functionality only available in Racket. The benefits are twofold: you require only what you need, which is more efficient, and you always know exactly where your functions come from.</p>
<p>For more information about <code>require</code> and <code>only-in</code>, see <a href="https://docs.racket-lang.org/guide/module-require.html">Imports: require [The Racket Guide]</a>.</p>
</br>
<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; isbn-racket.v2.rkt</span>
<span style="color: #888888">; - submodules (for tests)</span>
<span style="color: #888888">; - require rackunit</span>
<span style="color: #888888">; - check-equal?, check-false, check-true</span>
<span style="color: #888888">; - only-in in require</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #008800; font-weight: bold">#lang racket/base</span>
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/list</span>
<span style="color: #996633">empty?</span>
<span style="color: #996633">first</span>
<span style="color: #996633">range</span>))
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/string</span>
<span style="color: #996633">string-normalize-spaces</span>
<span style="color: #996633">string-replace</span>
<span style="color: #996633">string-split</span>))
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">rackunit</span>)
(<span style="color: #008800; font-weight: bold">require </span>(<span style="color: #0066BB; font-weight: bold">only-in</span> <span style="color: #996633">racket/file</span>
<span style="color: #996633">file->string</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Data Types</span>
<span style="color: #888888">; An ISBN is one of:</span>
<span style="color: #888888">; - ISBN-13</span>
<span style="color: #888888">; - ISBN-10</span>
<span style="color: #888888">; An ISBN-String is one of:</span>
<span style="color: #888888">; - ISBN-13-String</span>
<span style="color: #888888">; - ISBN-10-String</span>
<span style="color: #888888">; An ISBN-13 is an valid ISBN-13-String</span>
<span style="color: #888888">; An ISBN-10 is an valid ISBN-10-String</span>
<span style="color: #888888">; where valid means that the numbers in that string</span>
<span style="color: #888888">; fullfil a certain mathematical computation. (See</span>
<span style="color: #888888">; ISBN User Manual for information about this computation)</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-13-ex</span> <span style="background-color: #fff0f0">"9781593274917"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-ex</span> <span style="background-color: #fff0f0">"0262062186"</span>)
<span style="color: #888888">; An ISBN-13-String is a String consisting of 13 digits,</span>
<span style="color: #888888">; where a digit is one of '0', '1', ..., '9'.</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-13-str-ex</span> <span style="background-color: #fff0f0">"1234567890123"</span>)
<span style="color: #888888">; An ISBN-10-String is a String consisting of 9 digits,</span>
<span style="color: #888888">; and a last letter that can be a digit or 'X'.</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-str-ex-1</span> <span style="background-color: #fff0f0">"1234567890"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-str-ex-2</span> <span style="background-color: #fff0f0">"123456789X"</span>)
<span style="color: #888888">; An ISBN-Format is one of:</span>
<span style="color: #888888">; - 'isbn-13</span>
<span style="color: #888888">; - 'isbn-10</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Patterns and Regexes</span>
<span style="color: #888888">; [See ISBN International User Manual 7e. Sect. 5]</span>
<span style="color: #888888">; - Pattern Components</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-sep</span> <span style="background-color: #fff0f0">"[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-id</span> <span style="background-color: #fff0f0">"ISBN(-1[03])?:? "</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-13)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-10)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"97[89][ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">"\\d{1,5}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registrant</span> <span style="background-color: #fff0f0">"\\d{1,7}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-publication</span> <span style="background-color: #fff0f0">"\\d{1,6}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-check</span> <span style="background-color: #fff0f0">"\\d"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-check</span> <span style="background-color: #fff0f0">"[X\\d]"</span>)
<span style="color: #888888">; - Look ahead to ISBN groups</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
<span style="color: #888888">; - Main patterns</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span>
<span style="color: #996633">pat-isbn-13-look-ahead</span>
<span style="color: #996633">pat-isbn-prefix</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-13-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-id</span>
<span style="color: #996633">pat-isbn-10-look-ahead</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-10-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/prefix</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"\\d{10}"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"\\d{13}"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"\\d{9}[X\\d]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/prefix</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10</span>))
<span style="color: #888888">; - Regexes</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-id</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-id</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-sep</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-sep</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Predicates</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">""</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #0000DD; font-weight: bold">1</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0-262-06218-6"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>)) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218"</span>)) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #003366; font-weight: bold">#f</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="background-color: #fff0f0">"isbn-10"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-13-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-10-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-13</span>) (<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-10</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Validation</span>
<span style="color: #888888">; ISBN-13-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-13 string a valid isbn-13</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274917"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896831"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274912"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896834"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span>)
<span style="color: #0000DD; font-weight: bold">10</span>))
<span style="color: #888888">; ISBN-10-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-10 string a valid isbn-10</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026256114X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026206218X"</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262561141"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>))
<span style="color: #888888">; [List-of N] [List-of N] N -> Boolean</span>
<span style="color: #888888">; abstract checksum algorithm for isbn validation</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-true</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">7</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span>)
<span style="color: #0000DD; font-weight: bold">10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">10</span>)
(<span style="color: #0066BB; font-weight: bold">range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #996633">multiplicands</span> <span style="color: #996633">multipliers</span> <span style="color: #996633">mod</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">sum</span> (<span style="color: #0066BB; font-weight: bold">foldl</span> <span style="color: #996633">+</span> <span style="color: #0000DD; font-weight: bold">0</span> (<span style="color: #007020">map </span><span style="color: #996633">*</span> <span style="color: #996633">multiplicands</span> <span style="color: #996633">multipliers</span>)))
(<span style="color: #007020">zero? </span>(<span style="color: #007020">modulo </span><span style="color: #996633">sum</span> <span style="color: #996633">mod</span>)))
<span style="color: #888888">; ISBN-String -> [List-of N]</span>
<span style="color: #888888">; translates str into the numbers their isbn letters represent</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="background-color: #fff0f0">"026256114X"</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">10</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">str</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-digit->number</span> <span style="color: #996633">char</span>)
(<span style="color: #0066BB; font-weight: bold">cond</span>
[(<span style="color: #007020">char=? </span><span style="color: #996633">char</span> <span style="color: #0044DD">#\X</span>) <span style="color: #0000DD; font-weight: bold">10</span>]
[<span style="color: #0066BB; font-weight: bold">else</span> (<span style="color: #007020">string->number </span>(<span style="color: #007020">string </span><span style="color: #996633">char</span>))]))
(<span style="color: #007020">map </span><span style="color: #996633">isbn-digit->number</span> (<span style="color: #007020">string->list </span><span style="color: #996633">str</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Extraction</span>
<span style="color: #888888">; String -> [List-of ISBN]</span>
<span style="color: #888888">; extracts all isbns from str</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="background-color: #fff0f0">"none"</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>))
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's a sep's</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"0201896834"</span>
<span style="background-color: #fff0f0">"9780201896831"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"9780201896831"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="color: #996633">str</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">candidates</span>
(<span style="color: #0066BB; font-weight: bold">foldr</span> <span style="color: #996633">append</span> <span style="color: #333333">'</span>()
(<span style="color: #007020">map </span><span style="color: #996633">isbn-match*</span> (<span style="color: #0066BB; font-weight: bold">string-split</span> <span style="color: #996633">str</span> <span style="background-color: #fff0f0">"\n"</span>))))
(<span style="color: #0066BB; font-weight: bold">filter</span> <span style="color: #996633">isbn?</span> (<span style="color: #007020">map </span><span style="color: #996633">isbn-normalize</span> <span style="color: #996633">candidates</span>)))
<span style="color: #888888">; String ISBN-Format -> [Maybe ISBN]</span>
<span style="color: #888888">; extracts the first isbn of given format from str, if any</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">""</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">""</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="color: #AA6600">'isbn-13</span>))
(<span style="color: #0066BB; font-weight: bold">check-false</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">"9781593274917"</span> <span style="color: #AA6600">'isbn-10</span>))
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>)
<span style="color: #AA6600">'isbn-13</span>)
<span style="background-color: #fff0f0">"9781593274917"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>)
<span style="color: #AA6600">'isbn-10</span>)
<span style="background-color: #fff0f0">"0262062186"</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="color: #996633">str</span> <span style="color: #996633">format</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">p?</span>
(<span style="color: #008800; font-weight: bold">cond </span>[(<span style="color: #007020">equal? </span><span style="color: #996633">format</span> <span style="color: #AA6600">'isbn-13</span>) <span style="color: #996633">isbn-13?</span>]
[(<span style="color: #007020">equal? </span><span style="color: #996633">format</span> <span style="color: #AA6600">'isbn-10</span>) <span style="color: #996633">isbn-10?</span>]))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbns</span>
(<span style="color: #0066BB; font-weight: bold">filter</span> <span style="color: #996633">p?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="color: #996633">str</span>)))
(<span style="color: #0066BB; font-weight: bold">cond</span>
[(<span style="color: #0066BB; font-weight: bold">empty?</span> <span style="color: #996633">isbns</span>) <span style="color: #003366; font-weight: bold">#f</span>]
[<span style="color: #0066BB; font-weight: bold">else</span> (<span style="color: #0066BB; font-weight: bold">first</span> <span style="color: #996633">isbns</span>)]))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Helpers</span>
<span style="color: #888888">; String -> [List-of String]</span>
<span style="color: #888888">; matches substrings in the given string looking like isbn tags</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">"abc\nd"</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-equal?</span>
(<span style="color: #0066BB; font-weight: bold">isbn-match*</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>))
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized (all matched)</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's and sep's (all matched)</span>
<span style="background-color: #fff0f0">"ISBN 0-262-06218-6"</span>
<span style="background-color: #fff0f0">"ISBN: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-10 0 262 06218-6"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0-201-89683-4"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0-201-89683-1"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0201896831"</span>
<span style="color: #888888">;not isbn strings (usually impossible in real-world)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"9780201896834"</span> <span style="color: #888888">;partially matched</span>
<span style="color: #888888">;isbn strings, but isbn invalid (all matched)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"1593274913"</span>
<span style="background-color: #fff0f0">"9781593274912"</span> <span style="background-color: #fff0f0">"0201896833"</span> <span style="background-color: #fff0f0">"9780201896834"</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">regexp-match*</span> <span style="color: #996633">re-isbn</span> (<span style="color: #0066BB; font-weight: bold">string-normalize-spaces</span> <span style="color: #996633">str</span>)))
<span style="color: #888888">; String -> String</span>
<span style="color: #888888">; removes the isbn-id and, then, the isbn separators from str</span>
(<span style="color: #0066BB; font-weight: bold">module+</span> <span style="color: #996633">test</span>
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"123-45 67"</span>) <span style="background-color: #fff0f0">"1234567"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-equal?</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-14: hi"</span>) <span style="background-color: #fff0f0">"ISBN14:hi"</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">string-replace</span>
(<span style="color: #0066BB; font-weight: bold">string-replace</span> <span style="color: #996633">str</span> <span style="color: #996633">re-isbn-id</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #996633">re-isbn-sep</span> <span style="background-color: #fff0f0">""</span>))
</pre></div>
<div>
<p>Next article in the series: <a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction.html">Racket: <code>for</code>, <code>match</code>, sequences</a></p>
</div>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-41556371556196199042019-04-17T09:08:00.000+02:002019-04-27T09:16:29.313+02:00From HtDP to Racket. Racket (1): #lang, local definitions, minor tweaks<div id="toc" style="font-size:90%">
<p><em>TOC of the series</em></p>
<ol style="list-style-type: decimal">
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in.html">ISBN Extraction in ISL+ with regular expressions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_17.html">Racket: <code>#lang</code>, local definitions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_70.html">Racket: <code>only-in</code>, <code>rackunit</code>, test submodules</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction.html">Racket: <code>for</code>, <code>match</code>, sequences</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_17.html">Racket: <code>provide</code>, contracts</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_24.html">Racket: I/O, optional and keyword arguments, point-free style</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-addons.html">Racket: exception handling, http requests, JSON</a></li>
</ol>
</div>
</hr>
<p>The very first thing we need to do in order to change from ISL to Racket is to select the appropriate language via the DrRacket's menu 'Language:Choose Language'.</p>
<p>We will choose <code>#lang racket/base</code>. You can also select <code>#lang racket</code>. The first option loads the base of Racket, and everything else has to be required on demand. The second option loads the core and many other libraries.</p>
With this addition the <code>racket/base</code> requirement of our previous version is, of course, no longer needed.</p>
<p>Before trying to run the code, please comment out all tests. We will look into them in the next post.</p>
<p>If you run the code now you will get a syntax error. This is expected. ISL+ is not Racket.</p>
<p>A first quick fix is to require <code>racket/list</code>, which provides functions like <code>range</code>, <code>empty?</code>, and <code>first</code> used in our prior implementation.</p>
<p>Another simple fix has to do with <code>symbol=?</code>. <code>symbol=?</code> is provided by <code>racket/bool</code>. You could require that module. Or, as we are going to do, use the more generic <code>equal?</code>. In general, though, it is better to use a specific function like <code>symbol=?</code>.</p>
<p><code>explode</code> is another function only available in teaching languages. You have two options here: implementing your own version of <code>explode</code> or refactoring the function that uses <code>explode</code>, <code>isbn-string->numbers</code>. We choose the second approach, that iterates now on <code>Char</code>'s instead of on <code>1String</code>'s.</p>
<p>A more serious modification has to do with local definitions. This is an *SL construct. Most of the time you should use a simple <code>define</code> instead, but you may need <code>let</code> forms in other special cases.</p>
<p>The resulting transformation looks as follows:</p>
<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; isbn-racket.v1.rkt</span>
<span style="color: #888888">; - local -> define</span>
<span style="color: #888888">; - require racket/list</span>
<span style="color: #888888">; - isbn-string->numbers refactored</span>
<span style="color: #888888">; - symbol=? -> equal? [or require racket/bool]</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #008800; font-weight: bold">#lang racket/base</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/list</span>) <span style="color: #888888">;empty?, first, range</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/string</span>) <span style="color: #888888">;string-normalize-spaces</span>
<span style="color: #888888">;string-replace</span>
<span style="color: #888888">;string-split</span>
<span style="color: #888888">;(require racket/file) ;file->string (for tests)</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Data Types</span>
<span style="color: #888888">; An ISBN is one of:</span>
<span style="color: #888888">; - ISBN-13</span>
<span style="color: #888888">; - ISBN-10</span>
<span style="color: #888888">; An ISBN-String is one of:</span>
<span style="color: #888888">; - ISBN-13-String</span>
<span style="color: #888888">; - ISBN-10-String</span>
<span style="color: #888888">; An ISBN-13 is an valid ISBN-13-String</span>
<span style="color: #888888">; An ISBN-10 is an valid ISBN-10-String</span>
<span style="color: #888888">; where valid means that the numbers in that string</span>
<span style="color: #888888">; fullfil a certain mathematical computation. (See</span>
<span style="color: #888888">; ISBN User Manual for information about this computation)</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-13-ex</span> <span style="background-color: #fff0f0">"9781593274917"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-ex</span> <span style="background-color: #fff0f0">"0262062186"</span>)
<span style="color: #888888">; An ISBN-13-String is a String consisting of 13 digits,</span>
<span style="color: #888888">; where a digit is one of '0', '1', ..., '9'.</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-13-str-ex</span> <span style="background-color: #fff0f0">"1234567890123"</span>)
<span style="color: #888888">; An ISBN-10-String is a String consisting of 9 digits,</span>
<span style="color: #888888">; and a last letter that can be a digit or 'X'.</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-str-ex-1</span> <span style="background-color: #fff0f0">"1234567890"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-str-ex-2</span> <span style="background-color: #fff0f0">"123456789X"</span>)
<span style="color: #888888">; An ISBN-Format is one of:</span>
<span style="color: #888888">; - 'isbn-13</span>
<span style="color: #888888">; - 'isbn-10</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Patterns and Regexes</span>
<span style="color: #888888">; [See ISBN International User Manual 7e. Sect. 5]</span>
<span style="color: #888888">; - Pattern Components</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-sep</span> <span style="background-color: #fff0f0">"[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-id</span> <span style="background-color: #fff0f0">"ISBN(-1[03])?:? "</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-13)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-10)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"97[89][ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">"\\d{1,5}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registrant</span> <span style="background-color: #fff0f0">"\\d{1,7}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-publication</span> <span style="background-color: #fff0f0">"\\d{1,6}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-check</span> <span style="background-color: #fff0f0">"\\d"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-check</span> <span style="background-color: #fff0f0">"[X\\d]"</span>)
<span style="color: #888888">; - Look ahead to ISBN groups</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
<span style="color: #888888">; - Main patterns</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span>
<span style="color: #996633">pat-isbn-13-look-ahead</span>
<span style="color: #996633">pat-isbn-prefix</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-13-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-id</span>
<span style="color: #996633">pat-isbn-10-look-ahead</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-10-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/prefix</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"\\d{10}"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"\\d{13}"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"\\d{9}[X\\d]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/prefix</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10</span>))
<span style="color: #888888">; - Regexes</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-id</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-id</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-sep</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-sep</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Predicates</span>
<span style="color: #888888">#|</span>
<span style="color: #888888">(check-expect (isbn? "9781593274917") #t)</span>
<span style="color: #888888">(check-expect (isbn? "0262062186") #t)</span>
<span style="color: #888888">(check-expect (isbn? #f) #f)</span>
<span style="color: #888888"> </span>
<span style="color: #888888">(check-expect (isbn-13? "9781593274917") #t)</span>
<span style="color: #888888">(check-expect (isbn-13? "0262062186") #f)</span>
<span style="color: #888888">(check-expect (isbn-13? "") #f)</span>
<span style="color: #888888"> </span>
<span style="color: #888888">(check-expect (isbn-10? "9781593274917") #f)</span>
<span style="color: #888888">(check-expect (isbn-10? "0262062186") #t)</span>
<span style="color: #888888">(check-expect (isbn-10? 1) #f)</span>
<span style="color: #888888"> </span>
<span style="color: #888888">(check-expect (isbn-string? "9781593274912") #t)</span>
<span style="color: #888888">(check-expect (isbn-string? "026206218X") #t)</span>
<span style="color: #888888">(check-expect (isbn-string? "97815932749122") #f) ;too long</span>
<span style="color: #888888">(check-expect (isbn-string? "978159327491") #f) ;too short</span>
<span style="color: #888888">(check-expect (isbn-string? "0262062189X") #f) ;too long</span>
<span style="color: #888888">(check-expect (isbn-string? "026206218") #f) ;too short</span>
<span style="color: #888888">(check-expect (isbn-string? "0-262-06218-6") #f)</span>
<span style="color: #888888"> </span>
<span style="color: #888888">(check-expect (isbn-13-string? "9781593274912") #t)</span>
<span style="color: #888888">(check-expect (isbn-13-string? "97815932749122") #f) ;too long</span>
<span style="color: #888888">(check-expect (isbn-13-string? "978159327491") #f) ;too short</span>
<span style="color: #888888">(check-expect (isbn-13-string? #f) #f)</span>
<span style="color: #888888"> </span>
<span style="color: #888888">(check-expect (isbn-10-string? "026206218X") #t)</span>
<span style="color: #888888">(check-expect (isbn-10-string? "0262062189X") #f) ;too long</span>
<span style="color: #888888">(check-expect (isbn-10-string? "026206218") #f) ;too short</span>
<span style="color: #888888">(check-expect (isbn-10-string? #f) #f)</span>
<span style="color: #888888"> </span>
<span style="color: #888888">(check-expect (isbn-format? 'isbn-13) #t)</span>
<span style="color: #888888">(check-expect (isbn-format? 'isbn-10) #t)</span>
<span style="color: #888888">(check-expect (isbn-format? "isbn-10") #f)</span>
<span style="color: #888888">|#</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-13-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-10-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-13</span>) (<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-10</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Validation</span>
<span style="color: #888888">; ISBN-13-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-13 string a valid isbn-13</span>
<span style="color: #888888">#|</span>
<span style="color: #888888">(check-expect (isbn-13-valid? "9781593274917") #t)</span>
<span style="color: #888888">(check-expect (isbn-13-valid? "9780201896831") #t)</span>
<span style="color: #888888">(check-expect (isbn-13-valid? "9781593274912") #f)</span>
<span style="color: #888888">(check-expect (isbn-13-valid? "9780201896834") #f)</span>
<span style="color: #888888">|#</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span>)
<span style="color: #0000DD; font-weight: bold">10</span>))
<span style="color: #888888">; ISBN-10-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-10 string a valid isbn-10</span>
<span style="color: #888888">#|</span>
<span style="color: #888888">(check-expect (isbn-10-valid? "0262062186") #t)</span>
<span style="color: #888888">(check-expect (isbn-10-valid? "026256114X") #t)</span>
<span style="color: #888888">(check-expect (isbn-10-valid? "026206218X") #f)</span>
<span style="color: #888888">(check-expect (isbn-10-valid? "0262561141") #f)</span>
<span style="color: #888888">|#</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>))
<span style="color: #888888">; [List-of N] [List-of N] N -> Boolean</span>
<span style="color: #888888">; abstract checksum algorithm for isbn validation</span>
<span style="color: #888888">#|</span>
<span style="color: #888888">(check-expect (isbn-checksumf '(9 7 8 1 5 9 3 2 7 4 9 1 7)</span>
<span style="color: #888888"> '(1 3 1 3 1 3 1 3 1 3 1 3 1)</span>
<span style="color: #888888"> 10) #t)</span>
<span style="color: #888888">(check-expect (isbn-checksumf '(0 2 6 2 0 6 2 1 8 10)</span>
<span style="color: #888888"> (range 10 0 -1)</span>
<span style="color: #888888"> 11) #f)</span>
<span style="color: #888888">|#</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #996633">multiplicands</span> <span style="color: #996633">multipliers</span> <span style="color: #996633">mod</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">sum</span> (<span style="color: #0066BB; font-weight: bold">foldl</span> <span style="color: #996633">+</span> <span style="color: #0000DD; font-weight: bold">0</span> (<span style="color: #007020">map </span><span style="color: #996633">*</span> <span style="color: #996633">multiplicands</span> <span style="color: #996633">multipliers</span>)))
(<span style="color: #007020">zero? </span>(<span style="color: #007020">modulo </span><span style="color: #996633">sum</span> <span style="color: #996633">mod</span>)))
<span style="color: #888888">; ISBN-String -> [List-of N]</span>
<span style="color: #888888">; translates str into the numbers their isbn letters represent</span>
<span style="color: #888888">#|</span>
<span style="color: #888888">(check-expect (isbn-string->numbers "026256114X")</span>
<span style="color: #888888"> '(0 2 6 2 5 6 1 1 4 10))</span>
<span style="color: #888888">|#</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">str</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-digit->number</span> <span style="color: #996633">char</span>)
(<span style="color: #0066BB; font-weight: bold">cond</span>
[(<span style="color: #007020">char=? </span><span style="color: #996633">char</span> <span style="color: #0044DD">#\X</span>) <span style="color: #0000DD; font-weight: bold">10</span>]
[<span style="color: #0066BB; font-weight: bold">else</span> (<span style="color: #007020">string </span><span style="color: #996633">char</span>)]))
(<span style="color: #007020">map </span><span style="color: #996633">isbn-digit->number</span> (<span style="color: #007020">string->list </span><span style="color: #996633">str</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Extraction</span>
<span style="color: #888888">; String -> [List-of ISBN]</span>
<span style="color: #888888">; extracts all isbns from str</span>
<span style="color: #888888">#| </span>
<span style="color: #888888">(check-expect (isbn-find/list "") '())</span>
<span style="color: #888888">(check-expect (isbn-find/list "none") '())</span>
<span style="color: #888888">(check-expect</span>
<span style="color: #888888"> (isbn-find/list (file->string "test-isbn-examples"))</span>
<span style="color: #888888"> (list</span>
<span style="color: #888888"> ;isbn normalized</span>
<span style="color: #888888"> "0262062186" "026256114X" "1593274912"</span>
<span style="color: #888888"> "9781593274917" "0201896834" "9780201896831"</span>
<span style="color: #888888"> ;isbn w/ several id's a sep's</span>
<span style="color: #888888"> "0262062186" "026256114X" "0262062186" "0201896834"</span>
<span style="color: #888888"> "9780201896831" "026256114X" "9780201896831"))</span>
<span style="color: #888888">|#</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="color: #996633">str</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">candidates</span>
(<span style="color: #0066BB; font-weight: bold">foldr</span> <span style="color: #996633">append</span> <span style="color: #333333">'</span>()
(<span style="color: #007020">map </span><span style="color: #996633">isbn-match*</span> (<span style="color: #0066BB; font-weight: bold">string-split</span> <span style="color: #996633">str</span> <span style="background-color: #fff0f0">"\n"</span>))))
(<span style="color: #0066BB; font-weight: bold">filter</span> <span style="color: #996633">isbn?</span> (<span style="color: #007020">map </span><span style="color: #996633">isbn-normalize</span> <span style="color: #996633">candidates</span>)))
<span style="color: #888888">; String ISBN-Format -> [Maybe ISBN]</span>
<span style="color: #888888">; extracts the first isbn of given format from str, if any</span>
<span style="color: #888888">#|</span>
<span style="color: #888888">(check-expect (isbn-find "" 'isbn-13) #f)</span>
<span style="color: #888888">(check-expect (isbn-find "" 'isbn-10) #f)</span>
<span style="color: #888888">(check-expect (isbn-find "0262062186" 'isbn-13) #f)</span>
<span style="color: #888888">(check-expect (isbn-find "9781593274917" 'isbn-10) #f)</span>
<span style="color: #888888">(check-expect (isbn-find (file->string "test-isbn-examples")</span>
<span style="color: #888888"> 'isbn-13)</span>
<span style="color: #888888"> "9781593274917")</span>
<span style="color: #888888">(check-expect (isbn-find (file->string "test-isbn-examples")</span>
<span style="color: #888888"> 'isbn-10)</span>
<span style="color: #888888"> "0262062186")</span>
<span style="color: #888888">|#</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="color: #996633">str</span> <span style="color: #996633">format</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">p?</span>
(<span style="color: #008800; font-weight: bold">cond </span>[(<span style="color: #007020">equal? </span><span style="color: #996633">format</span> <span style="color: #AA6600">'isbn-13</span>) <span style="color: #996633">isbn-13?</span>]
[(<span style="color: #007020">equal? </span><span style="color: #996633">format</span> <span style="color: #AA6600">'isbn-10</span>) <span style="color: #996633">isbn-10?</span>]))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbns</span>
(<span style="color: #0066BB; font-weight: bold">filter</span> <span style="color: #996633">p?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="color: #996633">str</span>)))
(<span style="color: #0066BB; font-weight: bold">cond</span>
[(<span style="color: #0066BB; font-weight: bold">empty?</span> <span style="color: #996633">isbns</span>) <span style="color: #003366; font-weight: bold">#f</span>]
[<span style="color: #0066BB; font-weight: bold">else</span> (<span style="color: #0066BB; font-weight: bold">first</span> <span style="color: #996633">isbns</span>)]))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Helpers</span>
<span style="color: #888888">; String -> [List-of String]</span>
<span style="color: #888888">; matches substrings in the given string looking like isbn tags</span>
<span style="color: #888888">#|</span>
<span style="color: #888888">(check-expect (isbn-match* "") '())</span>
<span style="color: #888888">(check-expect (isbn-match* "abc\nd") '())</span>
<span style="color: #888888">(check-expect</span>
<span style="color: #888888"> (isbn-match* (file->string "test-isbn-examples"))</span>
<span style="color: #888888"> (list</span>
<span style="color: #888888"> ;isbn normalized (all matched)</span>
<span style="color: #888888"> "0262062186" "026256114X" "1593274912"</span>
<span style="color: #888888"> "9781593274917" "0201896834" "9780201896831"</span>
<span style="color: #888888"> ;isbn w/ several id's and sep's (all matched)</span>
<span style="color: #888888"> "ISBN 0-262-06218-6"</span>
<span style="color: #888888"> "ISBN: 0 262 56114 X"</span>
<span style="color: #888888"> "ISBN-10 0 262 06218-6"</span>
<span style="color: #888888"> "ISBN-10: 0-201-89683-4"</span>
<span style="color: #888888"> "ISBN-13: 978-0-201-89683-1"</span>
<span style="color: #888888"> "ISBN-10: 0 262 56114 X"</span>
<span style="color: #888888"> "ISBN-13: 978-0201896831"</span>
<span style="color: #888888"> ;not isbn strings (usually impossible in real-world)</span>
<span style="color: #888888"> "026206218X" "0262561141" "9780201896834" ;partially matched</span>
<span style="color: #888888"> ;isbn strings, but isbn invalid (all matched)</span>
<span style="color: #888888"> "026206218X" "0262561141" "1593274913"</span>
<span style="color: #888888"> "9781593274912" "0201896833" "9780201896834"))</span>
<span style="color: #888888">|#</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">regexp-match*</span> <span style="color: #996633">re-isbn</span> (<span style="color: #0066BB; font-weight: bold">string-normalize-spaces</span> <span style="color: #996633">str</span>)))
<span style="color: #888888">; String -> String</span>
<span style="color: #888888">; removes the isbn-id and, then, the isbn separators from str</span>
<span style="color: #888888">#|</span>
<span style="color: #888888">(check-expect (isbn-normalize "123-45 67") "1234567")</span>
<span style="color: #888888">(check-expect (isbn-normalize "ISBN 123") "123")</span>
<span style="color: #888888">(check-expect (isbn-normalize "ISBN: 123") "123")</span>
<span style="color: #888888">(check-expect (isbn-normalize "ISBN-10 123") "123")</span>
<span style="color: #888888">(check-expect (isbn-normalize "ISBN-10: 123") "123")</span>
<span style="color: #888888">(check-expect (isbn-normalize "ISBN-13 123") "123")</span>
<span style="color: #888888">(check-expect (isbn-normalize "ISBN-13: 123") "123")</span>
<span style="color: #888888">(check-expect (isbn-normalize "ISBN-14: hi") "ISBN14:hi")</span>
<span style="color: #888888">|#</span>
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">string-replace</span>
(<span style="color: #0066BB; font-weight: bold">string-replace</span> <span style="color: #996633">str</span> <span style="color: #996633">re-isbn-id</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #996633">re-isbn-sep</span> <span style="background-color: #fff0f0">""</span>))
</pre></div>
<div>
<p>Next article in the series: <a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_70.html">Racket: <code>only-in</code>, <code>rackunit</code>, test submodules</a></p>
</div>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-5464979927363736012019-04-16T18:45:00.000+02:002019-08-14T10:29:42.222+02:00From HtDP to Racket. ISBN extraction in ISL+<p><em>WARNING: This series is deprecated. The up-to-date version can be found <a href="https://los-pajaros-de-hogano.blogspot.com/2019/08/from-htdp-to-racket-new-version.html">here</a></em></p>
<div id="toc" style="font-size:90%">
<p><em>TOC of the series</em></p>
<ol style="list-style-type: decimal">
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in.html">ISBN Extraction in ISL+ with regular expressions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_17.html">Racket: <code>#lang</code>, local definitions</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_70.html">Racket: <code>only-in</code>, <code>rackunit</code>, test submodules</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction.html">Racket: <code>for</code>, <code>match</code>, sequences</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_17.html">Racket: <code>provide</code>, contracts</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction_24.html">Racket: I/O, optional and keyword arguments, point-free style</a></li>
<li><a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-addons.html">Racket: exception handling, http requests, JSON</a></li>
</ol>
</div>
</hr>
<p>If you have read the book <a href="https://htdp.org/2018-01-06/Book/index.html">How to Design Programs (HtDP)</a> you already know some parts of Racket, the language on which BSL, ISL, etc. are built. And you may wish to get more stuff about Racket itself and find out how to translate your HtDP skills into full Racket.</p>
<p>You could read the book <a href="https://nostarch.com/realmofracket.htm">Realm of Racket</a>, where Racket is introduced as needed in a friendly setting familiar to HtDP readers. You could even try <a href="https://docs.racket-lang.org/guide/index.html">The Racket Guide</a>, a gently and complete introduction to the language. But you may miss something in the middle, something not so terse as <em>The Guide</em>, written from (professional) programmers to (professional) programmers, but more systematic than the <em>Realm</em>.</p>
<p>That's the intent of this series of posts. Its purpose is to show by means of an example how to start the transition from HtDP languages (in particular, ISL+) to Racket. At least, what new things to consider when working directly in Racket.</p>
<p>An example cannot be complete at all, though. Racket is a very rich language, and sooner than later you'll need to go deeply into <em>The Guide</em> and <a href="https://docs.racket-lang.org/index.html">The Racket Reference</a>. So take them as a first step into Racket for whom already masters HtDP.</p>
<p>A public repository for the code presented in this series is available at <a href="https://gitlab.com/lsanjuan/from-htdp-to-racket">FromHtDPtoRacket (git repo)</a></p>
<p>The sample problem we are going to tackle says as follows:</p>
<p>Create a program to obtain bibliographic information about pdf books. It is assumed that those pdfs are readable (meaning that they are not made of photos of pages) and, for simplicity, unencrypted.</p>
<p>This task can be naturally designed as a composition of the following sub-tasks:</p>
<ol style="list-style-type: decimal">
<li>Read the text contained in the pdf pages.</li>
<li>Search for the ISBN on those pages.</li>
<li>Retrieve from a remote provider information about the book with that ISBN.</li>
<li>Parse the response to represent it as a Racket data type.</li>
</ol>
<p>The second sub-task will be implemented entirely in ISL+, with the aid of a few Racket batteries. From the ISL+ implementation a Racket version will be derived. How to go from the ISL version to the Racket version is the main subject of these posts.</p>
<p>For completeness, an initial solution to the rest of the steps will be given directly in Racket.</p>
<p>Regarding the exposition I don't want to be verbose in describing all Racket constructs presented. The aim is not to explain Racket. Instead, I'll introduce those constructs when needed, and point to places in the Racket documentation that explain them.</p>
<p>Let's get started with the second step, the extraction of the ISBN, if any, from an input string.</p>
<p>This involves, of course, domain knowledge. See <a href="https://www.isbn-international.org/content/isbn-users-manual">https://www.isbn-international.org/content/isbn-users-manual</a> for more information (specifically, Section 5).</p>
<p>In summary, an ISBN can be of two types (formats): ISBN-10 and ISBN-13. The number itself is a string of 13 or 10 digits (from '0' to '9'), respectively. ISBN-10 accepts 'X' as last digit, too. An ISBN-10 number consists of four groups of digits, three of them of variable length (the first group can have up to 5 digits; the second up to 7 digits; the third up to 6 digits), the last group is a single digit. In turn, an ISBN-13 has one more group, a prefix of 3 digits, currently: 978 or 979. Groups may appear separated by hyphens or spaces. So we could see forms like the following:</p>
<pre><code>0262062186
0-262-06218-6
0 262 06218 6
9780201896831
978-0-201-89683-1
978 0 201 89683 1
978-0201896831</code></pre>
<p>In printed books, that number is typically preceded by an identifier that can appear under different guises:</p>
<pre><code>ISBN
ISBN:
ISBN-10
ISBN-13
ISBN-10:
ISBN-13:</code></pre>
<p>followed by a space before the number itself.</p>
<p>Additionally, a string matching that format is an actual ISBN if it passes a validity check, that involves an arithmetic operation. See the ISBN User Manual cited above for a description of the algorithm.</p>
<p>Although it is possible to devise a pure ISL+ implementation, it is better to resort to Racket regular expressions support, if only because the resulting Racket version will share the very same design with the ISL-with-regexes version. Otherwise, the ISL code would be quite different, and the translation into Racket not so seamless.</p>
<p>Regular expressions support is in the core of Racket, so we need to require <code>racket/base</code> to use it. A warning, though: don't require <code>racket/base</code> in your ISL projects. if you really need it, just get into full Racket.</p>
<p>We will also require a couple of libraries to apply a few functions: <code>file->string</code> from <code>racket/file</code> for some test cases (you could use in place the function <code>read-file</code> from <code>2htdp/batch-io</code>), and, for brevity, three string functions, that you could design on your own by following the HtDP recipe (<code>string-split</code>, <code>string-replace</code>, and <code>string-normalize-spaces</code>).</p>
<p>The first section of the code is, as HtDP mandates, a description of the data types we will use to represent the information at hand.</p>
<p>Next, patterns and regular expressions embedding the description of ISBN forms described above are created.</p>
<p>For details about regular expressions patterns and the functions <code>regexp-match-exact?</code> and <code>regexp-match*</code> see <a href="https://docs.racket-lang.org/guide/regexp.html">Regular Expressions [Racket Guide]</a>, and <a href="https://docs.racket-lang.org/reference/regexp.html?q=regexp">Regular Expressions [Racket Reference]</a>
<p>The next part contains predicates concerning all the data types of the first section. In fact, those predicates should be enough for any programmer as a precise description of the data types involved.</p>
<p>The rest proposes an implementation. It begins with functions to validate isbn-string's according to the required mathematical algorithm. (Note, by the way, that <code>isbn-checksumf</code> is an abstract function obtained by following the design recipe explained in <a href="https://htdp.org/2018-01-06/Book/part_three.html">HtDP/2e (Part III)</a>)</p>
<p>The main functions are <code>isbn-find/list</code> and <code>isbn-find</code>, which produce a list of all isbn's found in a string, or the first isbn of a given isbn-format found in a string, respectively. The basic strategy is to search for sub-strings in all the lines of the input that look like an ISBN, and select from the candidates those that, previously normalized, are actual ISBNs.</p>
<p>This is the code:</p>
<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%"><span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; isbn-isl-with-batteries.rkt</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/base</span>) <span style="color: #888888">;for regexes support</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/string</span>) <span style="color: #888888">;string-normalize-spaces</span>
<span style="color: #888888">;string-replace</span>
<span style="color: #888888">;string-split</span>
(<span style="color: #008800; font-weight: bold">require </span><span style="color: #996633">racket/file</span>) <span style="color: #888888">;file->string (for tests)</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Data Types</span>
<span style="color: #888888">; An ISBN is one of:</span>
<span style="color: #888888">; - ISBN-13</span>
<span style="color: #888888">; - ISBN-10</span>
<span style="color: #888888">; An ISBN-String is one of:</span>
<span style="color: #888888">; - ISBN-13-String</span>
<span style="color: #888888">; - ISBN-10-String</span>
<span style="color: #888888">; An ISBN-13 is an valid ISBN-13-String</span>
<span style="color: #888888">; An ISBN-10 is an valid ISBN-10-String</span>
<span style="color: #888888">; where valid means that the numbers in that string</span>
<span style="color: #888888">; fullfil a certain mathematical computation. (See</span>
<span style="color: #888888">; ISBN User Manual for information about this computation)</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-13-ex</span> <span style="background-color: #fff0f0">"9781593274917"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-ex</span> <span style="background-color: #fff0f0">"0262062186"</span>)
<span style="color: #888888">; An ISBN-13-String is a String consisting of 13 digits,</span>
<span style="color: #888888">; where a digit is one of '0', '1', ..., '9'.</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-13-str-ex</span> <span style="background-color: #fff0f0">"1234567890123"</span>)
<span style="color: #888888">; An ISBN-10-String is a String consisting of 9 digits,</span>
<span style="color: #888888">; and a last letter that can be a digit or 'X'.</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-str-ex-1</span> <span style="background-color: #fff0f0">"1234567890"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbn-10-str-ex-2</span> <span style="background-color: #fff0f0">"123456789X"</span>)
<span style="color: #888888">; An ISBN-Format is one of:</span>
<span style="color: #888888">; - 'isbn-13</span>
<span style="color: #888888">; - 'isbn-10</span>
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Patterns and Regexes</span>
<span style="color: #888888">; [See ISBN International User Manual 7e. Sect. 5]</span>
<span style="color: #888888">; - Pattern Components</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-sep</span> <span style="background-color: #fff0f0">"[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-id</span> <span style="background-color: #fff0f0">"ISBN(-1[03])?:? "</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-13)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-id</span> <span style="background-color: #fff0f0">"(?:ISBN(?:-10)?:? )?"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"97[89][ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">"\\d{1,5}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-registrant</span> <span style="background-color: #fff0f0">"\\d{1,7}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-publication</span> <span style="background-color: #fff0f0">"\\d{1,6}[ -]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-check</span> <span style="background-color: #fff0f0">"\\d"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-check</span> <span style="background-color: #fff0f0">"[X\\d]"</span>)
<span style="color: #888888">; - Look ahead to ISBN groups</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-look-ahead</span>
(<span style="color: #007020">string-append </span><span style="background-color: #fff0f0">"(?="</span> <span style="color: #996633">pat-isbn-registration</span> <span style="background-color: #fff0f0">")"</span>))
<span style="color: #888888">; - Main patterns</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span>
<span style="color: #996633">pat-isbn-13-look-ahead</span>
<span style="color: #996633">pat-isbn-prefix</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-13-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10/groups</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-id</span>
<span style="color: #996633">pat-isbn-10-look-ahead</span>
<span style="color: #996633">pat-isbn-registration</span>
<span style="color: #996633">pat-isbn-registrant</span>
<span style="color: #996633">pat-isbn-publication</span>
<span style="color: #996633">pat-isbn-10-check</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13/prefix</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-id</span> <span style="color: #996633">pat-isbn-prefix</span> <span style="background-color: #fff0f0">"\\d{10}"</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"\\d{13}"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"\\d{9}[X\\d]"</span>)
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-13</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/prefix</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-13/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn-10</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-10-norm</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10/groups</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">pat-isbn</span>
(<span style="color: #007020">string-append </span><span style="color: #996633">pat-isbn-13</span> <span style="background-color: #fff0f0">"|"</span>
<span style="color: #996633">pat-isbn-10</span>))
<span style="color: #888888">; - Regexes</span>
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-id</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-id</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-sep</span> (<span style="color: #007020">regexp </span><span style="color: #996633">pat-isbn-sep</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-13-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-13-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn-10-norm</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn-10-norm</span>))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">re-isbn</span> (<span style="color: #007020">pregexp </span><span style="color: #996633">pat-isbn</span>))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Predicates</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"9781593274917"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="background-color: #fff0f0">"0262062186"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"9781593274917"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">"0262062186"</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"9781593274917"</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="background-color: #fff0f0">"0262062186"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #0000DD; font-weight: bold">1</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>) <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>) <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>) <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"026206218"</span>) <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="background-color: #fff0f0">"0-262-06218-6"</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"9781593274912"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"97815932749122"</span>) <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="background-color: #fff0f0">"978159327491"</span>) <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218X"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"0262062189X"</span>) <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #888888">;too long</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="background-color: #fff0f0">"026206218"</span>) <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #888888">;too short</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #003366; font-weight: bold">#f</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-13</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #AA6600">'isbn-10</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="background-color: #fff0f0">"isbn-10"</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-13-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-string?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">and </span>(<span style="color: #007020">string? </span><span style="color: #996633">v</span>) (<span style="color: #0066BB; font-weight: bold">regexp-match-exact?</span> <span style="color: #996633">re-isbn-10-norm</span> <span style="color: #996633">v</span>)))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-format?</span> <span style="color: #996633">v</span>)
(<span style="color: #008800; font-weight: bold">or </span>(<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-13</span>) (<span style="color: #007020">equal? </span><span style="color: #996633">v</span> <span style="color: #AA6600">'isbn-10</span>)))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Validation</span>
<span style="color: #888888">; ISBN-13-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-13 string a valid isbn-13</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274917"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896831"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9781593274912"</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="background-color: #fff0f0">"9780201896834"</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-13-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span>)
<span style="color: #0000DD; font-weight: bold">10</span>))
<span style="color: #888888">; ISBN-10-String -> Boolean</span>
<span style="color: #888888">; is the given isbn-10 string a valid isbn-10</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262062186"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026256114X"</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"026206218X"</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="background-color: #fff0f0">"0262561141"</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-10-valid?</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">isbn-str</span>)
(<span style="color: #0066BB; font-weight: bold">range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>))
<span style="color: #888888">; [List-of N] [List-of N] N -> Boolean</span>
<span style="color: #888888">; abstract checksum algorithm for isbn validation</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">7</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">9</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">7</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">3</span> <span style="color: #0000DD; font-weight: bold">1</span>)
<span style="color: #0000DD; font-weight: bold">10</span>) <span style="color: #003366; font-weight: bold">#t</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">8</span> <span style="color: #0000DD; font-weight: bold">10</span>)
(<span style="color: #0066BB; font-weight: bold">range</span> <span style="color: #0000DD; font-weight: bold">10</span> <span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">-1</span>)
<span style="color: #0000DD; font-weight: bold">11</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-checksumf</span> <span style="color: #996633">multiplicands</span> <span style="color: #996633">multipliers</span> <span style="color: #996633">mod</span>)
(<span style="color: #0066BB; font-weight: bold">local</span> [(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">sum</span>
(<span style="color: #0066BB; font-weight: bold">foldl</span> <span style="color: #996633">+</span> <span style="color: #0000DD; font-weight: bold">0</span> (<span style="color: #007020">map </span><span style="color: #996633">*</span> <span style="color: #996633">multiplicands</span> <span style="color: #996633">multipliers</span>)))]
(<span style="color: #007020">zero? </span>(<span style="color: #007020">modulo </span><span style="color: #996633">sum</span> <span style="color: #996633">mod</span>))))
<span style="color: #888888">; ISBN-String -> [List-of N]</span>
<span style="color: #888888">; translates str into the numbers their isbn letters represent</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="background-color: #fff0f0">"026256114X"</span>)
<span style="color: #333333">'</span>(<span style="color: #0000DD; font-weight: bold">0</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">2</span> <span style="color: #0000DD; font-weight: bold">5</span> <span style="color: #0000DD; font-weight: bold">6</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">1</span> <span style="color: #0000DD; font-weight: bold">4</span> <span style="color: #0000DD; font-weight: bold">10</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-string->numbers</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">local</span> [(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-digit->number</span> <span style="color: #996633">d</span>)
(<span style="color: #0066BB; font-weight: bold">cond</span>
[(<span style="color: #007020">string=? </span><span style="color: #996633">d</span> <span style="background-color: #fff0f0">"X"</span>) <span style="color: #0000DD; font-weight: bold">10</span>]
[<span style="color: #0066BB; font-weight: bold">else</span> (<span style="color: #007020">string->number </span><span style="color: #996633">d</span>)]))]
(<span style="color: #007020">map </span><span style="color: #996633">isbn-digit->number</span> (<span style="color: #0066BB; font-weight: bold">explode</span> <span style="color: #996633">str</span>))))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; ISBN Extraction</span>
<span style="color: #888888">; String -> [List-of ISBN]</span>
<span style="color: #888888">; extracts all isbns from str</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="background-color: #fff0f0">"none"</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-expect</span>
(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>))
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's a sep's</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"0201896834"</span>
<span style="background-color: #fff0f0">"9780201896831"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"9780201896831"</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">local</span> [(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">candidates</span>
(<span style="color: #0066BB; font-weight: bold">foldr</span> <span style="color: #996633">append</span> <span style="color: #333333">'</span>()
(<span style="color: #007020">map </span><span style="color: #996633">isbn-match*</span> (<span style="color: #0066BB; font-weight: bold">string-split</span> <span style="color: #996633">str</span> <span style="background-color: #fff0f0">"\n"</span>))))]
(<span style="color: #0066BB; font-weight: bold">filter</span> <span style="color: #996633">isbn?</span> (<span style="color: #007020">map </span><span style="color: #996633">isbn-normalize</span> <span style="color: #996633">candidates</span>))))
<span style="color: #888888">; String ISBN-Format -> [Maybe ISBN]</span>
<span style="color: #888888">; extracts the first isbn of given format from str, if any</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">""</span> <span style="color: #AA6600">'isbn-13</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">""</span> <span style="color: #AA6600">'isbn-10</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">"0262062186"</span> <span style="color: #AA6600">'isbn-13</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="background-color: #fff0f0">"9781593274917"</span> <span style="color: #AA6600">'isbn-10</span>) <span style="color: #003366; font-weight: bold">#f</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>)
<span style="color: #AA6600">'isbn-13</span>)
<span style="background-color: #fff0f0">"9781593274917"</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-find</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>)
<span style="color: #AA6600">'isbn-10</span>)
<span style="background-color: #fff0f0">"0262062186"</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-find</span> <span style="color: #996633">str</span> <span style="color: #996633">format</span>)
(<span style="color: #0066BB; font-weight: bold">local</span> [(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">p?</span>
(<span style="color: #008800; font-weight: bold">cond </span>[(<span style="color: #0066BB; font-weight: bold">symbol=?</span> <span style="color: #996633">format</span> <span style="color: #AA6600">'isbn-13</span>) <span style="color: #996633">isbn-13?</span>]
[(<span style="color: #0066BB; font-weight: bold">symbol=?</span> <span style="color: #996633">format</span> <span style="color: #AA6600">'isbn-10</span>) <span style="color: #996633">isbn-10?</span>]))
(<span style="color: #008800; font-weight: bold">define </span><span style="color: #996633">isbns</span>
(<span style="color: #0066BB; font-weight: bold">filter</span> <span style="color: #996633">p?</span> (<span style="color: #0066BB; font-weight: bold">isbn-find/list</span> <span style="color: #996633">str</span>)))]
(<span style="color: #0066BB; font-weight: bold">cond</span>
[(<span style="color: #0066BB; font-weight: bold">empty?</span> <span style="color: #996633">isbns</span>) <span style="color: #003366; font-weight: bold">#f</span>]
[<span style="color: #0066BB; font-weight: bold">else</span> (<span style="color: #0066BB; font-weight: bold">first</span> <span style="color: #996633">isbns</span>)])))
<span style="color: #888888">; ----------------------------------------------------------</span>
<span style="color: #888888">; Helpers</span>
<span style="color: #888888">; String -> [List-of String]</span>
<span style="color: #888888">; matches substrings in the given string looking like isbn tags</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="background-color: #fff0f0">"abc\nd"</span>) <span style="color: #333333">'</span>())
(<span style="color: #0066BB; font-weight: bold">check-expect</span>
(<span style="color: #0066BB; font-weight: bold">isbn-match*</span> (<span style="color: #0066BB; font-weight: bold">file->string</span> <span style="background-color: #fff0f0">"test-isbn-examples"</span>))
(<span style="color: #0066BB; font-weight: bold">list</span>
<span style="color: #888888">;isbn normalized (all matched)</span>
<span style="background-color: #fff0f0">"0262062186"</span> <span style="background-color: #fff0f0">"026256114X"</span> <span style="background-color: #fff0f0">"1593274912"</span>
<span style="background-color: #fff0f0">"9781593274917"</span> <span style="background-color: #fff0f0">"0201896834"</span> <span style="background-color: #fff0f0">"9780201896831"</span>
<span style="color: #888888">;isbn w/ several id's and sep's (all matched)</span>
<span style="background-color: #fff0f0">"ISBN 0-262-06218-6"</span>
<span style="background-color: #fff0f0">"ISBN: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-10 0 262 06218-6"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0-201-89683-4"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0-201-89683-1"</span>
<span style="background-color: #fff0f0">"ISBN-10: 0 262 56114 X"</span>
<span style="background-color: #fff0f0">"ISBN-13: 978-0201896831"</span>
<span style="color: #888888">;not isbn strings (usually impossible in real-world)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"9780201896834"</span> <span style="color: #888888">;partially matched</span>
<span style="color: #888888">;isbn strings, but isbn invalid (all matched)</span>
<span style="background-color: #fff0f0">"026206218X"</span> <span style="background-color: #fff0f0">"0262561141"</span> <span style="background-color: #fff0f0">"1593274913"</span>
<span style="background-color: #fff0f0">"9781593274912"</span> <span style="background-color: #fff0f0">"0201896833"</span> <span style="background-color: #fff0f0">"9780201896834"</span>))
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-match*</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">regexp-match*</span> <span style="color: #996633">re-isbn</span> (<span style="color: #0066BB; font-weight: bold">string-normalize-spaces</span> <span style="color: #996633">str</span>)))
<span style="color: #888888">; String -> String</span>
<span style="color: #888888">; removes the isbn-id and, then, the isbn separators from str</span>
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"123-45 67"</span>) <span style="background-color: #fff0f0">"1234567"</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-10: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-13: 123"</span>) <span style="background-color: #fff0f0">"123"</span>)
(<span style="color: #0066BB; font-weight: bold">check-expect</span> (<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="background-color: #fff0f0">"ISBN-14: hi"</span>) <span style="background-color: #fff0f0">"ISBN14:hi"</span>)
(<span style="color: #008800; font-weight: bold">define </span>(<span style="color: #0066BB; font-weight: bold">isbn-normalize</span> <span style="color: #996633">str</span>)
(<span style="color: #0066BB; font-weight: bold">string-replace</span>
(<span style="color: #0066BB; font-weight: bold">string-replace</span> <span style="color: #996633">str</span> <span style="color: #996633">re-isbn-id</span> <span style="background-color: #fff0f0">""</span>) <span style="color: #996633">re-isbn-sep</span> <span style="background-color: #fff0f0">""</span>))
</pre></div>
<div>
<p>Next article in the series: <a href="https://los-pajaros-de-hogano.blogspot.com/2019/04/from-htdp-to-racket-isbn-extraction-in_17.html">Racket: <code>#lang</code>, local definitions</a></p>
</div>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-30600425079189181032017-12-04T21:24:00.000+01:002017-12-05T14:04:49.805+01:00Algunos juegos de entrenamiento auditivo en Scratch<p>Durante las últimas semanas he estado echándole un vistazo al "lenguaje" de programación <a href="https://scratch.mit.edu">Scratch</a>. El objetivo de Scratch es que el principiante en programación (se piensa principalmente en niños) no tenga que vérselas con la sintaxis del lenguaje de turno y disponga de un entorno amigable y visual. Así, en lugar de escribir código, el niño manipula bloques visuales, como piezas de lego, con los que se montan los "códigos" (llamados "bloques" en terminología de Scratch).
<p>No tengo claro si esto realmente puede facilitar el aprendizaje a los más pequeños. En todo caso, Scratch para un programador resulta cuando menos problemático. A veces cosas sencillas de hacer en cualquier lenguaje, en Scratch resultan cosas engorrosas de hacer o enrevesadas. Hay, además, un buen número de limitaciones: no es posible realizar baterías de tests, no hay forma eficaz de construir imágenes mediante código, y un largo etcétera.
<p>Me resulta particularmente irritante que los procedimientos no sean realmente funciones, el hecho de que no puedan devolver valores, y en su lugar haya que modificar variables de estado, que en muchas ocasiones tienen que ser inevitablemente globales. No es una buena forma de aprender principios esenciales de programación.
<p>No obstante, por hablar de alguna cosa buena, siempre desde el punto de vista de un programador, Scratch, a diferencia de App Inventor, otro sistema parecido a Scratch, sí proporciona ciertas opciones de orientación a objetos gracias a la idea de "clonación" (se pueden crear objetos a partir de prototipos) y a través de un sistema de paso de "mensajes" (los objetos pueden pasar y recibir cierta clase elemental de mensajes). Eso sí parece bueno en cuanto a presentar al niño ciertos fundamentos serios de programación.
<p>Pero, sin duda, la mayor ventaja de Scratch es que con él va incluida una plataforma que permite subir a la nube las aplicaciones que se van construyendo y ejecutarlas desde el navegador. Esto libera al programador de buscarse la vida con JavaScript o similares, al menos hasta el momento en que <a href=http://webassembly.org/>WebAssembly</a> sea lo suficientemente maduro como para cubrir esta imperiosa necesidad independientemente del lenguaje. Eso sí, Scratch desgraciadamente require Flash, una tecnología obsoleta, pero es de esperar que esta situación sea temporal.
<p>Pero basta de introducciones, el objetivo de esta entrada no era hablar de Scratch, sino simplemente informar de que he creado algunos pequeños juegos en Scratch que tal vez sean útiles para los estudiantes de música. No están bien verificados, y están hechos algunos con prisa y todos sin especial atención al atractivo visual, pero ahí van por si a alguien le interesa entrenarse en un contexto más o menos lúdico:
<ul>
<li>
<a href="https://scratch.mit.edu/projects/190681442/">Entrenamiento en el reconocimiento de intervalos</a>
</li>
<li>
<a href="https://scratch.mit.edu/projects/190643342/">Entrenamiento en el reconocimiento de acordes de séptima</a>
</li>
<li>
<a href="https://scratch.mit.edu/projects/190606873/">Entrenamiento en el reconocimiento y memorización de secuencias melódicas dentro de la tonalidad</a>
</li>
<li>
<a href="https://scratch.mit.edu/projects/190920959/">Entrenamiento en la lectura musical para guitarristas</a> (en inglés)
</li>
</ul>
<p>Algunos comentarios a estas aplicaciones, por orden de importancia.
<p>La segunda aplicación, la del reconocimiento de acordes, es un tanto problemática. La mediocre calidad (midi inevitablemente) de la salida hace el reconocimiento de acordes bastante más difícil de lo que realmente es y debería ser; la ausencia de un sonido real, con todos sus armónicos, aquí es una importante desventaja.
<p>La tercera aplicación sobre memoria dentro de la tonalidad genera secuencias melódicas aleatorias. Aunque dentro de la tonalidad, éstas pueden ser en ocasiones forzadas, y además no hay variación métrica alguna. Esto es artificial, pero un programa con secuencias melódicas más naturales y con métricas variadas es totalmente otro cantar, nada que se pueda hacer en unas horas y con Scratch, requeriría inteligencia artificial, sin duda, y un lenguaje de programación de verdad. En todo caso, tal como está, creo que sigue sirviendo, al menos como puro y duro entrenamiento de la memoria y el reconocimiento auditivo.
<p>Las tres primeras aplicaciones poseen su interfaz en castellano, pero el código está, salvo mínimas excepciones necesarias, en inglés, ya que son meras copias de las versiones inglesas que hice en primer lugar.
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-70444247934965249662017-04-26T15:58:00.001+02:002017-04-26T16:45:36.369+02:00The Game of Life implemented in functional style (Racket) and with complex numbers<p>If you're reading this you probably know what The Game of Life is. If not, visit the Wikipedia for more information:</p>
<p>https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life</p>
<p>You may want to just recall the rules governing those aggregates of live cells called polyominos. They are very simple:</p>
<ul>
<li><p>a live cell with more than 3 live neighbors (where neighbors of a cell are those cells adjacent to it in every direction) dies (over-population).</p></li>
<li><p>a live cell with less than 2 live neighbors dies (under-population).</p></li>
<li><p>a dead cell with exactly 3 live neighbors becomes live.</p></li>
</ul>
<p>The following diagrams clarify the rules and serve as the preliminary analysis of the problem domain.</p>
<p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnwol7OXZNOTB9th7xohVVLOUP_uxjlz3J26DWEuWAu4NYgXLyR-Y71odY5KBF2_3jNQXfD68gbimbJ2ivFA5ZKFnKd-kuyNfVCXe8NXxdrGuL8TCRh4vToLse3P8r-LqwvBXqvM61_r0/s1600/gol-examples.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnwol7OXZNOTB9th7xohVVLOUP_uxjlz3J26DWEuWAu4NYgXLyR-Y71odY5KBF2_3jNQXfD68gbimbJ2ivFA5ZKFnKd-kuyNfVCXe8NXxdrGuL8TCRh4vToLse3P8r-LqwvBXqvM61_r0/s400/gol-examples.png" width="400" height="387" /></a></p>
<p>And now the code. It is written in a language based on Racket called "Intermediate Student Language with lambda". It can be run on DrRacket (https://download.racket-lang.org/) by setting "Language" to that. ISL+ is a purely functional language that, additionally, allows the use of anonymous functions via lambda expressions. I have also included the Racket module for working with sets (<code>require racket/set</code>). You can, of course, define your own implementation of the set functions applied, instead. They are easy to define. For supporting graphics and interactivity two more modules are required: <code>2htdp/image</code> and <code>2htdp/universe</code>.</p>
<p>Regarding the kind of data to represent the domain information I have chosen complex numbers, which are well supported by Racket, to represent a cell. The advantage of using complex numbers instead of, say, a structure of x and y coordinates is mainly performance. A functional implementation without any kind of mutation will be typically slow. At least resorting to a plain data type should improve things in that regard. That choice doesn't compromise readability, though, I think. Besides, the implementation of functions, in particular <code>neighbors</code>, is really neat with such a data type.</p>
<p>On the other hand, I have represented polyominos as sets of cells. Actually lists of cells but treated as immutable sets. That, I hope, produces an elegant and pretty understandable implementation. It also allows an arbitrary number of cells in the grid. I'm aware that clever algorithms could improve efficiency, but I preferred to write first the most natural and readable version.</p>
<p>One last thing, the animation tends to be sluggish after certain point, depending on the clock rate specified and the number of cells involved in that moment. To avoid that annoyance from the very beginning, <code>freeze</code>ing the image of the grid is absolutely indispensable.</p>
<p>If you want to change the number of rows and columns of the grid, modify <code>COLS#</code> and <code>ROWS#</code> as you wish.</p>
<p>To play the game, run the program with <code>(main START)</code> from the Interactions Window within DrRacket. Select cells to construct a polyomino by clicking on them on the initial grid. To clear the grid, press 'Escape', to (re)start the animation press 'Enter', to stop the animation, press any key except 'Enter' or 'Escape'.</p>
<pre>
;; ===============================================
;; CONSTANTS
;; -----------------------------------------------
;; Game rules
(define OVER-POPULATION-THRESHOLD 3)
(define UNDER-POPULATION-THRESHOLD 2)
(define PROCREATION-FACTOR 3)
;; -----------------------------------------------
;; Graphics - Dimensions
; number of rows and columns in the grid
(define ROWS# 81)
(define COLS# 81)
; length of each cell side (in pixels)
(define SIDE 10)
; x and y positions of the middle of, roughly, the grid's center tile
(define CTR-X
(if (odd? COLS#)
(quotient (* COLS# SIDE) 2)
(- (quotient (* COLS# SIDE) 2) (quotient SIDE 2))))
(define CTR-Y
(if (odd? ROWS#)
(quotient (* ROWS# SIDE) 2)
(- (quotient (* ROWS# SIDE) 2) (quotient SIDE 2))))
;; -----------------------------------------------
;; Graphics - Images
(define LIVE-CELL (color-frame "transparent"
(square (sub1 SIDE) "solid" "red")))
(define DEAD-CELL (square SIDE "outline" "black"))
(define GRID
(local [(define col-img
(foldr above
empty-image
(build-list ROWS# (lambda (_) DEAD-CELL))))]
(freeze
(foldr beside
empty-image
(build-list COLS# (lambda (_) col-img))))))
;; ===============================================
;; DATA DEFINITIONS
;; -----------------------------------------------
;; Cell is Complex
;; interp. a cell in a game of life, where its real and imaginary
;; parts represent, respectively, its positions on the x and
;; y axis of the complex plane of the game.
;; remark: actual values for those parts are chosen in such a way
;; that the 0+0i cell in cell patterns is, roughly, at the
;; center of the pattern, which makes easier to design
;; drawing functions
(define C0 (make-rectangular 0 0)) ;0+0i
;; -----------------------------------------------
;; Life is (listof Cell)
;; interp. a set of living cells in a game of life
(define I-TROMINO
(list (make-rectangular 0 1)
(make-rectangular 0 0)
(make-rectangular 0 -1)))
(define P-PENTOMINO
(list (make-rectangular 0 1)
(make-rectangular 1 1)
(make-rectangular 0 0)
(make-rectangular 1 0)
(make-rectangular 0 -1)))
(define R-PENTOMINO
(list (make-rectangular 0 1)
(make-rectangular 1 1)
(make-rectangular -1 0)
(make-rectangular 0 0)
(make-rectangular 0 -1)))
;; -----------------------------------------------
;; Direction is one of:
(define E (make-rectangular 1 0))
(define NE (make-rectangular 1 1))
(define N (make-rectangular 0 1))
(define NW (make-rectangular -1 1))
(define W (make-rectangular -1 0))
(define SW (make-rectangular -1 -1))
(define S (make-rectangular 0 -1))
(define SE (make-rectangular 1 -1))
;; interp. the eight cardinal points
(define DIRECTIONS (list E NE N NW W SW S SE))
;; -----------------------------------------------
(define-struct gol (life running?))
;; GoL is (make-gol Life Boolean)
;; interp. a game of life with its life and a
;; state, running or stopped
(define STOPPED #f)
(define RUNNING #t)
(define START (make-gol '() STOPPED))
;; ===============================================
;; FUNCTIONS
;; -----------------------------------------------
;; GoL -> GoL
;; start main with (main START)
(define (main gol)
(big-bang gol
(on-tick next .5)
(to-draw render)
(on-mouse add-cell)
(on-key handle-key)))
;; -----------------------------------------------
;; GoL -> GoL
;; produce the next gol from the given one
(define (next gol)
(cond [(gol-running? gol)
(make-gol (evolve (gol-life gol)) RUNNING)]
[else gol]))
;; -----------------------------------------------
;; Life -> Life
;; produce the next evolutive step of the given life
(define (evolve l)
(local [;; Cell -> (listof Cell)
;; produce the neighbors of the given cell
(define (neighbors c)
(map (lambda (d) (+ c d)) DIRECTIONS))
;; Cell -> Natural
;; produce the number of the living neighbors of given cell
(define (alive-neighbors# c)
(set-count (set-intersect (neighbors c) l)))
;; Cell -> Boolean
;; determine whether the given cell is going to survive
(define (survive? c)
(<= UNDER-POPULATION-THRESHOLD
(alive-neighbors# c)
OVER-POPULATION-THRESHOLD))
;; Cell -> Boolean
;; determine whether the given cell is going to rise
(define (rise? c)
(= (alive-neighbors# c) PROCREATION-FACTOR))
;; surviving cells in the given life
(define surviving
(filter survive? l))
;; surrounding cells around the given life
(define environ
(set-subtract (foldr set-union '() (map neighbors l)) l))
;; cells going to live in the environment of the given life
(define rising
(filter rise? environ))]
(set-union surviving rising)))
;; -----------------------------------------------
;; GoL -> Image
;; render the given gol life on the grid
(define (render gol)
(local [;; Cell -> Posn
;; produce the position on the grid corresponding
;; to the given cell
(define (cell->pos c)
(make-posn (+ CTR-X (* (real-part c) SIDE))
(- CTR-Y (* (imag-part c) SIDE))))
;; Cell Image -> Image
;; draw the given the cell onto the given image
(define (draw-cell c img)
(local [(define p (cell->pos c))]
(place-image LIVE-CELL (posn-x p) (posn-y p) img)))]
(foldr draw-cell GRID (gol-life gol))))
;; -----------------------------------------------
;; GoL Integer Integer MouseEvent -> GoL
;; add the cell clicked on to the given gol's life
(define (add-cell gol x y me)
(local [(define (xy->cell x y)
(local [(define ctr-delta
(make-posn (ceiling (sub1 (/ COLS# 2)))
(ceiling (sub1 (/ ROWS# 2)))))]
(make-rectangular (- (quotient x SIDE)
(posn-x ctr-delta))
(+ (- (quotient y SIDE))
(posn-y ctr-delta)))))]
(cond [(mouse=? me "button-down")
(make-gol (cons (xy->cell x y) (gol-life gol))
(gol-running? gol))]
[else gol])))
;; -----------------------------------------------
;; GoL KeyEvent -> GoL
;; handle keys as follows
;; - 'Enter' - starts the animation
;; - 'Escape' - clear all cells and stops the animation
;; - any other key - stops the animation
(define (handle-key l ke)
(cond [(key=? ke "\r")
(make-gol (gol-life l) RUNNING)]
[(key=? ke "escape")
(make-gol '() STOPPED)]
[else
(make-gol (gol-life l) STOPPED)]))
</pre>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-23220534169309089912017-02-05T20:32:00.000+01:002017-04-06T09:09:36.631+02:00The unfold function. Gentle introduction from examples<p>This post is about a higher-order function known by functional programmers under the name <code>unfold</code> or <code>ana</code> (from the word anamorphism), It is less famous than <code>fold</code> but still worth of consideration and pretty useful.</p>
<p>Most of the expositions I know introducing <code>unfold</code> are based on Haskell or on a Haskell-like language. To mention some seminal but readable papers:</p>
<ul>
<li><p><a href="https://pdfs.semanticscholar.org/fec6/b29569eac1a340990bb07e90355efd2434ec.pdf">Meijer, E. et al., <em>Functional Programming with Bananas, Lenses, Envelops and Barbed Wire</em></a></p></li>
<li><p><a href="http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/unfold.ps.gz">Gibbons, J and Jones, G., <em>The Under-Appreciated Unfold</em></a></p></li>
</ul>
<p>There is also a brief and illuminating introduction in Hutton, G., <em>Programming in Haskell</em>, Cambridge University Press, 2016.</p>
<p>My intention is to present <code>unfold</code> under a Scheme-like syntax and at slower pace than in the referred papers. In particular, I'll use a domain-specific language tailored for teaching purposes from Racket. It is the language employed in the excellent book <a href="http://www.ccs.neu.edu/home/matthias/HtDP2e/index.html">Felleisen, M. et. al., <em>How to Design Programs</em></a>. From this book, as well as from the amazing MOOC <a href="https://www.edx.org/xseries/how-code-systematic-program-design">How To Code: Systematic Program Design</a> I also borrowed the same systematic way of presentation.</p>
<p>All the code below can be run on DrRacket, the IDE for our language that is available here:
<p><a href="https://download.racket-lang.org/">https://download.racket-lang.org/</a></p>
Specifically, if you want to run the code, set the DrRacket language to 'Advanced Student Language'.</p>
Additionally, write these two lines at the beginning of the code. They load a couple of libraries I will rely upon</p>
<pre><code>(require racket/function)
(require racket/list)</code></pre>
<hr />
<p>Let us consider functions that produce a list from a (seed) value. So functions with this signature:</p>
<p>;; Y -> (listof X)</p>
<p>that is a function that takes one argument of type Y, and produces a list of elements of type X.</p>
<p>[Hopefully, the meaning of those signatures is self-explanatory. For more information look into the first chapter of HtDP/2e or take the How To Code MOOC.</p>
<p>Also, signatures here are just code comments for helping us design functions, but not checked by the compiler].</p>
<p>A function that resembles that signature is, say, <code>copier</code>, that creates a list of <code>n</code> copies of a string [see HtDP/2e section 9.3]. Note that this function contains an extra parameter, the string to be copied. So it doesn't follow the signature, but I begin with it because it is very easy to understand. We will look later what we can do with the extra parameter.</p>
<p>The signature of <code>copier</code> is</p>
<pre><code>;; Natural String -> (listof String)</code></pre>
<p>The implementation based on the template for <code>Natural</code>:</p>
<pre><code>;; <template for Natural>
(define (fn-for-natural n)
(cond [(zero? n) (...)]
[else
(... (fn-for-natural (sub1 n)))]))</code></pre>
<p>is straightforward:</p>
<pre><code>;; Natural String -> (listof String)
;; produce a list with n copies of s
(check-expect (copier 0 "hi") '())
(check-expect (copier 3 "hi") '("hi" "hi" "hi"))
(define (copier n s)
(cond [(zero? n) '()]
[else
(cons s
(copier (sub1 n) s))]))</code></pre>
<p>Another typical function that constructs a list from some initial value is <code>string-split</code>, that produces a list of words in a given string.</p>
<p>For convenience we represent <code>String</code> as <code>(listof 1String)</code>, where <code>1String</code> is a <code>String</code> consisting of a single letter. [In the definition I'll use the list functions <code>takef</code> and <code>dropf</code> provided by <code>racket/list</code>. These are also known as <code>take-while</code> and <code>drop-while</code> in other languages.]</p>
<p>To design this function we rely on the template for lists:</p>
<pre><code>#;
(define (fn-for-lox lox)
(cond [(empty? lox) (...)]
[else
(... (first lox)
(fn-for-lox (rest lox)))]))
;; (listof 1String) -> (listof (listof 1String))
;; produce the list of words in los
(check-expect (string-split '()) '())
(check-expect (string-split '(" ")) '(()))
(check-expect (string-split '("a")) '(("a")))
(check-expect (string-split '("a" "b" " " "c" " " "d" "e" "f"))
'(("a" "b") ("c") ("d" "e" "f")))
(define (string-split los)
(cond [(empty? los) '()]
[else
(cons (first-word los)
(string-split (remove-first-word los)))]))
;; (listof 1String) -> (listof 1String)
;; produce the first word in los
(check-expect (first-word '()) '())
(check-expect (first-word '("a" "b" " ")) '("a" "b"))
(define (first-word los)
(takef los not-whitespace?))
;; (listof 1String) -> (listof 1String)
;; remove from los its first word as any leading whitespaces before it
(check-expect (remove-first-word '()) '())
(check-expect (remove-first-word '("a" "b" " " "c" "d" " " "e"))
'("c" "d" " " "e"))
(define (remove-first-word los)
(trim-leading-whitespaces (dropf los not-whitespace?)))
;; (listof 1String) -> (listof 1String)
;; remove from los its leading whitespaces
(check-expect (trim-leading-whitespaces '()) '())
(check-expect (trim-leading-whitespaces '("a")) '("a"))
(check-expect (trim-leading-whitespaces '(" " " " "a")) '("a"))
(define (trim-leading-whitespaces los)
(dropf los string-whitespace?))
;; 1String -> Boolean
;; determine whether given s is not a whitespace
(check-expect (not-whitespace? " ") #f)
(check-expect (not-whitespace? "a") #t)
(define not-whitespace? (compose not string-whitespace?))</code></pre>
<p>What about the higher-order function <code>map</code>? It actually produces a list too, this time from a given list and some function over the type of its elements:</p>
<pre><code>;; (X -> Y) (listof X) -> (listof Y)
;; produce the list (list (f x1) (f x2) ...) by applying
;; f to each element of lox
(check-expect (my-map sqr '()) '())
(check-expect (my-map sqr '(1 2 3 4)) '(1 4 9 16))</code></pre>
<p>Again a function over a list that we can define easily:</p>
<pre><code>(define (my-map f lox)
(cond [(empty? lox) '()]
[else
(cons (f (first lox))
(my-map f (rest lox)))]))</code></pre>
<p>As a final example over a more intricate input type consider the function <code>zip</code> that takes a pair of lists and produces a list of pairs.</p>
<p>A natural recursive implementation of <code>zip</code> is as follows:</p>
<pre><code>;; (listof X) (listof Y) -> (listof (list X Y))
;; produce (list (x1 y1) (x2 y2) ...) from given lists, if lists
;; have different length, excess elements of the longer list
;; are discarded.
(check-expect (zip0 '() '()) '())
(check-expect (zip0 '(1 2 3) '(3 4 5)) '((1 3) (2 4) (3 5)))
(check-expect (zip0 '(1 2) '(3)) '((1 3)))
(check-expect (zip0 '(1 2) '(3 4 5)) '((1 3) (2 4)))
(define (zip0 lox loy)
(cond [(or (empty? lox) (empty? loy)) '()]
[else
(cons (list (first lox) (first loy))
(zip0 (rest lox) (rest loy)))]))</code></pre>
<p>More suitable for the argument's sake is to pass both lists wrapped in a single pair of type <code>(list (listof X) (listof Y))</code>:</p>
<pre><code>;; (list (listof X) (listof Y)) -> (listof (list X Y))
;; produce (list (x1 y1) (x2 y2) ...) from given list pair ...
(check-expect (zip '(() ())) '())
(check-expect (zip '((1 2 3) (3 4 5))) '((1 3) (2 4) (3 5)))
(check-expect (zip '((1 2) (3))) '((1 3)))
(check-expect (zip '((1 2) (3 4 5))) '((1 3) (2 4)))
(define (zip lp)
(cond [(ormap empty? lp) '()]
[else
(cons (map first lp)
(zip (map rest lp)))]))</code></pre>
<p>;; ---------------------------------</p>
<p>All of those functions share the same pattern.</p>
<p>They all have a predicate that produces the empty list if it is satisfied by the seed value, and a list constructor that applies some functions to the first and the rest of the constructed list.</p>
<p>Therefore we can abstract the pattern from the examples. [More about abstraction from examples in HtDP/2e Sect. 15].</p>
<pre><code>#;
(define (copier ... ... ... n s)
(cond [(...? n) '()]
[else
(cons (... s) ;-> s, what about n?
(copier (... n) s))]))
#;
(define (string-split ... ... ... los)
(cond [(...? los) '()]
[else
(cons (... los)
(string-split (... los)))]))
#;
(define (my-map ... ... ... lox)
(cond [(...? lox) '()]
[else
(cons (... lox)) ;-> (f ...)
(my-map f (... lox))]))
#;
(define (zip ... ... ... lp)
(cond [(...? lp) '()]
[else
(cons (... lp)
(zip (... lp)))]))</code></pre>
<p>Note that in order to preserve the same pattern on all templates I have made a few tweaks in the template of a couple of the above functions.</p>
<p>First, The seed value is missing in <code>copier</code>. There is an <code>s</code> instead of <code>n</code>. Also, there is no function call at the same place at which it appears in the rest of the templates.</p>
<p>Secondly, I have temporarily removed the <code>f</code> argument from <code>my-map</code>. Instead I put a comment to remind me that <code>f</code> will play, for sure, some role in the final design.</p>
<p>After filling the gaps we get this:</p>
<pre><code>;; (Y -> Bool) (Y -> X) (Y -> Y) Y -> (listof X)
(define (unfold p? f g b)
(cond [(p? b) '()]
[else
(cons (f b)
(unfold p? f g (g b)))]))</code></pre>
<p>This abstract function encapsulates a recursive pattern that is dual to <code>fold</code>. While <code>fold</code> de-structs a list (so the funny name by which it is known, cata-morphism), <code>unfold</code> cons-tructs a list (and, likewise, is called ana-morphism).</p>
<p>Now we can re-defined the examples with <code>unfold</code>, and amend the tweaks made above as required.</p>
<p>The easiest one is <code>string-split</code>:</p>
<pre><code>;; (listof 1String) -> (listof (listof 1String))
;; produce the list of words in los
(check-expect (string-split2 '()) '())
(check-expect (string-split2 '(" ")) '(()))
(check-expect (string-split2 '("a")) '(("a")))
(check-expect (string-split2 '("a" "b" " " "c" " " "d" "e" "f"))
'(("a" "b") ("c") ("d" "e" "f")))
(define (string-split2 los)
(unfold empty? first-word remove-first-word los))</code></pre>
<p>As for <code>my-map</code> the original <code>f</code> argument can be reinstated at the place occupied by the <code>f</code> of <code>unfold</code> as the initial member of a function composition with <code>first</code>:</p>
<pre><code>;; (X -> Y) (listof X) -> (listof Y)
;; produce the list (list (f x1) (f x2) ...) by applying
;; f to each element of lox
(check-expect (my-map2 sqr '()) '())
(check-expect (my-map2 sqr '(1 2 3 4)) '(1 4 9 16))
(define (my-map2 f lox)
(unfold empty? (compose f first) rest lox))</code></pre>
<p>Regarding <code>copier</code> the only really diverging thing is the extra parameter, <code>s</code>. In other words, the <code>unfold</code> pattern takes a single seed parameter while <code>copier</code> takes two. One way to cope with this is to implement <code>copier</code> as a closure. Besides, there is no actual function call on the first term of the constructed list. In such cases the pattern obliges to supply some function. Typically, <code>identity</code> or, as here, <code>const</code>. [<code>const</code> is a function that produces the value of its body whatever the argument passed into it. It is provided by <code>racket/function</code>].</p>
<pre><code>;; Natural String -> (listof String)
;; produce a list with n copies of s
(check-expect (copier2 0 "hi") '())
(check-expect (copier2 3 "hi") '("hi" "hi" "hi"))
(define (copier2 n0 s)
(local [(define (recr n)
(unfold zero? (const s) sub1 n))]
(recr n0)))</code></pre>
<p><code>zip</code> doesn't entail anything special. We just pass the appropriate functions at due places. We can give them names, anonymous functions seem to be simpler, though.</p>
<pre><code>;; (list (listof X) (listof Y)) -> (listof (list X Y))
;; produce (list (x1 y1) (x2 y2) ...) from given list pair ...
(check-expect (zip2 '(() ())) '())
(check-expect (zip2 '((1 2 3) (3 4 5))) '((1 3) (2 4) (3 5)))
(check-expect (zip2 '((1 2) (3))) '((1 3)))
(check-expect (zip2 '((1 2) (3 4 5))) '((1 3) (2 4)))
(define (zip2 lp)
(unfold (lambda (xs) (ormap empty? xs))
(lambda (xs) (map first xs))
(lambda (xs) (map rest xs))
lp))</code></pre>
<p>Or in a more concise manner, taking advantage of <code>curry</code>, a library function in <code>racket/function</code> [For more info about currying: https://en.wikipedia.org/wiki/Currying]:</p>
<pre><code>(define (zip3 lp)
(unfold (curry ormap empty?)
(curry map first)
(curry map rest)
lp))</code></pre>
<p>In summary, whenever you find a function that constructs a list from some value, unfold might be a very useful and elegant abstraction.</p>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-47944565923795121082016-08-23T23:59:00.001+02:002016-09-30T19:32:05.653+02:00Reading R Code. Factors in R<p>R factors are a common source of confusion for R beginners, even in their most basic use. In this post I try to shed some light on R factors. But rather than only rely on external sources of information or on the R documentation, I will make some experiments and have a look at the implementation to understand what a factor is and how it behaves in the most simple use cases.</p>
<p>First of all, R factors are intended for representing categorical variables. A categorical variable is a variable that can take values of a limited set of so-called <em>categories</em>. For instance, to encode the gender of a population we would use a categorical variable with two possible categories, male and female. Or to encode the educational level of a population under 18 we can use a categorical variable with three categories: elementary, middle and high.</p>
<p>In addition, categories of a categorical variable may or may not have some internal ordering. We cannot ascribe any sound ordering to the first example of categorical variable, but we should in the second case (elementary <code><</code> middle <code><</code> high)</p>
<p>In R, the term <code>levels</code> stands for categories of the categorical variable (the factor). <code>labels</code> in turn suggests something like custom names for those categories.</p>
<p>This distinction might be blurry, and it is. Think of some data you are going to analyze, say, a record of the gender of 6 persons:</p>
<pre><code>> x <- c("F", "M", "F", "F", "F", "M")</code></pre>
<p>If we want to convert this into a factor, which are the levels? Maybe, "male" and "female"?</p>
<pre><code>> factor(x, c("male", "female"))
[1] <NA> <NA> <NA> <NA> <NA> <NA>
Levels: male female</code></pre>
<p>Yikes! This doesn't work. Interestingly, no error message has been triggered, and levels are stored as we intended. But what are those <code><NA></code>s there?</p>
<p>Let's try without supplying levels and see what happens:</p>
<pre><code>> factor(x)
> [1] F M F F F M
> Levels: F M</code></pre>
<p>This looks better, at least nasty <code><NA></code>s have disappeared. So, when no levels are supplied, levels end up being the distinct elements of the original <code>x</code>. It seems that we could pass them instead of our <code>"male"</code> and <code>"female"</code> before:</p>
<pre><code>> factor(x, levels = c("M", "F"))
> [1] F M F F F M
> Levels: M F</code></pre>
<p>Fine! This works. Only that levels appear now in a different order, the one we have supplied.</p>
<p>Still we would prefer the more human-friendly "male" and "female". It might be that these words are rather labels. Let's try:</p>
<pre><code>> factor(x, labels = c("male", "female"))
> [1] male female male male male female
> Levels: male female</code></pre>
<p>Much better, but wait, our initial vector was another one. This totally wrecks havoc with <code>x</code>!</p>
<p>Looks like the order in which we pass the labels makes a serious effect. And it certainly does:</p>
<pre><code>> factor(x, labels = c("female", "male"))
> [1] female male female female female male
> Levels: female male</code></pre>
<p>Great! If for some hidden reason (maybe, say, because we expect that in a plot of this factor the legend shows male scores before female ones) we still want the other ordering of levels, we may try to combine both parameters:</p>
<pre><code>> factor(x, levels = c("M", "F"), labels = c("male", "female"))
> [1] female male female female female male
> Levels: male female</code></pre>
<p>That's it! Particularly, note how each label corresponds to each level in the values supplied. To verify, something like the following (where there is a mismatch in the ordering of levels and labels)</p>
<pre><code>> factor(x, levels = c("F", "M"), labels = c("male", "female"))
> [1] male female male male male female
> Levels: male female</code></pre>
<p>ruins <code>x</code> again.</p>
<p>From these experiments we can draw some intuitive conclusions:</p>
<ol style="list-style-type: decimal">
<li>levels are the distinct elements in the object to be converted to factor.</li>
<li>If levels are not supplied they are constructed from those unique elements sorted in alphabetical order.</li>
<li>labels are the "names" we want to give to our levels.</li>
<li>If no supplied, labels are the same as the levels.</li>
<li>The order of labels passed must match the order of the levels, either of the default levels or of the levels we supply.</li>
<li>The less risky way to create factors with custom labels is to supply both parameters.</li>
</ol>
<p>Looking at the implementation makes crystal clear what we have found out.</p>
<pre><code> 1 function (x = character(), levels, labels = levels, exclude = NA,
2 ordered = is.ordered(x), nmax = NA)
3 {
4 if (is.null(x))
5 x <- character()
6 nx <- names(x)
7 if (missing(levels)) {
8 y <- unique(x, nmax = nmax)
9 ind <- sort.list(y)
10 y <- as.character(y)
11 levels <- unique(y[ind])
12 }
13 force(ordered)
14 exclude <- as.vector(exclude, typeof(x))
15 x <- as.character(x)
16 levels <- levels[is.na(match(levels, exclude))]
17 f <- match(x, levels)
18 if (!is.null(nx))
19 names(f) <- nx
20 nl <- length(labels)
21 nL <- length(levels)
22 if (!any(nl == c(1L, nL)))
23 stop(gettextf("invalid 'labels'; length %d should be 1 or %d",
24 nl, nL), domain = NA)
25 levels(f) <- if (nl == nL)
26 as.character(labels)
27 else paste0(labels, seq_along(levels))
28 class(f) <- c(if (ordered) "ordered", "factor")
29 f
30 }</code></pre>
<p>Let us consider the second point above. If levels are not supplied they will be the distinct elements of the given vector:</p>
<pre><code>if (missing(levels)) {
y <- unique(x, nmax = nmax)
ind <- sort.list(y)
y <- as.character(y)
levels <- unique(y[ind])
}</code></pre>
<p>If levels are missing, we create a vector <code>y</code> of unique elements of <code>x</code>:</p>
<pre><code>> y <- unique(x)
> y
[1] "F" "M"</code></pre>
<p>Note that <code>nmax</code> is set by default to <code>NA</code> in the function header:</p>
<pre><code>function (x = character(), levels, labels = levels, exclude = NA,
ordered = is.ordered(x), nmax = NA) </code></pre>
<p>and <code>unique(x, nmax = NA)</code> is equivalent to the call just above.</p>
<p>Also, note that if the order of elements in <code>x</code> were different, say, <code>c("M", "F", "M")</code>, <code>unique</code> would produce the distinct elements just by removing duplicates in the given vector:</p>
<pre><code>> unique(c("M", "F", "M"))
[1] "M" "F"</code></pre>
<p>So <code>unique</code> doesn't sort the result.</p>
<p>To sort the result we need a sorting function, here <code>sort.list</code>. This function produces the indices suitable for subsetting <code>y</code>:</p>
<pre><code>> sort.list(y)
[1] 1 2
> y[sort.list(y)]
[1] "F" "M"</code></pre>
<p>Note also that <code>y</code> holds a character vector always. So levels produced are always a character vector sorted in alphabetical order.</p>
<p>If levels are supplied it is expected that they match the unique elements of <code>x</code>. Pay attention to this crucial line:</p>
<pre><code>f <- match(x, levels)</code></pre>
<p><code>match</code> is a nice but maybe tricky function to understand, it produces the positions (= indices) of matches of <code>x</code> in <code>levels</code>.</p>
<p>Let's try some examples. Recall that <code>x</code> is:</p>
<pre><code>> x
[1] "F" "M" "F" "F" "F" "M"
> match(x, c("M", "F"))
[1] 2 1 2 2 2 1</code></pre>
<p>Indeed, the first element of <code>x</code> matches the <code>2</code>nd element of <code>levels</code>; the second element of <code>x</code> matches the <code>1</code>st element of <code>levels</code>, and so on.</p>
<p>If there is no match, a <code>NA</code> is produced by default:</p>
<pre><code>> match(x, c("M", "O"))
[1] NA 1 NA NA NA 1</code></pre>
<p>Now we see where the <code>NA</code>s in our initial wrong attempt with "male" and "female" as levels came from.</p>
<p>What about labels?</p>
<p>If labels are not supplied they are just the levels as the function header states (<code>levels</code> is the default value for <code>labels</code>).</p>
<p>If they are supplied, these lines give labels to levels:</p>
<pre><code>levels(f) <- if (nl == nL)
as.character(labels)
else paste0(labels, seq_along(levels))</code></pre>
<p>Actually, the line that is executed for our previous examples with <code>labels</code> is just:</p>
<pre><code>levels(f) <- as.character(labels)</code></pre>
<p>since in our examples the number of levels are labels are the same (<code>nl == nL</code>, where <code>nl <- length(labels)</code> and <code>nL <- length(levels)</code>)</p>
<p>Note again that levels with those new labels are still always a character object.</p>
<p>The last line in the snippet above deals with the case where a single label is supplied. We haven't tried an example for that, it is time to do it now:</p>
<pre><code>> factor(x, labels = "gender")
[1] gender1 gender2 gender1 gender1 gender1 gender2
Levels: gender1 gender2</code></pre>
<p>This is a valid way to assign labels to levels, though not so good for this example. The code responsible of producing this vector is:</p>
<pre><code>paste0(labels, seq_along(levels))</code></pre>
<p><code>seq_along(levels)</code> produces a sequence <code>1, 2, ... n</code> where <code>n</code> is the length of <code>levels</code>. <code>paste0</code> just concatenates the single label with those digits in the sequence via recycling.</p>
<p>However, the most remarkable fact we may notice from looking at the implementation is that a <code>factor</code> in R is just an integer vector whose attributes "levels" (as we have seen), "names" (as names of <code>x</code> [see line 6 and lines 18-19 in the implementation]) and, above all, "class" are set appropriately.</p>
<p>As for the latter the last but one line in the implementation sets the <code>"class"</code> attribute to <code>"factor"</code> always. Besides, if <code>x</code> is an ordered vector (the default behavior), or if we pass <code>TRUE</code> as value to the <code>ordered</code> argument, <code>"class"</code> is set to <code>c("ordered", "factor")</code>:</p>
<pre><code>class(f) <- c(if (ordered) "ordered", "factor") </code></pre>
<p>Note the order of elements in the last vector for "class". The order here matters, and means that in such a case <code>f</code> is like an instance of the class <code>"ordered"</code>, that in turn is somewhat like a subclass of "<code>factor</code>". [See <code>?class</code> for more details about this kind of inheritance mechanism.]</p>
<p>A nice way of finishing this post is to demonstrate what we have discovered, that a factor is, from the R's internal point of view, just an integer vector with certain attributes set appropriately.</p>
<pre><code>> f <- c(1L, 2L, 1L, 1L, 1L, 2L)
> levels(f) <- c("female", "male")
> class(f) <- "factor"
> str(f)
Factor w/ 2 levels "female","male": 1 2 1 1 1 2
> f
[1] female male female female female male
Levels: female male
> f2 <- c(1L, 3L, 3L, 2L, 1L, 2L)
> levels(f2) <- c("elem", "middle", "high")
> class(f2) <- c("ordered", "factor")
> str(f2)
Ord.factor w/ 3 levels "elem"<"middle"<..: 1 3 3 2 1 2
> f2
[1] elem high high middle elem middle
Levels: elem < middle < high</code></pre>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-77430978377802057552016-08-08T18:45:00.000+02:002016-08-08T23:47:59.459+02:00Reading R Code. The function Reduce in R (part II)<p>[ <em>This post is the third one in a series about <code>Reduce</code>. To understand it you also need to read the <a href="http://los-pajaros-de-hogano.blogspot.com.es/2016/07/reading-r-code-function-reduce-folds.html">first</a> and <a href="http://los-pajaros-de-hogano.blogspot.com.es/2016/08/reading-r-code-function-reduce-in-r.html">second</a> posts in this series</em>.]</p>
<h3 id="loops">Loops</h3>
<p>Our implementation of <code>Reduce</code> directly translates the recursive procedures capturing the <code>fold-left</code> and <code>fold-right</code> computations. I think that this kind of recursive thinking is not only more high-level but also easier to understand, and that was the reason for beginning with them. However, in languages that don't provide mechanisms for optimizing recursion, iteration is always expressed via loops or another non-recursive procedure.</p>
<p>We have to do so in R if we hope to handle sequences of certain length, otherwise we'll get something like this:</p>
<pre><code>> source("my_reduce.R")
> my_reduce("+", 1:100000)
Error: evaluation nested too deeply ...</code></pre>
<p>Let's get started with converting the higher-level recursion used so far into a lower-level kind of loop for our first simple implementation in order to see how this can be easily done for the rest of the function.</p>
<p>That first implementation was for <code>fold-left</code> with a given initial value:</p>
<pre><code>my_reduce <-
function(f, x, init) {
f <- match.fun(f)
iter <- function(acc, x) {
len <- length(x)
if (!len) {
acc
}
else {
first <- x[[1L]]
rest <- tail(x, -1L)
iter(f(acc, first), rest)
}
}
iter(init, x)
}</code></pre>
<p>Using a <code>for</code> loop instead of recursion we get this self-explanatory version:</p>
<pre><code>my_reduce <-
function(f, x, init) {
f <- match.fun(f)
acc <- init
for (e in x) {
acc <- f(acc, e)
}
acc
}</code></pre>
<p>Accessing elements by its index is another way to get the same thing. The refactored function based on indices would be:</p>
<pre><code>my_reduce <-
function(f, x, init) {
f <- match.fun(f)
acc <- init
len <- length(x)
ind <- seq_len(len)
for (i in ind) {
acc <- f(acc, x[[i]])
}
acc
}</code></pre>
<p><code>seq_len</code> is an R function that takes a number and produces an integer vector from 1 to that number (included). So, <code>seq_len(4)</code> produces the sequence <code>1, 2, 3, 4</code>. <code>ind</code> in the code above just refers to the numerical indices of <code>x</code> to be used in the loop.</p>
<p>A cosmetic and optional detail. Since the local variable <code>acc</code> takes the value passed as <code>init</code>, and the initial <code>init</code> value won't be used any more, we can stick with <code>init</code> as the name for the accumulated value so far, and remove <code>acc</code>:</p>
<pre><code>my_reduce <-
function(f, x, init) {
f <- match.fun(f)
len <- length(x)
ind <- seq_len(len)
for (i in ind) {
init <- f(init, x[[i]])
}
init
}</code></pre>
<h3><code>Reduce</code>, at last!</h3>
<p>That's actually the R implementation for left folding with <code>init</code> given:</p>
<pre><code>function(f, x, init) {
len <- length(x)
f <- match.fun(f)
ind <- seq_len(len)
for (i in ind) init <- forceAndCall(2, f, init, x[[i]])
init
}</code></pre>
<p>The only subtle difference lies in how <code>f</code> is called. Instead of a direct application, it uses the <code>forceAndCall</code> R function. The purpose of this is to avoid delayed evaluation of arguments. That means that we force R to evaluate the first 2 arguments (note the <code>2</code> as first argument) of <code>f</code> immediately before executing the <code>f</code> body, rather than leaving R to evaluate them after. This is rather technical at this point and we can go ahead without fully understanding. Just note that this construct is like calling the function but in a better way when that function is to be the value passed to a higher-order function like <code>Reduce</code>. We might go deeper into this in the future.</p>
<p>Once we have transformed our initial version into a loop-based version the first half of <code>Reduce</code> is now easy to understand.</p>
<pre><code> 1 Reduce <-
2 function(f, x, init, right = FALSE, accumulate = FALSE)
3 {
4 mis <- missing(init)
5 len <- length(x)
6
7 if(len == 0L) return(if(mis) NULL else init)
8
9 f <- match.fun(f)
10
11
12 if(!is.vector(x) || is.object(x))
13 x <- as.list(x)
14
15 ind <- seq_len(len)
16
17 if(mis) {
18 if(right) {
19 init <- x[[len]]
20 ind <- ind[-len]
21 }
22 else {
23 init <- x[[1L]]
24 ind <- ind[-1L]
25 }
26 }
27
28 if(!accumulate) {
29 if(right) {
30 for(i in rev(ind))
31 init <- forceAndCall(2, f, x[[i]], init)
33 }
34 else {
35 for(i in ind)
36 init <- forceAndCall(2, f, init, x[[i]])
37 }
38 init
39 }
40 else {
.. # to be explained later
.. }
.. }</code></pre>
<p>Apart from pretty minor differences, it is the same code as in our implementation but using indexing and looping instead of recursion.</p>
<p>Let's see:</p>
<ul>
<li><p>Line 7 is just the code that handles what happens if the given object is empty, the empty list or an empty vector, typically. It returns <code>NULL</code> if no initial value is given; otherwise, returns the initial value.</p></li>
<li><p>Lines 12-13 introduce one new thing only present in the official R version. Our version assumes a list as input. In R, a vector is expected, These lines ensure that if the value is not a vector or is an object in general it is converted to a type that the following code can deal with, a <code>list</code>.</p></li>
<li><p>Lines 17-26 initialize variables <code>init</code> and <code>ind</code> accordingly to the kind of fold (left or right) requested if no initial value were provided. <code>init</code> is set exactly as in our version. Regarding <code>ind</code>, instead of working on the rest of the list directly as in our recursive implementation, indices are initialized appropriately, <code>ind[-1L]</code> produces the next indices to the index of the first element in order to process the rest of list for left folding. On the other hand <code>ind[-len]</code> produces the indices for right folding: those of all elements save the last one (that has been used as <code>init</code>). These indices will be reverted in line 30 to process the remaining list from right to left as explained in the previous post.</p></li>
<li><p>Finally [lines 29-37], the function <code>f</code> is iteratively called with the proper<br />order of parameters for each kind of fold, and the result of folding returned in line 38.</p></li>
</ul>
<p>The rest of <code>Reduce</code> is the code handling the case were the caller asks for a sequence of partial results rather than the final reduction. We'll look at it in the next post.</p>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-25522236606142874942016-08-06T21:47:00.000+02:002016-08-08T23:38:57.891+02:00Reading R Code. The function Reduce in R (part I)<p>[<em>This post is the second one about the function <code>Reduce</code>. It assumes everything discussed in the <a href="https://los-pajaros-de-hogano.blogspot.com.es/2016/07/reading-r-code-function-reduce-folds.html"></em>first post about Reduce</a>]</p>
<br/>
<p>The function <code>Reduce</code> in R is based on the Lisp function <code>reduce</code>. It also adds an option borrowed from Haskell's <code>scan</code> functions.</p>
<p>Let's see this by exploring a few simple examples and observing their behavior in R as well as in Lisp and Haskell.</p>
<h3 id="what-does-reduce-do">What does <code>Reduce</code> do?</h3>
<pre><code>## Example 1.
# R
> Reduce(`+`, 1:4)
[1] 10
# Lisp
[1]> (reduce #'+ '(1 2 3 4))
10
# Haskell
Prelude> foldl (+) 1 [2, 3, 4]
10</code></pre>
<p>Look at the Haskell function. I have used <code>foldl</code>, that is the <code>fold-left</code> function in Haskell, because R and Lisp do <code>fold-left</code> by default. Note also that while in Haskell the initial value has to be specified, in R and in Lisp we don't need to do so. When not specified the initial value for left-folding is the first element in the given list.</p>
<p>The evaluation of all these functions (<code>fold-left</code>) expands as follows for the given example:</p>
<pre><code>(((1 + 2) + 3) + 4)
## Example 2.
# R
> Reduce(`-`, 1:4)
[1] -8
# Lisp
[1]> (reduce #'- '(1 2 3 4))
-8
# Haskell
Prelude> foldl (-) 1 [2, 3, 4]
-8</code></pre>
<p>The expansion is the same:</p>
<pre><code>(((1 - 2) - 3) - 4)
## Example 3.
# R
> Reduce(`-`, 1:4, right = TRUE)
[1] -2
# Lisp
[1]> (reduce #'- '(1 2 3 4) :from-end t)
-2
# Haskell
Prelude> foldr (-) 4 [1, 2, 3]
-2</code></pre>
<p>The evaluation expands to this now:</p>
<pre><code>(1 - (2 - (3 - 4)))</code></pre>
<p>This is <code>fold-right</code>. In R and Lisp we have to set an option for this fold, and at this point it should be clear why these two functions are described as "left" and "right" folds. Note also that, for <code>fold-right</code> the initial value, if given (as in Haskell) become the right-most item in the expansion. In fact, in R and Lisp if the initial value is not given, it is the last element of the given list.</p>
<p>R provides an extra feature borrowed from Haskell for producing a sequence of results of accumulated values rather than the final reduction.</p>
<p>In Haskell, there is a special set of functions for this purpose, <code>scan</code> functions, <code>scanr</code> corresponds to <code>foldr</code> and <code>scanl</code> to <code>foldl</code>:</p>
<pre><code>Prelude> scanl (-) 1 [2, 3, 4]
[1,-1,-4,-8]</code></pre>
<p>Observe the partial results in the computations above:</p>
<pre><code>initial value: = 1
1st result : 1 - 2 = -1
2nd result : -1 - 3 = -4
3rd result : -4 - 4 = -8</code></pre>
<p>Or, for a right fold:</p>
<pre><code>Prelude> scanr (-) 4 [1, 2, 3]
[-2,3,-1,4]</code></pre>
<p>where partial results from right to left and in reverse order are:</p>
<pre><code>initial value: = 4
1st result : 3 - 4 = -1
2nd result : 2 - (-1) = 3
3rd result : 1 - 3 = -2</code></pre>
<p>The R equivalents need the <code>accumulate</code> parameter set to <code>TRUE</code>:</p>
<pre><code>Reduce("-", 1:4, accumulate = TRUE)
> [1] 1 -1 -4 -8</code></pre>
<p>and</p>
<pre><code>Reduce("-", 1:4, right = TRUE, accumulate = TRUE)
> [1] -2 3 -1 4</code></pre>
<p>These functions are just variants of the tail-recursive pattern where a second accumulator is added to keep track of computed values so far.</p>
<p>So for instance <code>scanl</code> can be implemented as follows:</p>
<pre><code> (define (scan-left f i lox0)
(local [(define (f-for-lox-acc acc rsf lox)
(cond [(empty? lox) (reverse (cons acc rsf))]
[else
(f-for-lox-acc (f acc (first lox))
(cons acc rsf)
(rest lox))]))]
(f-for-lox-acc i empty lox0)))</code></pre>
<p>Only and extra accumulator <code>rsf</code> has been added. It maintains a list of resulting values so far. Each time a new computed value is produced is inserted into the <code>rsf</code> accumulator. The result is finally reversed. It is possible to avoid the final <code>reverse</code> if computed values are appended to the end of <code>rsf</code>. Anyway it is clear the idea behind this.</p>
<h3 id="r-implementation-of-reduce">R implementation of <code>Reduce</code></h3>
<p>As we have seen, <code>Reduce</code> in R is a multipurpose function. Other languages as Haskell prefer independent elementary functions for each task (right/left fold and returning accumulated values or not). I find the latter more elegant, easier to read and easier to test. R, though, is fond of functions with lots of parameters, and full of conditionals to select the code to execute for a specific purpose.</p>
<p>Even so, and with the goal of firstly implementing our own version for better understanding the R official version, it is reasonable to begin with basic use cases and add more after those have been implemented.</p>
<p>One of the simplest use case is to execute <code>Reduce</code> for left fold with a given initial value. As usual, we need a very basic set of tests. (more should be added for good coverage). The following are hopefully enough at this stage:</p>
<pre><code>test_that("test reduce: fold-left, init given", {
expect_equal(my_reduce("+", integer(0), init = 1), 1)
expect_equal(my_reduce("-", 2:4, init = 1), -8)
expect_equal(my_reduce("/", c(2, 9, 13), init = 7), 7/234)
expect_equal(my_reduce(list, 2:4, init = 1), (list(list(list(1, 2), 3), 4)))
})</code></pre>
<p>The most natural implementation at this point is the translation into R of the <code>fold-left</code> tail recursive procedure (first variant), as this:</p>
<pre><code>my_reduce <-
function(f, x, init) {
f <- match.fun(f)
iter <- function(acc, x) {
len <- length(x)
if (!len) {
acc
}
else {
first <- x[[1L]]
rest <- tail(x, -1L)
iter(f(acc, first), rest)
}
}
iter(init, x)
}</code></pre>
<p>Assuming that test cases are saved into the file <code>test_my_reduce.R</code>, running tests with:</p>
<pre><code>> library(testthat)
> test_file("test_my_reduce.R")</code></pre>
<p>confirms that this implementation works.</p>
<p>A few points about this code. The first line applies <code>match.fun</code> as customary in R code to check the validity of the argument passed as <code>f</code>. I have used <code>1L</code> instead of just <code>1</code> for indexing. This is a common practice in R base code: when an integer is required the number as a literal integer (the number followed by <code>L</code>) is passed. Finally, I have intentionally named the auxiliary helper as <code>iter</code> for reasons that will be clear. Any name would have worked though.</p>
<p>The next step is handling the case where no initial value is passed. Some test cases for this:</p>
<pre><code>test_that("test reduce: fold-left, init missing", {
expect_equal(my_reduce("+", integer(0)), NULL)
expect_equal(my_reduce("-", 1:4), -8)
expect_equal(my_reduce("/", c(7, 2, 9, 13)), 7/234)
expect_equal(my_reduce(list, 1:4), (list(list(list(1, 2), 3), 4)))
})</code></pre>
<p>The first test case is for the empty vector. As no initial value is given, we cannot return it as before. By convention we return <code>NULL</code> in this case.</p>
<p>And this is an obvious implementation that passes all tests above [only the first lines are added, the <code>iter</code> function remains the same]:</p>
<pre><code>my_reduce <-
function(f, x, init) {
f <- match.fun(f)
mis <- missing(init)
len <- length(x)
if (mis && !len) {
return(NULL)
}
if (mis) {
init <- x[[1L]]
x <- tail(x, -1L)
}
iter <- function(acc, x) {
len <- length(x)
if (!len) {
acc
}
else {
first <- x[[1L]]
rest <- tail(x, -1L)
iter(f(acc, first), rest)
}
}
iter(init, x)
}</code></pre>
<p>What about the <code>fold-right</code> operation?</p>
<p>Let's write some tests for fold-right with and without the initial value given:</p>
<pre><code>test_that("test reduce: fold-right, init given", {
expect_equal(my_reduce("+", integer(0), init = 1, right = TRUE), 1)
expect_equal(my_reduce("-", 1:3, init = 4, right = TRUE), -2)
expect_equal(my_reduce("/", c(7, 2, 9), init = 13, right = TRUE), 63/26)
expect_equal(my_reduce(list, 1:3, init = 4, right = TRUE),
list(1, list(2, list(3, 4))))
})
test_that("test reduce: fold-right, init missing", {
expect_equal(my_reduce("+", integer(0), right = TRUE), NULL)
expect_equal(my_reduce("-", 1:4, right = TRUE), -2)
expect_equal(my_reduce("/", c(7, 2, 9, 13), right = TRUE), 63/26)
expect_equal(my_reduce(list, 1:4, right = TRUE),
list(1, list(2, list(3, 4))))
})</code></pre>
<p>As for the implementation it looks like it would be a very different piece of code if we were to translate the natural recursive procedure examined in the post referred above. However, when designing functions that handle several possibilities we should seek after maximizing the amount of code shared by each of them. The best possible scenario for the function at hand would be one in which the tail-recursive pattern employed for <code>fold-left</code> is shared by the <code>fold-right</code> operation. It turns out that we already have a promising candidate, the second variant of <code>fold-left</code>. Remember that it has the same signature as <code>fold-right</code>, and it shouldn't be difficult to adapt it so that it serves as a tail-recursive version for <code>fold-right</code>. A closer examination into the expansions of both procedures gives the answer. Let's recall them once again:</p>
<pre><code>fold-right (-) 1 '(2 3 4) : 2 - (3 - (4 - 1))
fold-left-2 (-) 1 '(2 3 4) : 4 - (3 - (2 - 1)) </code></pre>
<p>The only difference lies in the order of elements in the given list. The function we are searching for (a tail-recursive <code>fold-right</code>) should have the same expansion as <code>fold-right</code>:</p>
<pre><code>fold-right-2 (-) ?? : 2 - (3 - (4 - 1))</code></pre>
<p>Which arguments should it receive? Obviously:</p>
<pre><code>fold-right-2 (-) 1 '(4 3 2)</code></pre>
<p>Hence, <code>fold-right-2</code> is just <code>fold-left-2</code> with the input list reversed, and, as you surely remember, the only remaining difference between our <code>fold-left</code>, the one employed in R, <code>fold-left-1</code> and <code>fold-left-2</code> is the order of arguments passed to <code>f</code>.</p>
<p>All of these points are captured by the following implementation [number of lines included]:</p>
<pre><code>1 my_reduce <-
2 function(f, x, init, right = FALSE) {
3 iter <- function(acc, x) {
4 len <- length(x)
5 if (!len) {
6 acc
7 }
8 else {
9 first <- x[[1L]]
10 rest <- tail(x, -1L)
11
12 if (right) iter(f(first, acc), rest) else iter(f(acc, first), rest)
13 }
14 }
15
16 f <- match.fun(f)
17 mis <- missing(init)
18 len <- length(x)
19
20 if (mis && !len) {
21 return(NULL)
22 }
23
24 if (mis) {
25 if (right) {
26 init <- x[[len]]
27 x <- rev(head(x, -1L))
28 }
29 else {
30 init <- x[[1L]]
31 x <- tail(x, -1L)
32 }
33 }
34 else {
35 if (right) {
36 x <- rev(x)
37 }
38 }
39
40 iter(init, x)
41 }</code></pre>
<p>Changes with respect to the previous implementation are:</p>
<ol style="list-style-type: decimal">
<li>The nested function <code>iter</code> has been modified so that the recursive call passes parameters to <code>f</code> in the order suitable to the requested kind of fold [line 12].</li>
<li>The handling of options for the arguments <code>init</code> and <code>right</code> has been appropriately extended [lines 24-38]. Note, in particular, that when right is <code>TRUE</code> we need to reverse the sequence, as explained. The R function <code>rev</code> does the job [lines 27, 36].</li>
</ol>
<p>This implementation passes our current test set. Nice!</p>
<p>Do you think that this function is getting long? If so you are with me. As I said before I prefer shorter functions with as few parameters as possible and independent helpers. But R base code is plenty of this kind of monolithic functions rich in parameters and conditional branches.</p>
<p>[To be continued]</p>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-78159975046460622352016-07-31T13:06:00.001+02:002016-08-03T08:50:53.678+02:00Reading R Code. The function Reduce. Folds<p>Among the essential higher-order functions in functional programming (we have already seen <code>map</code> and <code>filter</code>) <code>reduce</code> (aka. <code>fold</code>) is probably the most amazing and powerful. It is the Swiss-Army-knife in functional programmers' hands, sort of. I highly recommend this excellent exposition, about the power and flexibility of <code>fold</code>:</p>
<p><a href="http://www.cs.nott.ac.uk/~pszgmh/fold.pdf">http://www.cs.nott.ac.uk/~pszgmh/fold.pdf</a></p>
<h3 id="what-is-fold">What is <code>fold</code>?</h3>
<p>Although <code>fold</code> deserves and probably requires several blog posts, I'll try an elementary presentation suitable to our purposes.</p>
<p><code>fold</code> is essentially the functional abstraction that corresponds to the very structure of lists.</p>
<p>A list of whatever kind of values, say, type <code>X</code>, is either the empty list or a list constructed from an <code>X</code> and a list of <code>X</code>s, where by construction I mean the operation of <code>cons</code>ing <code>X</code> and a list of <code>X</code>s. For instance (in Lisp):</p>
<pre><code>[1]> (cons 1 (list 2 3))
(1 2 3)</code></pre>
<p>or in Haskell, where <code>:</code> is the Haskell equivalent to the Lispier <code>cons</code>:</p>
<pre><code>Prelude> 1 : [2, 3]
[1,2,3]</code></pre>
<p>Note the self-referential nature of the type description for lists. Any function over lists relies on this self-referential structure, and it will have a natural form, where the recursive call emanates naturally from the type description. So the sketch or template of a function for list of <code>X</code> looks like this [written now in Racket]:</p>
<pre><code>(define (f-for-lox lox)
(cond [(empty? lox) (...)]
[else
(... (first lox)
(f-for-lox (rest lox)))]))
</code></pre>
<p>On the other hand, and by stack space efficiency reasons, functions over lists can be written in such a way that the recursive call is in tail position (in a position that ensures that is the last call in the procedure). Observe that <code>f-for-lox</code>, the recursive call in the template above, is not in tail position; instead, the function ending up in place of the last three dots in that template will be in tail position.</p>
<p>I heartily recommend to watch these excellent videos, on which this exposition is mostly based, for a better understanding:</p>
<ol style="list-style-type: decimal">
<li><a href="https://www.youtube.com/watch?v=Z7qXdkt7yOE">https://www.youtube.com/watch?v=Z7qXdkt7yOE</a></li>
<li><a href="https://www.youtube.com/watch?v=nx8BALSH3nY">https://www.youtube.com/watch?v=nx8BALSH3nY</a></li>
<li><a href="https://www.youtube.com/watch?v=fMqbMiGuQ9c">https://www.youtube.com/watch?v=fMqbMiGuQ9c</a></li>
<li><a href="https://www.youtube.com/watch?v=6NyPb8jQROs">https://www.youtube.com/watch?v=6NyPb8jQROs</a></li>
</ol>
<p>Such functions where the recursive call is in tail positions are known as tail-recursive functions and normally written with the aid of a local function that supplies an accumulator. A tail-recursive template for lists has this form:</p>
<pre><code>(define (f-for-lox lox0)
(local [(define (f-for-lox-acc acc lox)
(cond [(empty? lox) (... acc)]
[else
(f-for-lox-acc (... acc (first lox))
(rest lox))]))]
(f-for-lox-acc ... lox0)))</code></pre>
<p>Or this one, if we change the order of components in the recursive call:</p>
<pre><code>(define (f-for-lox lox0)
(local [(define (f-for-lox-acc acc lox)
(cond [(empty? lox) (... acc)]
[else
(f-for-lox-acc (... (first lox) acc)
(rest lox))]))]
(f-for-lox-acc ... lox0)))</code></pre>
<p><code>fold</code> is the function that capture these two abstractions. Accordingly, there are two possible <code>fold</code> functions (the last one with two variants), <code>fold-right</code> that captures the structural recursive procedure, and <code>fold-left</code> that captures the tail-recursive one.</p>
<p>Let's see this. If we fill those templates with more meaningful placeholders, <code>i</code> standing for the initial value, and <code>f</code> standing for a function to combine the contribution of the first element and the contribution of the recursion, and we add them as parameters to the functions, we have this function for the natural recursive template:</p>
<pre><code>(define (fold-right f i lox)
(cond [(empty? lox) i]
[else
(f (first lox)
(f-for-lox (rest lox)))]))</code></pre>
<p>and this two variants for the two tail-recursive templates:</p>
<pre><code>(define (fold-left-1 f i lox0)
(local [(define (f-for-lox-acc acc lox)
(cond [(empty? lox) acc]
[else
(f-for-lox-acc (f acc (first lox))
(rest lox))]))]
(f-for-lox-acc i lox0)))
(define (fold-left-2 f i lox0)
(local [(define (f-for-lox-acc acc lox)
(cond [(empty? lox) acc]
[else
(f-for-lox-acc (f (first lox) acc)
(rest lox))]))]
(f-for-lox-acc i lox0)))</code></pre>
<p>that are precisely <code>fold-right</code> and <code>fold-left</code>, with two variants, respectively.</p>
<p>A careful analysis of those functions enable us to infer their respective signatures.</p>
<pre><code>;; fold-right :: (X Y -> Y) Y (listof X) -> Y
;; fold-left-1 (1st variant) :: (Y X -> Y) Y (listof X) -> Y
;; fold-left-2 (2nd variant) :: (X Y -> Y) Y (listof X) -> Y</code></pre>
<p>Note, in particular, that <code>fold-right</code> and the second variant of <code>fold-left</code> share the same signature, while the signature for the first variant of <code>fold-left</code> differs due to the different order of arguments in the call to <code>f</code>.</p>
<p><code>fold-right</code> is basically the same in all functional programming languages, while different languages pick the first version of <code>fold-left</code> (Lisp, Haskell, OCaml, ...) or the second one (SML, Racket, ...) for its implementation of this function.</p>
<p>The difference between the two variants of <code>fold-left</code> have an impact in many cases. Just choose as <code>f</code> a function for which the order of operands matters (a non-commutative function). For instance if <code>f</code> is <code>+</code> the order doesn't matter but if it is <code>-</code> it absolutely matters. Let's consider this for <code>-</code> in all the languages we have mentioned:</p>
<pre><code># Lisp [reduce is fold-left by default]
[1]> (reduce #'- '(1 2 3 4) :initial-value 0)
-10
# Haskell
Prelude> foldl (-) 0 [1, 2, 3, 4]
-10
# OCaml
# List.fold_left (-) 0 [1; 2; 3; 4] ;;
- : int = -10</code></pre>
<p>While:</p>
<pre><code># Racket
> (foldl - 0 '(1 2 3 4))
2
# SML
- List.foldl op- 0 [1, 2, 3, 4] ;
val it = 2 : int</code></pre>
<p>[The R's <code>Reduce</code> will be the subject of the next post.]</p>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-14402859720091151452016-07-28T10:26:00.001+02:002016-07-28T23:59:45.133+02:00Reading R code. The functions Map and mapply<p>In this post I'm going to consider the implementation of the function <code>Map</code> in R. As in the previous post in the series, I first introduce the typical <code>map</code> in functional languages. Then I go into the R's <code>Map</code> implementation. Along the way I explore some other R functions to the extent needed for a basic understanding of the implementation.</p>
<h3 id="what-is-map">What is <code>map</code></h3>
<p><code>map</code> applies a function to each element in a given list to get a new list with the result of the application. For instance:</p>
<pre><code>map sqrt [1, 2, 3]</code></pre>
<p>produces a list of the square roots of each number:</p>
<pre><code>[1.0,1.4142135623730951,1.7320508075688772]</code></pre>
<p>In general, <code>map</code> takes a function and a list of elements of some type, say <code>a</code>, and produces a list of elements of some other type, say <code>b</code>, where <code>a</code> and <code>b</code> are the types of, respectively, the input and output values of the function that <code>map</code> takes. The signature for the example above could be written as:</p>
<pre><code>(Num -> Double) [Num] -> [Double]</code></pre>
<p>meaning that the first argument, <code>sqrt</code>, takes a number (in general) and produces a double-precision floating point number; the second argument is a list of numbers, and the result a list of <code>Double</code>s.</p>
<p>So in general <code>map</code> has this signature:</p>
<pre><code>(a -> b) [a] -> [b]</code></pre>
<p>As with <code>filter</code> a natural implementation of <code>map</code> is a math-like recursive definition:</p>
<pre><code>map f [] = []
map f (x:xs) = f x : map f xs</code></pre>
<p>Or, in Racket:</p>
<pre><code>(define (map f lox)
(cond [(empty? lox) empty]
[else
(cons (f (first lox))
(map f (rest lox)))))</code></pre>
<p>This is the basic usage. The function <code>map</code> can usually handle multiple lists too. One of the variants works as follows:</p>
<p>It applies the function to the first element of given lists, then to the second, and so on till the last element of the shortest list is processed, the rest of elements of longer lists are ignored, and the result is the list produced by the successive application. This is a possible example with two lists as input. I use the name <code>zipWith</code> to refer to this kind of <code>map</code> that can take in two lists:</p>
<pre><code>zipWith (+) [1, 2, 3] [4, 5, 6, 7]</code></pre>
<p>produces:</p>
<pre><code>[5, 7, 9] -- so [1 + 4, 2 + 5, 3 + 6]</code></pre>
<p>The signature would be:</p>
<pre><code>zipWith :: (a b -> c) [a] [b] -> [c]</code></pre>
<p>Generalizations to cope with an arbitrary number of lists are also available in functional languages.</p>
<h3 id="map-in-r">Map in R</h3>
<p>The R <code>Map</code> function provides, as <code>?Map</code> states, a generalization of the sort described:</p>
<blockquote>
<p>‘Map’ applies a function to the corresponding elements of given vectors.</p>
</blockquote>
<p>(Note that like <code>Filter</code> the consumed objects are R vectors.)</p>
<p>However, unlike the generalized <code>map</code> mentioned, <code>Map</code> doesn't discard remaining elements of longer lists. Instead, it uses recycling:</p>
<blockquote>
<p>‘Map’ ... is similar to Common Lisp's ‘mapcar’ (with arguments being recycled, however)</p>
</blockquote>
<p>Common-Lisp <code>mapcar</code> is a function that behaves as explained above (the Haskell's <code>zipWith</code> example). Indeed, executing the corresponding Lisp code on the <code>clisp</code> interpreter gives the same result:</p>
<pre><code>[1]> (mapcar #'+ '(1 2 3) '(4 5 6 7))
(5 7 9)</code></pre>
<p>while in R recycling is at work, though:</p>
<pre><code>> Map(`+`, 1:3, 4:7)
[[1]]
[1] 5
[[2]]
[1] 7
[[3]]
[1] 9
[[4]]
[1] 8</code></pre>
<p>As you can see, when the shortest list is exhausted, R recycles it as needed: the last element is the result of 1 + 7, where 1 comes here from recycling the shorter list. Note also that the result is not an atomic vector as one might expect but a list. This is also documented:</p>
<blockquote>
<p>‘Map’ ... does not attempt to simplify the result ... Future versions may allow some control of the result type.</p>
</blockquote>
<p>As for the implementation <code>?Map</code> also tells us what it is:</p>
<blockquote>
<p>‘Map’ is a simple wrapper to ‘mapply’.</p>
</blockquote>
<p>It is not uncommon in R to find traces of implementation details in its docs.</p>
<p>The actual implementation expresses in code all of these points:</p>
<pre><code>function (f, ...)
{
f <- match.fun(f)
mapply(FUN = f, ..., SIMPLIFY = FALSE)
}</code></pre>
<p>Let's have a closer look into the definition.</p>
<p>The function takes a first argument <code>f</code>, the mapping function to be applied, and an undetermined number of extra arguments. The three dots construct allows to catch those extra arguments and pass them on later to <code>mapply</code>. Vectors on which <code>f</code> will be applied are among the arguments that the caller will pass; eventually, more arguments might be passed by the caller, arguments that <code>mapply</code> can receive.</p>
<p>In the first line, the function passed as first argument is extracted and saved into a local variable <code>f</code>, or discarded if it cannot be interpreted in any way as a function, that's what <code>match.fun</code> basically does.</p>
<h3 id="the-function-mapply">The function <code>mapply</code></h3>
<p>Once this first argument has been checked, it is passed as the <code>FUN</code> argument to <code>mapply</code> that in turn calls the underlying C code to carry out the computation. Additionally, <code>Map</code> sets the <code>SIMPLIFY</code> argument of <code>mapply</code> to <code>FALSE</code> to avoid the simplification, as documented.</p>
<p>We can see this better if we take a look at the implementation of <code>mapply</code>:</p>
<pre><code>function(FUN,..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE)
{
FUN <- match.fun(FUN)
dots <- list(...)
answer <- .Internal(mapply(FUN, dots, MoreArgs))
if (USE.NAMES && length(dots)) {
if (is.null(names1 <- names(dots[[1L]])) && is.character(dots[[1L]]))
names(answer) <- dots[[1L]]
else if (!is.null(names1))
names(answer) <- names1
}
if(!identical(SIMPLIFY, FALSE) && length(answer))
simplify2array(answer, higher = (SIMPLIFY == "array"))
else answer
}</code></pre>
<p>Note that <code>answer</code>, the returned value, is the result produced by the internal function, written in C, which is invoked via the call to <code>.Internal</code>. We come to this construct over and over while reading R base functions. Many R base functions just wrap the call to an underlying function, eventually preparing things for it, and/or adapting the result of it according to the arguments passed in the first place.</p>
<p><code>Map</code> sets <code>mapply</code>'s <code>SIMPLIFY</code> to <code>FALSE</code> so that the simplification that <code>mapply</code> could do otherwise will never be executed.</p>
<p>However, despite the fact that the intended arguments for the three dots are just the vectors, as <code>?Map</code> documents, nothing prevents us from passing other possible <code>mapply</code> arguments, <code>USE.NAMES</code> and <code>MoreArgs</code>.</p>
<p><code>MoreArgs</code> allows for passing extra arguments to the <code>f</code> function. For instance, to get a list of vectors where 42 is repeated from 1 to 4 times, we can use <code>MoreArgs</code> in this way [example borrowed from the <code>mapply</code> doc]:</p>
<pre><code>> Map(rep, times = 1:4, MoreArgs = list(x = 42))
[[1]]
[1] 42
[[2]]
[1] 42 42
[[3]]
[1] 42 42 42
[[4]]
[1] 42 42 42 42</code></pre>
<p>As R allows us to pass extra arguments just by naming them after the function,</p>
<pre><code>> Map(rep, times = 1:4, x = 42)</code></pre>
<p><code>MoreArgs</code> seems to be of little use in this regard.</p>
<p>Furthermore, one can always resort to the commonly-used idiom in functional programming: supplying an anonymous function in place of <code>f</code>:</p>
<pre><code>> Map(function(x) rep(42, times = x), 1:4)</code></pre>
<p>The other extra option that we may pass to <code>Map</code> is <code>USE.NAMES</code>. <code>mapply</code> sets it by default to <code>TRUE</code>. In such a setting, and if more arguments apart form the initial function <code>f</code> are passed (<code>length(dots)</code> is not 0) this code in <code>mapply</code> will be executed:</p>
<pre><code>if (USE.NAMES && length(dots)) {
if (is.null(names1 <- names(dots[[1L]])) && is.character(dots[[1L]]))
names(answer) <- dots[[1L]]
else if (!is.null(names1))
names(answer) <- names1
}</code></pre>
<p>Two cases are especially handled:</p>
<ul>
<li>The second argument passed to <code>mapply</code> (<code>dots[[1L]]</code>) doesn't have names but it is of character type. (Recall that the first argument is always the function <code>f</code>).</li>
<li>The second argument passed to <code>mapply</code> has names.</li>
</ul>
<p>In the first case, the character object is used to set the names of the result of <code>mapply</code>, as in this example taken from <code>?mapply</code>:</p>
<pre><code>> mapply(function(C, k) paste0(rep.int(C, k)), LETTERS[1:6], 6:1)</code></pre>
<p>In the second case, the names of the argument become the names of the result. For instance [again from <code>?mapply</code>]:</p>
<pre><code>> mapply(function(x, y) seq_len(x) + y,
> + c(a = 1, b = 2, c = 3), # names from first
> + c(A = 10, B = 0, C = -10))</code></pre>
<p>This is simply put on the doc:</p>
<blockquote>
<p>use names if the first ... argument has names, or if it is a character vector, use that character vector as the names.</p>
</blockquote>
<p>If we want no names handling in <code>Map</code> we can just set <code>USE.NAMES</code> to <code>FALSE</code> overriding the default behavior of <code>mapply</code>, or viewed from the implementation, falling back to the default behavior of the internal C code.</p>
<p>A couple of points about R idioms that we see in the <code>mapply</code>:</p>
<p>Assignments can be sub-expressions. Here:</p>
<pre><code>is.null(names1 <- names(dots[[1L]]))</code></pre>
<p>The assignment expression <code>names1 <- names(dots[[1L]])</code> evaluates to the value of <code>names1</code>, once assigned to. The value is passed to <code>is.null</code> as a part of the <code>if</code> condition. The value of <code>names1</code> is later re-used in the last branch.</p>
<p>Secondly, in a logical context, the number 0 evaluates to <code>FALSE</code>, any other number evaluates to <code>TRUE</code>. This allows for concise conditions as the previous one:</p>
<pre><code>if (USE.NAMES && length(dots))</code></pre>
<p>The second part of the relational expression check whether <code>dots</code> is empty or not. Hence, there is no need to write <code>length(dots) != 0</code>. This is a common idiom in many languages supporting boolean evaluation of different classes of objects.</p>
<p>To complete the basic understanding of these functions I should inspect more closely <code>match.fun</code> and <code>simplify2array</code> (the function responsible of simplifying the result). <code>match.fun</code> appears frequently in R code and I pin it on top of my task list, <code>simplify2array</code> is a helper function currently used only by <code>mapply</code> and <code>sapply</code>. We can live without studying it for the time being.átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-54107052169286012752016-07-26T14:42:00.000+02:002016-07-27T11:07:42.226+02:00Reading R code. The function Filter<p>R is functional language, however not a pure one like Haskell, R is fond of the functional paradigm. As John Chambers has recently remind us, in R <em>everything that happens is a function call</em> [John Chambers, <em>Extending R</em>, CRC Press]. R functions are first-class citizens and the functional programming style is prominent.</p>
<p><code>filter</code>, <code>map</code>, and <code>reduce</code> (aka. <code>fold</code>) are among the most wanted functions in the toolset of every functional programmer. R provides them too under the names <code>Filter</code>, <code>Map</code>, <code>Reduce</code>. Even though the <em>apply</em> family and vectorization are preferred, R is kind enough to give these functions to seasoned functional programmers. It couldn't have been otherwise.</p>
<p>The source code of base R, currently at <code>/src/library/base/R</code> contains a file with the implementation of these functions among others, <code>funprog.R</code>.</p>
<p>In my exploration of R source code I have to begin with something. <code>Filter</code> may be a good candidate. It is brief, easier to read, and a sensible choice for a fan of functional programming as I am.</p>
<h3 id="what-is-filter">What is filter?</h3>
<p><code>filter</code>, as its name suggests, serves the purpose of filtering elements in a list according to certain given function, called predicate, so that elements in this list for which the predicate holds are selected and the rest discarded, where <em>predicate</em> is the technical term for any function that produces true or false (a boolean value).</p>
<p>Some examples will make this clear.</p>
<p>Let's suppose we have this list of numbers <code>[1, 2, 3, 4, 5]</code>, and a function <code>odd</code> that takes a number and determines whether it is odd or not. Selecting all odd numbers in <code>[1, 2, 3, 4, 5]</code> is a matter of filtering them by their oddity</p>
<pre><code>filter odd [1, 2, 3, 4, 5]</code></pre>
<p>will produce <code>[1, 3, 5]</code></p>
<p>Another example. We want to select words in <code>["hi", "world", "bye", "hell"]</code> whose first letter is 'h'.</p>
<pre><code>filter start_with_h ["hi", "world", "bye", "hell"]</code></pre>
<p>will produce the desired list <code>["hi", "hell"]</code></p>
<p>In general, <code>filter</code> takes a predicate and a list of elements of some type, say type <code>a</code>, and produces a list of elements of the same type. Formally expressed:</p>
<pre><code>filter :: (a -> Bool) [a] -> [a]</code></pre>
<p>where <code>[a]</code> stands for list of elements of type <code>a</code>, and the arrow stands for a function whose arguments are to the left and the result to the right of the arrow. Note that the predicate, the first argument, is also a function that consumes values of type <code>a</code> and produces a boolean.</p>
<p>This type description is called the <em>signature</em> of the function. In some functional programming languages it is customary to apply the so called <em>currying</em> operation that translates the evaluation of a function taking multiple arguments into the evaluation of a sequence of functions, with a single argument each. Under currying, the signature of filter would be:</p>
<pre><code>filter :: (a -> Bool) -> [a] -> [a]</code></pre>
<p>Although this is a mathematically more appealing description, I'll stick here and in what follows to the first uncurried form.</p>
<p>What about implementing <code>filter</code>? A natural implementation of <code>filter</code> would take the form of a mathematical definition.</p>
<p>Mathematics is plenty of inductive or recursive definitions. Do you recall factorial? In Math, it is defined as a two-part function:</p>
<pre><code>factorial 0 = 1
factorial n = (factorial n - 1) * n</code></pre>
<p>In words, the factorial of 0 is equal to 1, the factorial of <em>n</em> is equal to the factorial of <em>n</em> - 1 times <em>n</em>.</p>
<p>Similarly, a math-like definition for <code>filter</code> could be expressed as a two-part function with two branches for the second part:</p>
<ul>
<li>The filter of a predicate and an empty list is the empty list.</li>
<li>The filter of a predicate and a non-empty list is
<ul>
<li>either (if the predicate holds for the first element of the given list) the list consisting of that first element and the result of the filter of the predicate and the rest of the given list,</li>
<li>or (otherwise) the filter of the predicate and the rest of the given list.</li>
</ul></li>
</ul>
<p>This wording is exact but verbose. Formalizing it a bit turns out to be easier to read. What follows is the actual Haskell implementation of <code>filter</code>, that hopefully is almost self-explanatory. Actually the signature before is also the real Haskell signature.</p>
<pre><code>filter p [] = []
filter p (x:xs) | p x = x : filter p xs
| otherwise = filter p xs</code></pre>
<p>Another possible syntax, now written in friendly Racket, that you may find even more readable, looks as follows [signature included as an initial comment]:</p>
<pre><code>;; (X -> Boolean) (listof X) -> (listof X)
(define (filter p lox)
(cond [(empty? lox) empty]
[else
(if (p (first lox))
(cons p (filter p (rest lox)))
(filter p (rest lox)))))</code></pre>
<h3 id="filter-in-r">Filter in R</h3>
<p>What about the R implementation? Here it is:</p>
<pre><code>Filter <-
function(f, x)
{
ind <- as.logical(unlist(lapply(x, f)))
x[which(ind)]
}</code></pre>
<p>It looks quite different. Obviously, it is not recursive. This is not surprising, recursion is the main technique in pure functional languages that are well prepared to handle recursive implementations without performance penalties.</p>
<p>Having noted that, let's explore this code in detail.</p>
<p>The very first thing we always have to do when studying a function definition is to be sure about the value(s) that the function consumes and the value that the function produces, the signature of the function. By the way, as you may know, some languages check signatures at compile time, like Haskell, others don't, like R or the Racket version above [though Racket has variants for type checking]. Whatever the case the signature is critical for programmers and for users, since it tells what kind of values are involved.</p>
<p>The documentation for <code>Filter</code> reveals the assumed signature. Omitting for the moment some nuances and summarizing the 'Arguments' and 'Details' section , <code>?Filter</code> says this:</p>
<blockquote>
<p>‘Filter’ applies the unary predicate function ‘f’ to each element of ‘x’ [which is a vector] ..., and returns the subset of ‘x’ for which this gives true.</p>
</blockquote>
<p>Recall the header of <code>Filter</code></p>
<pre><code>function(f, x)</code></pre>
<p>The doc says that <code>f</code>, the first argument, is a <em>unary predicate function</em>, meaning a function that takes a single argument (unary) [of any type, say <code>a</code> as before], and produces a boolean (<code>TRUE</code> or <code>FALSE</code>). The signature of the predicate is therefore:</p>
<pre><code>(a -> Bool)</code></pre>
<p>The second argument, <code>x</code>, is in turn a vector, instead of a list. A vector in R can be an atomic vector, a list, or an expression. The main R data type to represent arbitrarily large collections of objects, possibly nested, is vector and in this regard is a natural choice to represent what in functional languages is typically represented by lists.</p>
<p>Let us symbolize, just by convention, vector of some type with <code>[a]</code>, that's the type of the second argument of <code>Filter</code>.</p>
<p>The result of <code>Filter</code> is in turn a subset of <code>x</code>, hence again a vector of type <code>[a]</code>.</p>
<p>Therefore the whole signature could be specified as follows:</p>
<pre><code>Filter :: (a -> Bool) [a] -> [a]</code></pre>
<p>As it's easy to see, this is the same signature of <code>filter</code> we figure out before, just that <code>[a]</code> stands for list in the former and for vector (atomic or not) in the latter.</p>
<p>Let's go on with the implementation. In order to understand an implementation I find really helpful trying first to design our own version.</p>
<p>Before starting off with the implementation itself we should write examples of the function usage, at least as many as the function signature requires, and wrap them as test cases. Examples will guide the implementation, and at the same time, wrapped as test cases, provide for the indispensable testing workhorse. For the latter I will use <code>testthat</code> as explained in the final part of the first post in this series: <a href="https://los-pajaros-de-hogano.blogspot.com.es/2016/07/reading-r-code-introduction.html">https://los-pajaros-de-hogano.blogspot.com.es/2016/07/reading-r-code-introduction.html</a></p>
<p>So save these examples into the file <code>test_my_filter.R</code>:</p>
<pre><code>source("my_filter.R")
# Some predicates for testing filter
odd <- function(x) { x %% 2 != 0 }
starts_with_h <- function(x) { any(grepl(pattern = "^h.*", x = x)) }
numeric_expression <- function(x) { is.numeric(eval(x)) }
test_that("test my_filter", {
expect_equal(my_filter(odd, integer(0)), integer(0))
expect_equal(my_filter(is.atomic, list()), list())
expect_equal(my_filter(is.expression, expression()), expression())
expect_equal(my_filter(odd, 1:5), c(1, 3, 5))
expect_equal(my_filter(starts_with_h, c("hi", "world", "bye", "hell")),
c("hi", "hell"))
expect_equal(my_filter(is.atomic, list(1, list(3, 4), 5)), list(1, 5))
expect_equal(my_filter(numeric_expression, expression(c, "a", 1, 3)),
expression(1, 3))
})
</code></pre>
<p>A quick glance at <code>Filter</code> gives us a first hint as to the way we could follow in the implementation. The last line is a typical subsetting operation. We could try to use this idea to delineate our code.</p>
<p>You may figure it out after a bit of thinking. Let's begin with one of our examples:</p>
<pre><code>my_filter(odd, 1:5)</code></pre>
<p>Well, I have something like the vector <code>c(1, 2, 3, 4, 5)</code> and I want to filter odd elements in it. I also have a predicate <code>odd</code> that allows me to determine whether each element there is odd or not.</p>
<p>How should <code>my_filter</code> be implemented to produce the expected <code>c(1, 3, 5)</code>?</p>
<p>It could apply <code>odd</code> to each element in <code>c(1, 2, 3, 4, 5)</code> to get this: <code>c(TRUE, FALSE, TRUE, FALSE, TRUE)</code>, and then, in a typical R way, use the logical vector for indexing <code>c(1, 2, 3, 4, 5)</code>:</p>
<pre><code>c(1, 2, 3, 4, 5)[c(TRUE, FALSE, TRUE, FALSE, TRUE)]</code></pre>
<p>I can sketch a general version of this idea. Instead of <code>odd</code> and <code>1:5</code> I generalize to whatever predicate, <code>f</code>, and whatever vector, <code>x</code>. Also, and just for readability, I create a local variable, <code>ind</code>, to name the logical vector.</p>
<pre><code>my_filter <-
function(f, x) {
ind <- # apply f to each element in x
x[ind]
}</code></pre>
<p>Only one thing remains to be filled here, the code that applies <code>f</code> to each element in <code>x</code>.</p>
<p>If you have an imperative programming background you will come up with some kind of loop to process each element in the vector and check whether the predicate holds for it. Something along these lines:</p>
<pre><code>apply_f <-
function(f, x) {
fx <- vector("logical")
for (i in seq_along(x)) {
fx <- c(fx, f(x[[i]]))
}
fx
}</code></pre>
<p>Let's add this helper function to <code>my_filter</code> as a nested (local) function</p>
<pre><code>my_filter <-
function(f, x) {
apply_f <-
function() {
fx <- vector("logical")
for (i in seq_along(x)) {
fx <- c(fx, f(x[[i]]))
}
fx
}
ind <- apply_f()
x[ind]
}</code></pre>
<p>Wait a minute! Shouldn't <code>apply_f</code> be tested before doing anything else? and definitely before converting it into a local (and as such untestable) function? This is a great question. Let's claim that it is not needed because it is a too easy function and we trust it will work without further testing. In a moment we will return to this and see the consequences of this assumption.</p>
<p>It's time to run tests:</p>
<pre><code>> library(testthat)
> test_file("test_my_filter.R")</code></pre>
<p>Great! All tests passed.</p>
<p>However, instead of explicit loops you may know that idiomatic R usually favors the <em>apply</em> family of functions.</p>
<p>The immediate candidate for this task seems to be <code>sapply</code>, that is typically used when we want a vector as a result of applying the function to each element in a given vector <code>x</code>. <code>apply_f</code> could be written with <code>sapply</code> as follows:</p>
<pre><code>apply_f <-
function(f, x) {
sapply(x, f)
}</code></pre>
<p>Being so simple, it doesn't make sense to wrap the <code>sapply</code> call in a function. So our more idiomatic and concise filter implementation would be:</p>
<pre><code>my_filter <-
function(f, x) {
ind <- sapply(x, f)
x[ind]
}</code></pre>
<p>Run tests ... and, oops! one test fails! The case for the empty vector as input.</p>
<p>Looking into <code>?sapply</code> more carefully, we see that our initial expectation was based in a partial understanding of <code>sapply</code>:</p>
<blockquote>
<p>Simplification in ‘sapply’ is only attempted if ‘X’ has length greater than zero ...</p>
</blockquote>
<p>It turns out that <code>sapply</code> does not always produce a vector when a vector is given. In other words, our current implementation breaks the signature of <code>my_filter</code>.</p>
<p>We have different alternatives to fix the bug. One would be to use a conditional with one branch for the empty vector and another one for the rest of cases. Another option is to use <code>lapply</code>, that produces a list instead of a vector, and unlist the result to get the vector we need. This second strategy has an advantage. Core R code shouldn't use user friendly wrappers like <code>sapply</code> but primitive functions. It is not only a matter of style, it is usually a matter of performance (core functions are more efficient), and it is above all a matter of reliability. User wrappers are for helping users write quickly, mainly in an interactive setting, but they may change or even become defunct in future releases. In general, we shouldn't trust user wrapper functions when we want to write enduring code.</p>
<p>With <code>lapply</code> and <code>unlist</code> we get this solution:</p>
<pre><code>my_filter <-
function(f, x) {
ind <- unlist(lapply(x, f))
x[ind]</code></pre>
<p>}</p>
<p>and it passes all tests.</p>
<p>If we compare our last version with the official implementation we'll observe that there are still some differences.</p>
<p>Starting from the end, the official implementation prefers <code>which(ind)</code> instead of just <code>ind</code> for indexing. This is a subtle divergence. One thing it handles differently than our implementation is <code>NA</code> values. In our implementation, if a <code>NA</code> appears in <code>x</code> it will appear also as <code>NA</code> in <code>ind</code>, and if we subset <code>x</code> directly via <code>ind</code> the <code>NA</code> ends up in the result too. However, <code>?Filter</code> has to say something about this (just we omitted it in the first reading to keep things simple):</p>
<blockquote>
<p>Note that possible ‘NA’ values are currently always taken as false.</p>
</blockquote>
<p>This implies that <code>NA</code> values in <code>x</code> will be discarded from the result, whatever <code>f</code> and <code>x</code>, and this is exactly what <code>which</code> can nicely achieve. <code>which</code> takes a logical object and produces the indices, as integers, of <code>TRUE</code> values. In other words, for our case, where a vector is given as input, it has this signature:</p>
<pre><code>which :: [Bool] -> [Integer]</code></pre>
<p>For instance,</p>
<pre><code>> which(c(TRUE, FALSE, FALSE, TRUE))
[1] 1 4</code></pre>
<p>Additionally, <code>which</code> omits <code>NA</code> values in the logical vector, in other words, it treats them as if they were <code>FALSE</code>. For example:</p>
<pre><code>> which(c(TRUE, FALSE, FALSE, TRUE, NA))
[1] 1 4</code></pre>
<p>and that's precisely the documented behavior that <code>Filter</code> should show.</p>
<p>The second difference lies in the first line, the explicit conversion to logical absent from our solution:</p>
<pre><code>as.logical(unlist(lapply(x, f)))</code></pre>
<p>Someone could argue that <code>as.logical</code> is redundant. After all, the predicate <code>f</code> always produces a boolean value and the result of the composition of <code>lapply</code> and <code>unlist</code> should produce a logical vector. It should do, but it actually doesn't. Passing an empty vector to <code>unlist(lapply(...))</code> produces <code>NULL</code>:</p>
<pre><code>> unlist(lapply(integer(0), is.integer))
NULL</code></pre>
<p>Inadvertently our previous implementation worked, and it worked because when <code>NULL</code> is an index it is treated as <code>integer(0)</code> and indexing an empty vector with <code>integer(0)</code> produces an empty vector, logical by default.</p>
<pre><code>> vector()[integer(0)]
logical(0)</code></pre>
<p>Once we substitute direct indexing by <code>which(ind)</code> things stop working, <code>which(NULL)</code> would raise error because <code>which</code> requires a logical object as argument, and <code>NULL</code> is neither a logical value nor converted by <code>which</code> to logical. That's the reason for introducing <code>as.logical</code>. In general, <code>as.logical</code> makes a lot of sense by making sure that the signature of <code>which</code> will never be broken.</p>
<p>Let's go back to the moment when we hesitated about why not to test the helper function <code>apply_f</code>. Under the new light the question appears pretty reasonable. Had we tested <code>apply_f</code>, in other words, had we left it as a non-local function and verified it independently, we would have caught this bug soon.</p>
<p>For instance, we could have written <code>apply_f</code> as:</p>
<pre><code>apply_f <-
function(f, x) {
unlist(lapply(x, f))
}</code></pre>
<p>and add tests for it:</p>
<pre><code>test_that("test apply_f", {
expect_equal(apply_f(is.integer, integer(0)), logical(0))
expect_equal(apply_f(odd, 1:5), c(TRUE, FALSE, TRUE, FALSE, TRUE))
# ... (more tests here)
})</code></pre>
<p>Running tests would have uncovered the bug in the very beginning. Once <code>apply_f</code> passes all tests we can convert it into a local function, in this case just the call we have seen in the final definition. The morale is that untested code, which we lazily assume being too easy to test is often the source of unexpected bugs and countless wasted hours of debugging. Better to always test everything that is not absolutely trivial.</p>
<p>To finish this exploration and for completeness just add this new test case, which verifies the <code>NA</code> discarding behavior, to the <code>test_my_filter.R</code> file:</p>
<pre><code>expect_equal(my_filter(odd, c(1:4, NA)), c(1, 3))</code></pre>
<p>and confirm that the final implementation with <code>as.logical</code>, that is exactly the R version of <code>Filter</code>, passes all tests.</p>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-91630150288209352632016-07-25T15:34:00.001+02:002016-07-25T15:34:24.701+02:00Reading R code. Introduction<p>One of the most important things I learned when I started my higher education was that in order to really know deeply about a subject one has to dive into the original sources as soon as possible rather than solely sticking to secondary literature. Better to read Plato's and Kant's works in their original language than countless books about Critiques and Dialogues; better to analyze Mozart's and Beethoven's Sonatas (as Charles Rosen does) than skimming over a lot of general articles on Classical music.</p>
<p>When I was put in charge of developing a web site for my institution I hadn't any programming background at all. After some discouraging attempts to learning from intro books and tutorials, I decided to go to the sources and I studied the entire HTML and CSS specifications directly. It was hard, but everything was clear at last [Remark: nowadays it would be overwhelming. HTML and CSS specs are currently huge].</p>
<p>In learning programming languages the next step to the initial exposure to syntax, concepts and techniques might be well studying core libraries written by main developers.</p>
<p>How could one understand better a programming language than by trying to read the code that its core developers have created?</p>
<p>I'm always surprised by the scarcity of commentaries about exemplary code in any language. There is somewhat like a gap between introductory expositions and the source code itself. The latter is silently digested by the people who develop it or who create libraries on top of it, while beginners live always in the other much narrower side, their little friendly universe of tutorials and simple recipes, kind enough, for sure, but maybe a bit tasteless [though great exceptions can always be found out] when their time, the absolute beginners' time, has passed.</p>
<p>One reason for this lack might be that core libraries are very complex and abstract beasts, and an understanding of parts of it is just hopeless without a firm grasp on the whole architecture and design, something unreachable to anyone else but experts.</p>
<p>This is not always the case, though. At least it is not the case for a good bunch of functions in R base code. Many are written in R itself and they are almost self-contained in the sense that a preliminary comprehension doesn't depend on a complete acquaintance with the abstract underlying architecture.</p>
<p>So I've thought that I can give this idea a try, picking some R functions, and reading them with the aim of understanding R better. The goal is educational, self-educational mainly. Along the way I'll try to make things perhaps easier to others with a bit less programming background than mine hoping that in doing so I not only reinforce my understanding but also help others deepen their own.</p>
<p>I'm not an R expert. This means that while reading and trying to make sense of official implementations I probably will make some more or less educated guesses and I could (and surely will) make mistakes. So if someone more knowledgeable than I am (there should be many) read this, please let me know to fix any error, misleading step, or gratuitous deduction.</p>
<p>The intended audience is people with a basic working understanding of R data structures and programming constructs, including conditionals, loops and function definitions. The only required tool is the R console. And for the moment only one extension package will be used, the package <code>testthat</code> for unit testing support. It can be installed as usual via:</p>
<pre><code>> install.packages(testthat)</code></pre>
<p>To use <code>testthat</code> in a simple way (not the best one for real projects but enough for our purposes now) proceed as follows:</p>
<ol style="list-style-type: decimal">
<li>Save the function you want to test in a file.</li>
<li>Create a new file on the same directory for testing that function. The name of this file should start with 'test'.</li>
<li>Source the code of the function to be tested.</li>
<li>Write tests.</li>
<li>Run tests from the console.</li>
</ol>
<p>An example of this workflow.</p>
<p>Save the following function in <code>foo.R</code>:</p>
<pre><code>foo <-
function(x) {
ifelse((identical(x, 1)), "I'm 1", "I'm not 1")
}</code></pre>
<p>Create <code>test_foo.R</code> with this content:</p>
<pre><code>source("foo.R")
test_that("foo is 1 or not 1", {
expect_equal(foo(1), "I'm 1")
expect_equal(foo(0), "I'm not 1")
expect_equal(foo("hi"), "I'm not 1")
})</code></pre>
<p>Load <code>testthat</code> on your session and run tests from the console:</p>
<pre><code>> library(testthat)
> test_file("test_foo.R")</code></pre>
<p>Introducing unit testing from the very beginning may seem unnecessary. On the contrary, testing is of paramount importance, and writing first a minimal set of tests guides the implementation. Since we probably will write our own code here and there, it is crucial to be equipped with a tool that enable us to always write those tests. This is just common-place and fundamental practice whatever the programming language.</p>
<p>By the way, for a perfect introduction to programming fundamentals, where <em>programming</em> stand here for "programming well" rather than "just coding", read this book:</p>
<p><a href="http://www.ccs.neu.edu/home/matthias/HtDP2e/">http://www.ccs.neu.edu/home/matthias/HtDP2e/</a></p>
<p>and/or take this course:</p>
<p><a href="https://www.edx.org/xseries/how-code-systematic-program-design">https://www.edx.org/xseries/how-code-systematic-program-design</a></p>
<p>They both are gems that no one should miss.</p>
<p>One last point about the R source code. Apart from interactively getting the code as usual by typing the function name, for instance:</p>
<pre><code>> which</code></pre>
<p>and its documentation:</p>
<pre><code>> ?which</code></pre>
<p>you can, if you like, download the complete source from</p>
<p><a href="https://cran.r-project.org/sources.html">https://cran.r-project.org/sources.html</a></p>
<p>Also, you can access to the official current snapshot on Subversion if you know how to do so, or browse over (or clone) a non-official github mirror. I'm aware of these two:</p>
<ul>
<li><a href="https://github.com/wch/r-source">https://github.com/wch/r-source</a></li>
<li><a href="https://github.com/SurajGupta/r-source">https://github.com/SurajGupta/r-source</a></li>
</ul>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-90493923417745159522016-07-20T21:44:00.005+02:002016-07-22T08:55:18.575+02:00How to read the R documentation. An example with plot(). <p>From my experience as teaching assistant on several R intro MOOCs I'm getting the impression that beginners, and even intermediate users assume that the R documentation is only for experts and as a consequence don't read the doc in the first place.
<p>This is an unfortunate prejudice since the R built-in documentation is one of its most remarkable features and it is there to help precisely the users. Even though (I admit) some docs are pretty technical, many others are perfectly readable, even for beginners.
<p>In this post I'll try to show the strategy I typically follow when dealing with R doc pages.
<p>Let's take as excuse the following question posted in one of those MOOCs:
<blockquote><cite>What does the function plot do when its input is a data frame?<cite></blockquote>
<h3>Getting the right doc</h3>
<p>The first thing we need to do is to call the help for plot [I put the first few lines of the result]:
<p><code>> ?plot</code>
<pre>
# -------------------
plot package:graphics R Documentation
Generic X-Y Plotting
Description:
Generic function for plotting of R objects. For more details
about the graphical parameter arguments, see ‘par’.
...
# -------------------
</pre>
<p>Note the first sentence in the 'Description' section. It tells us that <code>plot</code> is a <em>generic function for plotting R objects</em>.<cite>.
<p>Not too much, isn't it? but still something. For our purposes <em>generic</em> function means that we need to search for a <em>method</em> (= another function), which will be actually called depending on the class of object we pass to <code>plot</code>. [Since my intent is to guide beginners I omit all discussions regarding technicalities, as that about the exact meaning of <em>method</em> and <em>function</em> in R, as well as the difference between non-generic and generic functions. For the moment take those terms and others of this sort just as jargon we are liberally using to talk about these things].
<p>To know more about <code>plot</code> methods we just type this:
<p><code> > methods(plot)</code>
<p>This is what I get on my installation:
<pre>
[1] plot.acf* plot.data.frame* plot.decomposed.ts*
[4] plot.default plot.dendrogram* plot.density*
[7] plot.ecdf plot.factor* plot.formula*
[10] plot.function plot.hclust* plot.histogram*
[13] plot.HoltWinters* plot.isoreg* plot.lm*
[16] plot.medpolish* plot.mlm* plot.ppr*
[19] plot.prcomp* plot.princomp* plot.profile.nls*
[22] plot.raster* plot.spec* plot.stepfun
[25] plot.stl* plot.table* plot.ts
[28] plot.tskernel* plot.TukeyHSD*
</pre>
<p>Among these methods <code>plot.data.frame</code> is naturally the one which we are interested in. And R helps here too:
<p><code> > ?plot.data.frame </code>
<pre>
# -------------------
plot.data.frame package:graphics R Documentation
Plot Method for Data Frames
Description:
‘plot.data.frame’, a method for the ‘plot’ generic. It is
designed for a quick look at numeric data frames.
Usage:
## S3 method for class 'data.frame'
plot(x, ...)
Arguments:
x: object of class ‘data.frame’.
...: further arguments to ‘stripchart’, ‘plot.default’ or ‘pairs’.
Details:
This is intended for data frames with _numeric_ columns. For more
than two columns it first calls ‘data.matrix’ to convert the data
frame to a numeric matrix and then calls ‘pairs’ to produce a
scatterplot matrix). This can fail and may well be inappropriate:
for example numerical conversion of dates will lose their special
meaning and a warning will be given.
For a two-column data frame it plots the second column against the
first by the most appropriate method for the first column.
For a single numeric column it uses ‘stripchart’, and for other
single-column data frames tries to find a plot method for the
single column.
See Also:
‘data.frame’
Examples:
plot(OrchardSprays[1], method = "jitter")
plot(OrchardSprays[c(4,1)])
plot(OrchardSprays)
plot(iris)
plot(iris[5:4])
plot(women)
# -------------------
</pre>
<h3>Reading Details</h3>
<p>Most R help pages share the same structure consisting of different sections (Title,
Description, Usage, etc)
<p>Some sections may be more important than others depending on what we want to know. For instance, if we only want to know what this function is about in general terms it might be enough to read the terse 'Description'. Or if we already know what the function does but we forget some particular use of certain argument we can look into the 'Arguments' section. If we really want to know what the function exactly does we will need to read 'Details' and 'Examples'.
<p>A superficial reading just doesn't work, we have to take the time to read thoroughly. Let's do it
now.
Even without fully understanding everything a careful reading allows us to outline what this function does:
<ul>
<li>If the given data frame consists of more than two columns, it ideally displays a
<em>scatterplot matrix</em>.</li>
<li>If the data frame has two columns, it displays a suitable <em>y</em> versus <em>x</em> plot, where <em>x</em> is the first column and <em>y</em> the second.</li>
<li>If the data frame has a single numeric column it generates a <em>stripchart</em>; if
that column is not numeric a suitable plot [not specified] is displayed.</li>
</ul>
<h3>Reading examples</h3>
<p>So far so good but an image, an example, worths thousands of words. And R usally excels in providing ready-made examples for us. This is critically important when reading the R documentation. If given, please don't skim examples, work them out. Even more, rather than just running them via <code>example(function_name)</code>, explore them on the console, one by one, at least till you reach a good enough understanding. Let's go on the 'Examples' section.
<p>The first three examples apply <code>plot</code> to the data set <code>OrchardSprays</code>, which a
basic R installation provides by default. This data set has the following structure:
<pre>
> str(OrchardSprays)
'data.frame': 64 obs. of 4 variables:
$ decrease : num 57 95 8 69 92 90 15 2 84 6 ...
$ rowpos : num 1 2 3 4 5 6 7 8 1 2 ...
$ colpos : num 1 1 1 1 1 1 1 1 2 2 ...
$ treatment: Factor w/ 8 levels "A","B","C","D",..: 4 5 2 8 7 6 3 1 3 2 ...
</pre>
<p>So four columns, the first three numeric, and the last one categorical (a <em>factor</em> in R parlance).
<p>The first example passes a data frame with a single column, a one-column subset drawn from the <code>OrchardSprays</code> data frame. Therefore, it's an example for the case where the data frame is made of a single numeric variable. We should expect a <em>stripchart</em>, as documented, and we get that:
<p><code> > plot(OrchardSprays[1], method = "jitter")</code>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjq3uUyCfWfRoJGsdn7zUkOleCxWnhGpYaTRzbTuypl9mmvb03EyshmDVIlXk3s4_UiwlRn1EZgLwbwrNacwoCdC47Dm95XlYhDCBTGM06FUOFM5RHGW4u-Cfs3fn9HSGuislikcV9Gg4/s1600/stripchart.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjq3uUyCfWfRoJGsdn7zUkOleCxWnhGpYaTRzbTuypl9mmvb03EyshmDVIlXk3s4_UiwlRn1EZgLwbwrNacwoCdC47Dm95XlYhDCBTGM06FUOFM5RHGW4u-Cfs3fn9HSGuislikcV9Gg4/s400/stripchart.png" width="400" height="400" /></a></div>
<p>The second example passes this data frame:
<pre>
> str(OrchardSprays[c(4, 1)])
'data.frame': 64 obs. of 2 variables:
$ treatment: Factor w/ 8 levels "A","B","C","D",..: 4 5 2 8 7 6 3 1 3 2 ...
$ decrease : num 57 95 8 69 92 90 15 2 84 6 ...
</pre>
So an example of the second case described above, where the first column (<em>x</em>) is categorical and the second (<em>y</em>) is numerical. A suitable plot would be a series of <em>boxplots</em>, one for level of the categorical variable. And that's exactly what we obtain:
<p><code> > plot(OrchardSprays[c(4, 1)])</code>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLpB7StbvxoSzAFXTi2V-Ps78Nixx-ryi1rOMneHaxo-ja6RdxGUcH4hCCzfvPF81KEJf9k6uIY-vyL8cVOyeJXudOSFhs5pVIZnprxATkv4qr3y_FEMLjRpxnzPIbr582R6AV0qoKl0M/s1600/boxplots.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLpB7StbvxoSzAFXTi2V-Ps78Nixx-ryi1rOMneHaxo-ja6RdxGUcH4hCCzfvPF81KEJf9k6uIY-vyL8cVOyeJXudOSFhs5pVIZnprxATkv4qr3y_FEMLjRpxnzPIbr582R6AV0qoKl0M/s400/boxplots.png" width="400" height="400" /></a></div>
<p>The third example passes the whole data frame. An illustration of the first case mentioned in the doc. And we get the corresponding <em>scatterplot matrix</em>:
<p><code> > plot(OrchardSprays)</code>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUw6eOGwOxx6CdPKJD1gEqZGvf_SoKFmMQQzM0Lnwp9czVWgBXH22jOaFDVggm5h0k71ZOx0gmdgYIlziH-fHJJIGwHjzdEzIeR0kcdK_-1O3D9uL34QcGzn6Tv9krIkyEqqL2xVUXxzA/s1600/scatterplotmatrix.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUw6eOGwOxx6CdPKJD1gEqZGvf_SoKFmMQQzM0Lnwp9czVWgBXH22jOaFDVggm5h0k71ZOx0gmdgYIlziH-fHJJIGwHjzdEzIeR0kcdK_-1O3D9uL34QcGzn6Tv9krIkyEqqL2xVUXxzA/s400/scatterplotmatrix.png" width="400" height="400" /></a></div>
<p>I leave the reader to investigate the last three examples. The only new case is <code>plot(women)</code>, where the input is a data frame with two columns but both numeric in this case.
<p>I honestly believe that reading this doc (as many other R docs) gives more reliable information about the function at hand than googling during hours or skimming over dozens of books.
<h3>Getting and reading the source code</h3>
<p>One more thing is still available to users, the source code itself, that obviously is the definitive answer to all questions.
<p>Many R functions are implemented in R itself, and an intermediate R user should be able to read and understand the implementation, to some extent at least. Yes, many core function are written in C and those are beyond
the level of expertise of a non professional programmer with sufficient time to invest in navigating over the entire C basis and making sense of it. But we can always give a try, just in case.
<p>As for the function <code>plot.data.frame</code> we are lucky.
<p>There is an initial difficulty, though, locating the source. Methods listed by the above mentioned <code>methods</code> function that come suffixed with * are functions whose code cannot be reached by just typing the function name, as usual. The source code is still accessible. In particular, when the function is an S3 method, as plot.data.frame is [I omit commenting about S3 vs S4 methods. See the R manuals
in cran.r-project.org for more info] we have among maybe others any of these two instructions:
<p><code> > getS3method("plot", "data.frame")</code>
<p>or
<p><code> > getAnywhere("plot.data.frame")</code>
<p>that displays this code [line numbers added for commenting below]:
<pre>
1 function (x, ...)
2 {
3 plot2 <- function(x, xlab = names(x)[1L], ylab = names(x)[2L],
...) plot(x[[1L]], x[[2L]], xlab = xlab, ylab = ylab,
...)
4 if (!is.data.frame(x))
5 stop("'plot.data.frame' applied to non data frame")
6 if (ncol(x) == 1) {
7 x1 <- x[[1L]]
8 cl <- class(x1)
9 if (cl %in% c("integer", "numeric"))
10 stripchart(x1, ...)
11 else plot(x1, ...)
12 }
13 else if (ncol(x) == 2) {
14 plot2(x, ...)
15 }
16 else {
17 pairs(data.matrix(x), ...)
19 }
19 }
</pre>
<p>A concise commentary:
<p>The function takes a mandatory argument, a data frame by assumption, and an arbitrary number
of optional arguments passed by possibly inner function calls to other graphic functions [line 1].
<p>It defines an inner function, <code>plot2</code>, that in turn calls plot with the first and second column of the given data frame and sets appropriate titles and labels for the resulting plot. This function will be used later for one of the possible cases mentioned in the documentation, where the data frame has two columns [line 3].
<p>The function exits with an error message if the argument passed is not a data frame [line 4-5]. This is just the usual defensive line to handle arguments that don't follow the assumed type of input.
<p>Next the main part [lines 6ff.] goes, that conditionally selects a kind of graph depending on the number and, if required, the class of columns in the data frame.
<p>If the data frame has one column and it is "numeric" or "integer" a <em>stripchart</em> is displayed; otherwise (the column is of another class) it relies on the generic plot again to generate the appropriate plot. [lines 6-11].
<p>If the data frame has two columns the previously defined <code>plot2</code> function is called, so that a suitable <em>y</em> vs. <em>x</em> plot is obtained [lines 13-14].
<p>Finally, if the number of columns is greater than 2, the another possible case, the data frame is coerced
to a <code>data.matrix</code> and the function <code>pairs</code> is applied to the result of the coercion [lines 16-17].
<p>To get a grasp on this last thing, here is my challenge, read now <code>?data.matrix</code> and <code>?pairs</code>.
<code>Have fun and happy R reading!</code>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-75300288144783886522015-01-29T12:54:00.000+01:002015-01-29T17:22:05.848+01:00Pandoc: Plantillas personalizadas de LaTeX para generación de documentos pdf[Esta entrada es una traducción libre al castellano de la <a href="http://los-pajaros-de-hogano.blogspot.com.es/2015/01/pandoc-customized-latex-templates-for.html">versión original escrita en inglés</a> en este mismo blog.]
<p>Una de las características más interesantes, aunque pocas veces comentada, de Pandoc es la de sus plantillas para LaTeX. Las plantillas de Pandoc nos permiten seguir escribiendo en Markdown, en lugar de volver de nuevo a LaTeX puro, incluso en los casos en que tengamos que producir documentos que no se considerarían "normales". En tales situaciones es posible también aprovecharse de la simplicidad de Markdown y relegar todos los detalles engorrosos a nuestras plantillas, siempre y cuando nuestras plantillas se atengan a la sintaxis y semántica de Pandoc.
<p>Como ejemplo ilustrativo, supongamos que debemos entregar con regularidad actas del día de reuniones en pdf, siguiendo un diseño formal, especializado y con mucho texto repetido de un acta a otra como el que se muestra en la siguiente imagen:
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-fZqaqQfuROC2JrPEQ4GbDJ0e1EsVTWZjlZpGzGC9jqYX3-7E5Ntl5IytoCYfF5fhOJXdhiJcKxiB-j6RDDNf53nEIx_vujY5zC8maSRbilQBznloUyyt-92n9xTsZGIPsDYq_RciG-Q/s1600/mi_documento.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-fZqaqQfuROC2JrPEQ4GbDJ0e1EsVTWZjlZpGzGC9jqYX3-7E5Ntl5IytoCYfF5fhOJXdhiJcKxiB-j6RDDNf53nEIx_vujY5zC8maSRbilQBznloUyyt-92n9xTsZGIPsDYq_RciG-Q/s640/mi_documento.png" /></a>
<p>Pandoc no parece ser la mejor herramienta para esta tarea. Trataré de demostrar que, por el contrario, Pandoc es especialmente apropiado para ella.
<h3>Plantillas de Pandoc</h3>
Como todo usuario de Pandoc sabe, la conversión de Markdown a LaTeX en Pandoc se basa en una plantilla por defecto que viene incluida en la instalación de Pandoc. Si se ejecuta esta orden:
<pre>pandoc --standalone --output mi_documento.pdf mi_documento.md</pre>
<p>O, con las formas abreviadas de las opciones:
<pre>pandoc -s -o mi_documento.pdf mi_documento.md</pre>
<p>se está aplicando un plantilla LaTeX por defecto, llamada <code>default.latex</code>, que en mi sistema y versión actual de Pandoc (1.12.2.1) se encuentra en <code>$PANDOC_DIR/data/templates/default.latex</code>. El resultado de aplicar esa plantilla al documento Markdown es un fichero que la máquina LaTeX (actualmente, pdflatex es la máquina por defecto) procesa de forma transparente para producir el pdf final.
<p>Un rápido vistazo a la plantilla por defecto muestra que dicha plantilla no es más que un fichero LaTeX normal y corriente en el que, además, se incluyen, ciertos determinados constructos (variables, condicionales y bucles).
<p>Si queremos usar otra plantilla, en lugar de la plantilla por defecto, ya sea una versión personalizada de dicha plantilla, ya una plantilla que creemos nosotros mismos completamente desde cero, necesitaremos pasar el nombre del fichero que la contiene a la opción <code>--template</code>. Suponiendo que el fichero de entrada y nuestra plantilla están en el mismo directorio, la orden correspondiente sería algo parecido a esto:
<pre>pandoc -s --template="mi_plantilla.latex" -o mi_documento.pdf mi_documento.md</pre>
<h3>Variables en Pandoc</h3>
Una variable en Pandoc tiene la siguiente sintaxis:
<pre>$nombre-de-variable$</pre>
<p>Cuando ejecutamos pandoc cada variable de la plantilla se substituye por su valor. Este valor se puede pasar a Pandoc de distintas formas. Una de ellas, como veremos más adelante, es pasárselo a través de la opción de línea de órdenes <code>-M nombre-de-variable=valor</code>(donde <code>-M</code> es la forma abreviada de <code>--metadata</code>).
<p>Por lo respecta a las variables pre-definidas por Pandoc y que están incluidas en la plantilla por defecto, más información sobre la mayoría de ellas se encuentra en la documentación oficial (http://johnmacfarlane.net/pandoc/README.html#templates).
<p>Para saber en concreto qué variables hay en la plantilla por defecto podemos también utilizar un filtro Unix como el siguiente:
<pre>grep -o '\$.*\$' /usr/share/pandoc/data/templates/default.latex \
| grep -v '\$endif\$\|\$else\$\|\$endfor\$'</pre>
<p>Es especialmente importante destacar que hay una variable crítica pre-definida, la variable <code>$body$</code>, que toda plantilla debería incluir, puesto que el contenido propiamente tal de nuestro documento de entrada será introducido en su lugar.
<p>Comprobemos esto último. Creemos una platilla LaTeX <code>simple.latex</code> con este contenido:
<pre>
\documentclass{minimal}
\begin{document}
$body$
\end{document}
</pre>
<p>y ejecutemos pandoc para que reciba el input de la entrada estándar desde terminal. El resultado de nuestra sesión de prueba es el siguiente:
<pre>
$ pandoc -s --template="simple.latex" --to latex
Hola
Ctrl+D
\documentclass{minimal}
\begin{document}
Hola
\end{document}
</pre>
<p>La primera línea es el comando ejecutado. Estoy pidiendo a Pandoc que aplique nuestra plantilla, <code>simple.latex</code> a la entrada que le vamos a pasar y que la convierta a formato LaTeX. Las dos siguientes líneas reproducen lo que he tecleado para que Pandoc lo consuma. <code>Ctrl+D</code> señala <code>EOF</code> (fin de fichero) y cierra la entrada estándar. El resto es la salida que Pandoc produce. Nótese que lo que he tecleado, "Hola", aparece tras el procesamiento, como esperábamos, en el lugar en que estaba <code>$body$</code> en la plantilla.
<p>Intentemos algo un poco más complicado. Añadamos una variable de nuestra cosecha, que llamaremos <code>$saludo$</code>, a la plantilla:
<pre>
\documentclass{minimal}
\begin{document}
$saludo$
$body$
\end{document}
</pre>
y comprobemos qué pasa:
<pre>
$ pandoc -s -M saludo="Hola gente" --template="simple.latex" --to latex
Esto es Pandoc
Ctrl+D
\documentclass{minimal}
\begin{document}
Hola gente
Esto es Pandoc
\end{document}
</pre>
<p>La orden es casi idéntica a la de antes. El añadido clave es la opción <code>-M</code> comentada previamente. A diferencia de la variable predefinida <code>$body$</code>, tenemos ahora que pasar los valores de nuestras propias variables a pandoc a través de la opción <code>-M</code>. Como vemos, en la salida, la variable en la plantilla es sustituida por el que valor que hemos dado.
<h3>Condicionales</h3>
Los condicionales tienen esta sintaxis:
<pre>
$if(variable)$
X
$else$
Y
$endif$
</pre>
<p>donde la cláusula <code>$else$</code> es opcional.
<p>Supongamos que nos interesa poder elegir, cuando sea necesario, entre diferentes clases de documentos para el mismo documento de entrada. En concreto, supongamos que queremos crear un documento <em>minimal</em> por defecto, a no ser que pidamos expresamente que el documento sea de otra determinada clase. Podemos conseguirlo a través de este condicional:
<pre>
$if(mi-clase-doc)$
$mi-clase-doc$
$else$
minimal
$endif$
</pre>
<p>Hay que tener cuidado con la sintaxis. Cada cláusula (<code>if()</code>, <code>else</code>, <code>endif</code>) va rodeada por el signo <code>$</code>. Las variables que serán remplazadas por sus valores también van entre <code>$</code>. Los valores literales, así como las referencias a la variable en la cláusula <code>if</code> van sin ese signo.
<p>Naturalmente, nuestro condicional debe colocarse en el lugar adecuado en la plantilla, a saber, la instrucción <code>\documentclass</code>:
<pre>
\documentclass{$if(mi-clase-doc)$
$mi-clase-doc$
$else$
minimal
$endif$}
</pre>
Escribir lo anterior en una sola línea quizá sea menos legible, pero también más característico del estilo LaTeX. Pongámosla, pues, así:
<pre>
\documentclass{$if(mi-clase-doc)$$mi-clase-doc$$else$minimal$endif$}
\begin{document}
$saludo$
$body$
\end{document}
</pre>
<p>Toca poner a prueba la plantilla:
<pre>
$ pandoc -s -M saludo="Hola gente" --template="simple.latex" --to latex
Esto debería ser minimal
Ctrl+D
\documentclass{minimal}
\begin{document}
Hola gente
Esto debería ser minimal
\end{document}
</pre>
<p>¡Funciona!
<p>Probemos ahora la otra posibilidad:
<pre>
$ pandoc -s -M saludo="Hola gente" -M mi-clase-doc="book" --template="simple.latex" --to latex
Y esto, book
Ctrl+D
\documentclass{book}
\begin{document}
Hola gente
Y esto, book
\end{document}
</pre>
<p>¡También funciona! Nótese que en esta ocasión hemos establecido el valor de la variable <code>mi-clase-doc</code> a "book" a través de la opción <code>-M</code>, tal como hicimos anteriormente con <code>$saludo$</code>.
<h3>Bucles</h3>
Los bucles funcionan de una manera semejante. La sintaxis básica de un bucle es la siguiente:
<pre>
$for(variable)$
X
$sep$separador
$endfor$
</pre>
<p>La línea <code>$sep$separador</code> es opcional. Sirve para definir un separador entre elementos consecutivos.
<p>Digamos que queremos anotar los participantes a una reunión en la primera línea de nuestro documento. Podemos definir una variable <code>$partipante$</code> en nuestra plantilla y dejar que Pandoc rellene su contenido. Queremos además que los nombres de los participantes aparezcan separados por una coma. Podríamos hacer todo esto añadiendo lo siguiente a nuestra plantilla:
<pre>
$for(participante)$
$participante$
$sep$,
$endfor$
</pre>
<p>O en una sola línea y en el lugar de la plantilla que corresponde:
<pre>
\documentclass{$if(mi-clase-doc$$mi-clase-doc$$else$minimal$endif$}
\begin{document}
Participantes: $for(participante)$$participante$$sep$, $endfor$
$saludo$
$body$
\end{document}
</pre>
<p>Hagamos de nuevo una prueba. Ahora añadiremos a la orden pandoc tantos <code>-M participante=...</code> como participantes queremos incluir.
<pre>
$ pandoc -s -M saludo="Hola gente" -M participante="W. Shakespeare" -M participante="Edgar A. Poe" --template="simple.latex" --to latex
Menudo plantel
Ctrl+D
\documentclass{minimal}
\begin{document}
Participantes: W. Shakespeare, Edgar A. Poe
Hola gente
Menudo plantel
\end{document}
</pre>
<p>¡Estupendo! Todo funciona perfecto.</p>
<h3>Bloques de meta-datos</h3>
Es, como poco, engorroso tener que pasar todas estas cosas a la línea de órdenes. No es obligatorio, por supuesto. Pandoc proporciona para esta tarea los así llamados <em>bloques de meta-datos</em>. Un bloque de meta-datos para nuestro experimento anterior tendría este aspecto:
<pre>
---
mi-clase-doc: minimal
saludo: Hola gente
participante:
- William Shakespeare
- Edgar A. Poe
---
</pre>
<p>Este bloque no es más que un fragmento de texto que sigue las especificación YAML (http://yaml.org/spec). Aparte de esto, los bloques YAML que se incluyen en un documento para que Pandoc lo procese deben empezar con una línea de tres guiones y terminar con una línea de tres puntos o tres guiones como se ve en el ejemplo.
<p>Un bloque de meta-datos consta de campos. Cada campo tiene un nombre y un valor asociado a ese nombre, separado del nombre por dos puntos. Algunos campos pueden contener varios valores, los cuales van precedidos por un guion, tal y como aparecen para el campo 'participante' en el ejemplo.
<p>Estos bloques nos permiten pasar a pandoc toda la información requerida sin tener que complicarnos la vida con las opciones en la línea de órdenes. La forma habitual de usar estos bloques es añadirlos al principio de nuestro documento de entrada. Otra forma, en mi opinión mejor, es crear un fichero <code>yaml</code> que pasamos a la vez que el fichero de entrada.
<p>Por ejemplo, si guardamos la entrada de nuestro último experimento (la cadena "Menudo plantel") en un fichero con nombre <code>mi_documento.md</code> y el bloque de meta-datos en un fichero con nombre <code>variables.yaml</code>, podemos llamar a pandoc como sigue para conseguir exactamente la misma salida que obtuvimos antes:
<pre>
pandoc -s --template="simple.latex" --to latex mi_documento.md variables.yaml
</pre>
<h3>El documento final de la reunión</h3>
Nuestra tarea inicial se vuelve realizable gracias a la flexibilidad de Pandoc, que acabamos de explicar. Es cuestión sencillamente de crear un documento LaTeX normal y corriente (nuestra plantilla) con unas pocas variables y un bucle. Todos los detalles complicados y el texto repetitivo se desplaza a la plantilla, mientras que el contenido relevante y propiamente tal puede escribirse como de costumbre y muy convenientemente en Markdown puro.
<p>Como referencia reproduzco todos los ficheros implicados en la creación del documento que mostré al principio de esta entrada. No voy a añadir nada más acerca del LaTeX que he usado, que es, dicho sea de paso, un poco chapucero ;-) En cualquier caso, asumo que los lectores conocen LaTeX lo suficiente.
<h4>mi_documento.md</h4>
<pre>
1. Lorem ipsum dolor sit amet,
Lorem ipsum dolor sit amet consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.
2. Ut enim ad minim veniam,
Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.
3. Duis aute irure dolor
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur.
4. Excepteur sint occaecat
Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum
</pre>
<h4>mi_plantilla.latex</h4>
<pre>
\documentclass[a4paper]{extreport}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[spanish]{babel}
\usepackage{marginnote}
\usepackage{background}
\setlength{\parindent}{0pt}
\SetBgScale{1}
\SetBgColor{black}
\SetBgAngle{0}
\SetBgHshift{-0.52\textwidth}
\SetBgVshift{-1mm}
\SetBgContents{\rule{0.4pt}{\textheight}}
\setlength{\marginparwidth}{32mm}
\renewcommand*{\raggedleftmarginnote}{}
\reversemarginpar
\begin{document}
\textbf{Informe}\marginnote{$for(asistente)$$asistente$$sep$\\ $endfor$}
$body$
\section{}
Freedonia, $dia$ de $mes$ de $ano$
\section{}
Jefe del Departamento
\vspace{2cm}
\emph{Firma: átopos}
\end{document}
</pre>
<h4>variables.yaml</h4>
<pre>
---
dia: 25
mes: Enero
ano: 2015
asistente:
- William Shakespeare
- Edgar A. Poe
- Miguel de Cervantes
---
</pre>
<h4>La orden de Pandoc que genera el pdf</h4>
<pre>
pandoc -s --template="mi_plantilla.latex" -o mi_documento.pdf mi_documento.md variables.yaml
</pre>
átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com2tag:blogger.com,1999:blog-7872588298095020991.post-4784018648192319632015-01-25T23:22:00.000+01:002015-01-28T15:36:26.292+01:00Pandoc: Customized LaTeX templates for PDF generationOne of the most interesting, however rarely commented, features of Pandoc is its support for LaTeX templates. Pandoc templates allows us to keep writing mostly in Markdown, instead of falling back to LaTeX, even if we have to produce documents that wouldn't be considered as "normal". In those cases we can still take advantage of Markdown simplicity by delegating all the gory-details to our own templates as long as they stick to the Pandoc syntax and semantics.
<p>As an illustration, let us suppose we have to regularly deliver very formalized (with a lot of boilerplate text) but at the same time really specialized (as for design) pdfs of meeting minutes such as the following:
<p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbGYk6cLDDuwAit7NO-7a8Bq6sJinnZZxU383P08I2fFVYRXcttGYvA3PwQiivCZ2KYnkTUItlmCeTSDziZmSgrImcS2siVIKhV7g-0Kk6tNiGsqCho0KtG0x1t1cF6y3NKAC3wdu57FM/s1600/my_document.png" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbGYk6cLDDuwAit7NO-7a8Bq6sJinnZZxU383P08I2fFVYRXcttGYvA3PwQiivCZ2KYnkTUItlmCeTSDziZmSgrImcS2siVIKhV7g-0Kk6tNiGsqCho0KtG0x1t1cF6y3NKAC3wdu57FM/s640/my_document.png" /></a>
<p>It would seem that Pandoc is not the best option for this task. I'll try to prove that Pandoc might be, on the contrary, particularly suitable.
<h3>Pandoc templates</h3>
As every Pandoc user knows converting to LaTeX from markdown relies on a default template included in the Pandoc installation. Issuing this command
<pre>pandoc --standalone --output my_document.pdf my_document.md</pre>
<p>Or, using short options, for brevity
<pre>pandoc -s -o my_document.pdf my_document.md</pre>
<p>applies a default LaTeX template named <code>default.latex</code> that, on my system and current version (1.12.2.1), can be found at <code>$PANDOC_DIR/data/templates/default.latex</code>. The result of applying the template to the markdown document is a file processed transparently by a LaTeX engine (currently, pdflatex by default) that produces the final pdf output.
<p>A quick inspection into the default template reveals that a Pandoc template is just a normal LaTeX file in which certain specific constructs (variables, conditionals and loops) are included.
<p>To use another template, either a customized version of the default template, or our own template written from scratch, we need to pass its filename to the option <code>--template</code>. Assuming that the template file is in the same directory as the input file the pandoc command would be as follows:
<pre>pandoc -s --template="my_template.latex" -o my_document.pdf my_document.md</pre>
<h3>Pandoc Variables</h3>
A variable in Pandoc has the following syntax.
<pre>$variable-name$</pre>
<p>Every variable in the template will be replaced with its actual value. This value can be passed in different ways. One of them is to pass it via the <code>-M variable-name=value</code> (<code>--metadata</code> is the long name switch) Pandoc command line option, as we will see later.
<p>As for pre-defined variables contained in the default template, you can see more information about most of them in the Pandoc documentation (http://johnmacfarlane.net/pandoc/README.html#templates).
<p>Another useful way to know what variables are actually present in the default template is to extract them with a Unix filter:
<pre>
grep -o '\$.*\$' /usr/share/pandoc/data/templates/default.latex \
| grep -v '\$endif\$\|\$else\$\|\$endfor\$'
</pre>
<p>A very important thing to point out is that there is a critical variable, <code>$body$</code> that should be present in every template. This variable is implicitly replaced with the actual content of our input.
<p>Let's check the latter by creating a minimal LaTeX template <code>simple.latex</code>
<pre>
\documentclass{minimal}
\begin{document}
$body$
\end{document}
</pre>
<p>and issuing pandoc to take our input from the terminal [Below is a reproduction of the test]
<pre>
$ pandoc -s --template="simple.latex" --to latex
Hi
Ctrl+D
\documentclass{minimal}
\begin{document}
Hi
\end{document}
</pre>
<p>The first line is the command issued. I'm telling Pandoc to apply our own template over whatever input is going to be passed, and convert it to LaTeX. The following lines are what I've actually typed in to be consumed by pandoc, hitting Ctrl+D (in Linux and other Unixes) signals EOF and closes the standard input. The rest is the output produced by pandoc. Note that what I've typed in, "Hi", is now in place of the <code>$body$</code> variable, as expected.
<p>Let's try something a bit more complicated, adding our own variable, say <code>$greeting$</code>, to our template:
<pre>
\documentclass{minimal}
\begin{document}
$greeting$
$body$
\end{document}
</pre>
<p>and test it as before
<pre>
$ pandoc -s -M greeting="Hi, World" --template="simple.latex" --to latex
This is Pandoc!
Ctrl+D
\documentclass{minimal}
\begin{document}
Hi, World
This is Pandoc!
\end{document}
</pre>
<p>The command is almost the same. The critical add-on is the <code>-M</code> option referred above. Unlike the variable <code>$body$</code>, the values for our variables need to be passed to pandoc explicitly via the <code>-M</code> option. Again, the variable in the template is replaced with the given value to produce the output.
<h3>Conditionals</h3>
Conditionals have this syntax:
<pre>
$if(variable)$
X
$else$
Y
$endif$
</pre>
<p>where the <code>$else$</code> part is optional.
<p>Let's suppose that we want to be able to choose between different document classes for the same document as needed. In particular, we want to create a minimal document by default, or another document class on demand. We can do this by creating this conditional:
<pre>
$if(my-doc-class)$
$my-doc-class$
$else$
minimal
$endif$
</pre>
<p>Be careful about the syntax. Each syntactic part (<code>if()</code>, <code>else</code>, <code>endif</code>) is enclosed in dollar signs. Variables to be replaced by their values are also surrounded by that sign. Literal values, as well as the variable reference in the <code>if</code> section go without them, though.
Of course, we have to put this conditional in the suitable place, the LaTeX <code>\documentclass</code> macro:
<pre>
\documentclass{$if(my-doc-class)$
$my-doc-class$
$else$
minimal
$endif$}
</pre>
<p>One-liners maybe less readable but more LaTeX-style aware. So put the corresponding one-liner into the template:
<pre>
\documentclass{$if(my-doc-class)$$my-doc-class$$else$minimal$endif$}
\begin{document}
$greeting$
$body$
\end{document}
</pre>
Now, Let's check:
<pre>
$ pandoc -s -M greeting="Hi, World" --template="simple.latex" --to latex
This should be a minimal
Ctrl+D
\documentclass{minimal}
\begin{document}
Hi, World
This should be a minimal
\end{document}
</pre>
<p>It works!
<p>Let's try the second use case:
<pre>
$ pandoc -s -M greeting="Hi, World" -M my-doc-class="book" --template="simple.latex" --to latex
And this one, a book
Ctrl+D
\documentclass{book}
\begin{document}
Hi, World
And this one, a book
\end{document}
</pre>
<p>It also works! Note that this time the previously defined variable <code>my-doc-class</code> is set to the value "book" by means of the option <code>-M</code> as we did before with <code>$greeting$</code>
<h3>Loops</h3>
Loops behave in a similar manner. The basic syntax is as follows:
<pre>
$for(variable)$
X
$sep$separator
$endfor$
</pre>
<p>The <code>$sep$separator</code> line (for defining a separator between consecutive items) is optional.
<p>Let's say we want to take note of participants in a meeting as the very first line in our document. We can define a variable <code>participant</code> in our template and leave Pandoc to fill its content. We want to separate by commas the names of participants. So we can add this to the template:
<pre>
$for(participant)$
$participant$
$sep$,
$endfor$
</pre>
Or just a one-liner in the corresponding place:
<pre>
\documentclass{$if(my-doc-class)$$my-doc-class$$else$minimal$endif$}
\begin{document}
Participants: $for(participant)$$participant$$sep$, $endfor$
$greeting$
$body$
\end{document}
</pre>
<p>Testing again ... Now we add as many <code>-M participant=...</code> options as participants we wish to set.
<pre>
$ pandoc -s -M greeting="Hi, World" -M participant="W. Shakespeare" -M participant="Edgar A. Poe" --template="simple.latex" --to latex
Good staff!
Ctrl+D
\documentclass{minimal}
\begin{document}
Participants: W. Shakespeare, Edgar A. Poe
Hi, World
Good staff!
\end{document}
</pre>
Nice. Everything works fine!
<h3>Metadata blocks</h3>
It's cumbersome, to say the least, being forced to pass all those things to the command line. You aren't, of course. Pandoc provides the so-called <em>metadata blocks</em> for this task. A metadata block for our previous experiments would look like as follows:
<pre>
---
my-doc-class: minimal
greeting: Hi, World
participant:
- William Shakespeare
- Edgar A. Poe
---
</pre>
<p>This is just a chunk of text following the YAML specification (http://yaml.org/spec). YAML blocks included in a document to be consumed by pandoc must begin with three hyphens and end with three points or three hyphens.
<p>A metadata block consists of fields. Each field has a name and its associated value separated by semicolon. Some fields can contain multiple values preceded by a hyphen like the participant field in the previous example.
<p>These blocks allow us to pass to pandoc all the required information without bothering with the command line switches. The customary way is to add the block at the beginning of our input document. Another way, even better in my opinion, is to create a yaml file that we pass to pandoc along with our input file.
<p>For instance, if we save the input of our last experiment (the "Good staff!" string) in a file named <code>my_document.md</code> and the metadata block above in a file named <code>variables.yaml</code>, pandoc can be called to produce exactly the same output as the one we obtained before (try it!) as follows:
<pre>pandoc -s --template="simple.latex" --to latex my_document.md variables.yaml</pre>
<h3>The final meeting document</h3>
Our initial task becomes feasible due to the explained Pandoc flexibility. It's just a matter of creating a normal LaTeX document (our template) with a bit of variables and loops. All of the gory-details and boilerplate text will be shifted to the template while the actual and relevant content could be written, as usual and conveniently, in pure Markdown.
<p>For reference I reproduce all the files involved in the creation of the document shown at the beginning of this entry. No further comments about LaTeX here, that is a bit quick&dirty, BTW ;-) I'm assuming readers already know LaTeX, anyway.
<h4>my_document.md</h4>
<pre>
1. Lorem ipsum dolor sit amet,
Lorem ipsum dolor sit amet consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.
2. Ut enim ad minim veniam,
Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat.
3. Duis aute irure dolor
Duis aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur.
4. Excepteur sint occaecat
Excepteur sint occaecat cupidatat non proident, sunt in
culpa qui officia deserunt mollit anim id est laborum
</pre>
<h4>my_template.latex</h4>
<pre>
\documentclass[a4paper]{extreport}
\usepackage[T1]{fontenc}
\usepackage{marginnote}
\usepackage{background}
\setlength{\parindent}{0pt}
\SetBgScale{1}
\SetBgColor{black}
\SetBgAngle{0}
\SetBgHshift{-0.52\textwidth}
\SetBgVshift{-1mm}
\SetBgContents{\rule{0.4pt}{\textheight}}
\setlength{\marginparwidth}{32mm}
\renewcommand*{\raggedleftmarginnote}{}
\reversemarginpar
\begin{document}
\textbf{Report}\marginnote{$for(attendee)$$attendee$$sep$\\ $endfor$}
$body$
\section{}
Freedonia, $month$ $day$, $year$
\section{}
Chief of Departament
\vspace{2cm}
\emph{Signature: atopos}
\end{document}
</pre>
<h4>variables.yaml</h4>
<pre>
---
day: 25
month: January
year: 2015
attendee:
- William Shakespeare
- Edgar A. Poe
- Miguel de Cervantes
---
</pre>
<h4>The Pandoc command to generate the pdf</h4>
<pre>pandoc -s --template="my_template.latex" -o my_document.pdf my_document.md variables.yaml</pre>
<p><em>[Spanish translation on the way...]</em>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com5tag:blogger.com,1999:blog-7872588298095020991.post-738227415688894512015-01-11T20:21:00.000+01:002015-01-18T22:33:44.112+01:00Typesetting code with latex and minted. A real-world example.<p>More than 4 years without writing a word in this blog :(
<p>What if I blend the trend right now? What about something on (what else!) LaTeX?
<p>I was trying to reread some pages from the K&R's C book taking notes along the way for my own use, and I wondered what LaTeX has to offer nowadays beyond the <a href="http://ctan.org/pkg/listings">venerable <code>listings</code> package</a>.
<p>And I've found out <a href="http://ctan.org/pkg/minted"><code>minted</code></a>
<p><code>listings</code> and <code>minted</code> user interfaces are similar. So the transition from one to the other is most of the time seamless. Anyway, both interfaces are clean and easy to use. Though <code>listings</code> may be more flexible, <code>minted</code> has an advantage for lazy users like me: no need to create custom syntax highlighting styles since, thanks to the Python <a href="http://pygments.org/"><em>Pygments</em></a> library integration provided by <code>minted</code>, we can get state of the art and wonderful highlighting niceties automatically!
<p>The only requirement (of course) is to have Python installed as well as the <em>Pygments</em> library. That's probably the case if you are using a modern Linux (MacOSX?) distro. For Windows users you may need to consult <code>minted</code> package documentation (particularly, section 2.4).
<p>If <em>Pygments</em> is not already installed (I assume Python is installed, it will be on almost all modern Unixes):
<pre>pip install Pygments</pre>
<p>should do the job.
<p>This is what I get (just a draft):
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsAznZFDJ3PZjvIpieIusMEwtHPkrEeAjONozhcjPX-gzBDvBDNXZvBldnadLDoGrQlhdpCcYBcDSy6P06N0vlHn5MHaIEOpYofH_A9eB4WrdqoJ0H6T8OQiztAvUeyNW-hKIfdC2nSU0/s1600/strlen.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsAznZFDJ3PZjvIpieIusMEwtHPkrEeAjONozhcjPX-gzBDvBDNXZvBldnadLDoGrQlhdpCcYBcDSy6P06N0vlHn5MHaIEOpYofH_A9eB4WrdqoJ0H6T8OQiztAvUeyNW-hKIfdC2nSU0/s640/strlen.png" /></a></div>
<p>Nice, isn't it? The LaTeX I've used is as follows.
<p>The preamble is self-explanatory:
<pre>
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{minted} % The expected way to load minted macros
</pre>
The C code for this file comes from the same single file <code>strlen2.c</code>. It's natural to search for a way to input text from files directly instead of writing it over and over. The <code>\inputminted</code> command provides the desirable support:
<pre>
\begin{document}
...
\inputminted[linenos]{c}{strlen2.c}
</pre>
In general, the <code>\inputminted</code> macro expects an optional set of key-value pairs (some options will be explained later) and two mandatory arguments: the programming language in which the code is written, C in my case, and the name of the file containing the actual code.
<pre>
\inputminted[<options>]{<language>}{<filename>}
</pre>
Interestingly, there is no need to rewrite code fragments, a really boring and error-prone task. We can select lines in the file on demand. For <code>listings</code> users this is a piece of cake, both packages provide practically the same keywords for options related to inputting code fragments. For the text at hand:
<ul>
<li><code>firstline=<number></code></li>
<li><code>lastline=<number></code></li>
</ul>
These options are self-explanatory. They select lines of interest from the first one to the last one, both included.
To display line numbers we have the option:
<ul>
<li><code>linenos=true</code></li>
</ul>
Or, simpler, as every LaTeX user knows:
<ul>
<li><code>linenos</code></li>
</ul>
I want to print the actual C code line numbers as they appear in the source. By default the line-number counter is reset to 1 for any code fragment, though. Therefore, I also have to provide the actual initial line number for each chunk:
<ul>
<li><code>firstnumber=<number></code></li>
</ul>
Finally, a colored frame background might make things more readable and beautiful:
<ul>
<li><code>background=<color></code>
</ul>
I've defined a color for this purpose in the preamble:
<pre>\definecolor{bg}{rgb}{0.95,0.95,0.95}</pre>
and refer to it as the value for <code>background</code> to obtain the final macro. Thus, the LaTeX code for the third C code fragment above would look like this:
<pre>\inputminted[background=bg,linenos,firstnumber=6,firstline=6,lastline=7]{c}{strlen2.c}</pre>
Copy&pasting such a huge macro is awful. As usual, laziness can be our best friend. This is a suitable place for putting a familiar macro shorthand in the preamble:
<pre>
\newcommand{\Cfrag}[3]{%
\inputminted[%
bgcolor=bg,
linenos,
firstnumber=#1,
firstline=#1,
lastline=#2
]{c}{#3}}
</pre>
This way each chunk of code can be included with a more friendly macro. For instance:
<pre>
\Cfrag{6}{7}{strlen2.c}
</pre>
All in all, the final LaTeX file looks as follows:
<pre>
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{minted}
\definecolor{bg}{rgb}{0.95,0.95,0.95}
\newcommand{\Code}[1]{\texttt{#1}}
\renewcommand{\labelitemi}{\color{red!50!black}\guilsinglright}
\newcommand{\Cfrag}[3]{%
\inputminted[%
bgcolor=bg,
linenos,
firstnumber=#1,
firstline=#1,
lastline=#2
]{c}{#3}}
\begin{document}
\section{The \Code{strlen} function}
\inputminted[linenos]{c}{strlen2.c}
\section{Commentary}
\Cfrag{1}{2}{strlen2.c}
\begin{itemize}
...
\end{itemize}
\Cfrag{4}{4}{strlen2.c}
\begin{itemize}
...
\end{itemize}
\Cfrag{6}{7}{strlen2.c}
\begin{itemize}
...
\end{itemize}
\Cfrag{8}{8}{strlen2.c}
\begin{itemize}
...
\end{itemize}
\end{document}
</pre>
And a last one <strong>important thing</strong>. You need to pass the option <code>-shell-escape</code> to <code>pdflatex</code> in order to get a pdf from a latex file containing minted macros. Something like this:
<pre>pdflatex -shell-escape strlen_commentary.tex</pre>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0tag:blogger.com,1999:blog-7872588298095020991.post-41807562255415456792011-11-02T16:35:00.002+01:002011-11-02T16:36:09.945+01:00El chino de Kurosawa (poema)<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwlqXHz4c3QSmQAhjwhaKVektP1PMQgYvTmRkbHpIRilodbHvU9x9CsAsEYCcXK7adrrBWYTCJbnOWxMYrYRol1Om778tK-vlDMQSRzxh1lOfL5t6gYCBJBXTV_1nGLSNnMDf0noVNsw0/s1600/el-chino-de-kuroswa.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 366px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwlqXHz4c3QSmQAhjwhaKVektP1PMQgYvTmRkbHpIRilodbHvU9x9CsAsEYCcXK7adrrBWYTCJbnOWxMYrYRol1Om778tK-vlDMQSRzxh1lOfL5t6gYCBJBXTV_1nGLSNnMDf0noVNsw0/s400/el-chino-de-kuroswa.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5670422648822963794" /></a>átoposhttp://www.blogger.com/profile/15405131193146954258noreply@blogger.com0