Elm in Jupyter Book
Contents
Elm in Jupyter Book#
Dit Jupyter Book biedt verschillende manieren om Elm-opdrachten en Elm-programma’s in code-cellen uit te voeren.
In de eerste plaats is er verschil tussen de gecompileerde versie van Elm en de geïnterpreteerde versie (Elm-REPL). Een tweede verschil is dat je Elm code-cellen kunt uitvoeren als “Live Code” of als Jupyter Notebook. Dit geeft in principe 4 mogelijkheden. (Echter, de gecompileerde versie werkt nog niet als “Live Code”.)
Over kernels
De code voor de Elm code-cellen wordt niet in de browser uitgevoerd, maar op een server “elders”. Op die server draait een Elm-REPL of een Elm (compilatie) kernel die de opdrachten uit de code-cellen verwerkt.
Elm REPL#
REPL staat voor: Read-Eval-Print-Loop. Elm-REPL is een interpreter die Elm-opdrachten regel voor regel uitvoert. Deze uitvoering verandert de toestand (namen en waarden) voor de regels (code-cellen) die je hierna uitvoert.
De Elm interpreter (Elm REPL) verwerkt de invoer (het programma) steeds direct, stap voor stap, in de context van de eerder uitgevoerde stappen (“incrementeel”). Dit model past goed bij het Jupyter notebook model, waarbij code-cellen stuk voor stuk direct uitgevoerd worden - bijvoorbeeld met de Python REPL interpreter. Dit model is handig als je de beginselen van Elm als functionele taal wilt leren, omdat je per stap direct feedback krijgt.
Waarschuwing
De Elm REPL code kun je niet uitvoeren in https://elm-lang.org/try. Daar wordt steeds een compleet Elm programma verwacht voor compilatie en uitvoering in de browser-context. Het programma moet resulteren in HTML-code, je hebt geen print opdracht o.i.d.
Waarschuwing
De volgorde waarin de cellen uitgevoerd worden is van belang. De aanname is dat de cellen in de tekst-volgorde uitgevoerd worden. Als je dat niet doet, kun je vreemde resultaten krijgen, bijvoorbeeld als een “import” of een definitie overgeslagen is.
Herstarten van de kernel. De Elm REPL kernel kan door je experimenten soms in een vreemde toestand raken, die niet meer klopt met de aannames van het Jupyter (Note)book. In dat geval kun je de kernel opnieuw opstarten, eventueel met “een schone lei”: restart and clear outputs. Je hoeft bij je experimenten dus niet bang te zijn dat je iets kapot maakt, of in paniek te raken als je vreemde resultaten krijgt. Een restart van de kernel lost veel problemen voor je op.
Live Code in Jupyter Book#
Je kunt de code-cellen in een Jupyter Book pagina “live” maken via de button “Live Code” onder het raket-icoontje bovenin. Als je dit opgestart hebt moet je even wachten totdat de “launch ready” is (in groene letters). Dit kan soms even duren, vooral als er net een nieuwe versie van het Jupyter Book is.
Een “live” cel heeft een “run” button, en “restart” en “restart & run all” buttons. Met de “run” button voer je de code in de cel boven de run-button uit. Je kunt deze code aanpassen en de cel opnieuw uitvoeren. Jupyter Book is bedoeld voor dit soort experimenten die je kunnen helpen om de programma-code te begrijpen. Soms staat er ook opdrachten waarin je gevraagd wordt om de code op een bepaalde manier aan te passen.
Aan de slag
Maak deze pagina “live” door op de Live Code button te klikken. Je kunt dan de opdrachten hieronder uitvoeren.
Elm REPL voorbeelden#
Elm-REPL maakt het eenvoudig om te experimenteren met kleine stukjes code, die je cel voor cel uitvoert.
voer de cellen in de gegeven volgorde uit;
geef als invoer voor REPL per cel een ELM-opdracht die ‘’op 1 regel past’’;
of de definitie van een (1) functie (of van een type) - eventueel op meerdere regels;
gebruik verschillende namen voor definities in verschillende cellen; vermijd her-definitie van een naam in verschillende cellen
Als de opdracht niet op 1 regel past, kan de Elm-REPL kernel in de war raken, met vreemde foutmeldingen als resultaat. Je kunt dan ook bij de volgende opdrachten problemen krijgen. Remedie: “restart kernel”.
Als een cel meer dan één definitie bevat raakt de Elm-REPL in de war, meestal onherstelbaar (restart kernel).
Elm is gevoelig voor indentatie (inspringen): het aantal spaties aan het begin van een regel heeft betekenis in Elm. Als je hierin een fout maakt, betekent je Elm programma mogelijk iets anders dan je denkt.
Nog een tip:
als namen als
length,mapoffoldrniet gevonden worden, heb je mogelijk niet de juiste libraries geïmporteerd.
We geven hieronder enkele voorbeelden.
Opdracht. Probeer deze voorbeelden uit als “Live Code”. Experimenteer met de Elm-uitdrukking en bekijk het resultaat.
Elm als rekenmachine.
3 + 4 * 5
Elm functie-definitie.
twice x =
x + x
Voorbeeld van een bijbehorende aanroep (zonder haakjes om het argument!):
twice 10
Elm heeft ‘sterke typering’.
10 + "ten"
Opdracht. Verander de + in ++ en probeer het nog eens. Kun je deze uitdrukking aanpassen zodat je geen foutmelding krijgt? Als je kernel in de war lijkt, herstart deze dan.
Jupyter Notebook interface#
Je kunt deze pagina ook opstarten als Jupyter Notebook. Gebruik hiervoor de knop “Binder” onder het raketje bovenin. Je moet even wachten totdat de (privé!) virtuele machine (server) voor de kernel(s) opgestart is. Dan krijg je deze pagina in het Jupyter Notebook interface.
Voor de uitvoering van de Elm-code is het belangrijkste verschil dat er geen “run” knop per cel is: deze knop vind je bovenin, met in het kernel-menu de mogelijkheid om de kernel opnieuw te starten. Maar je kunt een cel ook uitvoeren door (i) deze te selecteren; en (ii) Shift-Enter te toetsen.
Voor een uitgebreidere uitleg van het Jupyter Notebook interface, zie:
Aan de slag (2)
Open deze pagina als Jupyter Notebook, via de “Binder” knop onder het raketje. Je kunt dan de opdrachten hierboven opnieuw uitvoeren.
Elm (compilatie) kernel#
Naast de Elm-REPL kernel is er de Elm kernel die de code eerst compileert (naar JavaScript) en dan uitvoert. Voor deze kernel moet je de cellen die samen het programma vormen afsluiten met de regel:
-- compile-code
Deze manier van werken komt het meest overeen met het gebruik van Elm in de browser, als vervanger van JavaScript: dat is waar Elm voor ontworpen is.
Om vlot te werken is het aan te raden om je hele programma in één cel te plaatsen. Dan hoef je niet steeds meerdere cellen uit te voeren als je het programma verandert en opnieuw wilt proberen.
De Elm (compilatie) kernel werkt momenteel alleen in Jupyter Notebook, niet in “Live Code”.
Opdracht.
Start deze pagina (als je dat nog niet gedaan had) als Jupyter Notebook via de “Binder” button onder het raketje.
Verander de kernel van “Elm REPL” naar “Elm”: rechts boven de tekst op deze pagina zie je een cirkel met links daarvan “Elm REPL”. Klik op de tekst “Elm REPL”: je krijgt dan een pop-up waarin je een andere kernel kunt kiezen. Kies voor “Elm”.
Voer de onderstaande code-cel uit (met Shift-Enter, of via het Kernel-menu bovenin).
Een eerste voorbeeld van een programma met eenvoudige tekst-uitvoer (in de HTML-context):
import Html exposing (text)
main =
text (String.fromInt 42)
-- compile-code
Voer de onderstaande code-cel uit.
Als het programma loopt, met een fraaie tekening, kun je daaronder links een (klein) getal invoeren waarmee je een andere variant krijgt. Probeer 1, 2, 3, 4, 5, 6
import Html exposing (text, div, input, Attribute)
import Browser exposing (sandbox)
import Html.Events exposing (on, keyCode, onInput)
import Html.Attributes exposing(..)
import Json.Decode as Json
import String exposing(split)
import List exposing(..)
import Maybe exposing(..)
import Svg exposing (..)
import Svg.Attributes exposing (..)
type alias Point =
{ x : Float
, y : Float
}
koch : Point -> Point -> Int -> List Point -> List Point
koch a b limit points =
let
( dx, dy ) =
( b.x - a.x, b.y - a.y )
dist =
dx * dx + dy * dy |> sqrt
unit =
dist / 3
angle =
atan2 dy dx
p1 =
Point (a.x + dx / 3) (a.y + dy / 3)
p2 =
Point
(p1.x
+ (cos (angle - pi / 3))
* unit
)
(p1.y
+ (sin (angle - pi / 3))
* unit
)
p3 =
Point (b.x - dx / 3) (b.y - dy / 3)
in
if limit > 1 then
let
l =
limit - 1
in
List.concat
[ points
, koch a p1 l points
, koch p1 p2 l points
, koch p2 p3 l points
, koch p3 b l points
]
else
a :: p1 :: p2 :: p3 :: b :: points
startP1 : Point
startP1 =
Point 0 -150
startP2 : Point
startP2 =
Point 150 100
startP3 : Point
startP3 =
Point -150 100
pointsListToString: List Point -> String
pointsListToString l =
if List.isEmpty l then
""
else
let
h = withDefault (Point 0 0) (head (take 1 l))
in
(String.fromFloat h.x) ++ "," ++ (String.fromFloat h.y) ++ " " ++ (pointsListToString (drop 1 l))
main = Browser.sandbox { init = init, update = update, view = view }
type alias Model = { content : String }
init : Model
init = { content = "1" }
view model =
let
path =
pointsListToString (koch startP1 startP2 (Maybe.withDefault 1 (String.toInt model.content)) []) ++ pointsListToString (koch startP2 startP3 (Maybe.withDefault 1 (String.toInt model.content)) []) ++ pointsListToString (koch startP3 startP1 (Maybe.withDefault 1 (String.toInt model.content)) [])
in
div []
[ input [ placeholder "numbers separated by ,", value model.content, onInput Change ] []
, svg [ viewBox "0 0 200 200", Svg.Attributes.width "400px" ]
[ g [ transform "translate(100, 100) scale(0.5,-0.5)" ]
[
polyline [ fill "none", stroke "black", points path] []
]
]
]
type Msg
= Change String
update : Msg -> Model -> Model
update msg model =
case msg of
Change newContent ->
{ model | content = newContent }
-- compile-code