1/30: Show input value in p tag
Code+demo: https://ellie-app.com/bS7FDnKVN3ha1
My elm experience before today
I've gone through the Elm intro course on Frontend Masters, but I really don't learn much from following along.
These 30 days, I will instead come up with ideas that are suitable for my current level. That's how I learned JavaScript, so I think it's a good approach.
Disclaimer
I really don't know what I'm doing. Copy my code at your own risk.
Goal
My goal today is to have a range slider's value be shown in a p tag.
Here's the code I'll try to recreate, written in Svelte:
<script>
let n = 0;
</script>
<input type="range" bind:value={n}>
<p>{n}</p>
Obviously, the Elm equivalent isn't going to be nearly as simple as this, but I'm up for a good challenge.
I'm probably just going to add type annotations to my Elm code. People say they make the code more readable, and it lets the compiler provide better error messages.
Thoughts about cool/confusing aspects of Elm
This is a step-by-step walkthrough of today's code.
Many parts of the code are taken from this example: https://guide.elm-lang.org/architecture/text_fields.html
1. Import statements
module Main exposing (main)
import Browser
import Html exposing (Html, div, input, p, text)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
if you've written HTML before, these words should be familiar. The first line was added automatically by VS Code. (..)
means everything.
2. Main function / Browser.sandbox
main : Program () Model Msg
main =
Browser.sandbox { init = initialState, update = update, view = view }
The first line was suggested to me by VS Code, so why not. Is this a function that returns a "program parenthesis model message"? ¯\(ツ)/¯
What's being passed in here seems like the basic Elm structure that I've heard of before: Model (state), update function and view function.
Browser.sandbox
seems interesting. What other things can be added to the main function?
Do people always write update = update
and view = view
, or is there a more elegant way? In modern JavaScript for example, we can do this:
// These produce the same result
const oldWay = { update: update, view: view }
const newWay = { update, view }
The Browser.sandbox documentation isn't very clear to me. Is it just a construct to get something up and running for complete beginners?
3 Model
type alias Model =
{ value : String
, message : String
}
initialState : Model
initialState =
{ value = "0"
, message = "Hi!"
}
For such a simple app, this feels unnecessary. I can do that in two lines of JavaScript. At the same time, it does look pretty neat.
From what I understood, the input event listener in Elm only returns event.target.value
as a string.
I did try to convert that string to an int, but the String.toInt
function returns a Maybe Int
and not an Int
.
I guess this would be handled with a case statement, but I'll leave that for another day.
4. Update function
update : Msg -> Model -> Model
update msg model =
case msg of
Change newContent ->
{ model | value = newContent }
How is this function invoked? Is that taken care of by Browser.sandbox
? Is the return value from onInput
connected to it?
The phrasing "case msg of" sounds weird, not like something people would say out loud. Am I correct in thinking of it as "check the variable msg for these cases" instead?
Change newContent
: I guess Change
is a message or symbol, and newContent
is the return value from the event listener. Is that right?
5. View function
view : Model -> Html Msg
view model =
div []
[ input [ type_ "range", onInput Change ] []
, p [] [ text model.value ]
, p [] [ text model.message ]
]
I don't know exactly what an Html Msg is yet, but it's probably just HTML.
When I first saw this kind of syntax, I didn't like it at all. Now I think it's alright.
From what I understand, these HTML elements are functions that take two arguments: attributes and a list of child elements.
Having the commas at the beginning of the line is very nice.
type
is probably a reserved keyword in Elm, so I guess that's why it's called type_
instead.
Summary
Today, I've taken my first steps in Elm! For such a simple project, I would much rather recommend Svelte, but I'm sure Elm is very useful in more complex projects.
Even before I started this challenge today, I missed Elm:
Earlier today, I was working on an Express/React application for a technical interview. As usual, the React compiler said everything was okay, and a few clicks later - a huge red Chrome screen because of a runtime error.
I don't know what tomorrow's Elm project will be, but it won't have any runtime errors.
Please let me know if you spot any errors, misunderstandings or ugly code! :)
Here's today's full code:
module Main exposing (main)
import Browser
import Html exposing (Html, div, input, p, text)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
main : Program () Model Msg
main =
Browser.sandbox { init = initialState, update = update, view = view }
type alias Model =
{ value : String
, message : String
}
initialState : Model
initialState =
{ value = "0"
, message = "Hi!"
}
type Msg
= Change String
update : Msg -> Model -> Model
update msg model =
case msg of
Change newContent ->
{ model | value = newContent }
view : Model -> Html Msg
view model =
div []
[ input [ type_ "range", onInput Change ] []
, p [] [ text model.value ]
, p [] [ text model.message ]
]
Top comments (4)
Whenever a type has multiple terms, it means its a generic type.
If you're familiar with Java style generics, then
List Int
in Elm would be Java'sList<Integer>
. The purpose of generics is so that we can define what a List is once, but on different instances of it the operations (likepush
andget
) have their type signatures updated so that they accept or return different things, likeInt
values in our case. We didn't have to define aIntegerList
and aStringList
and aFooList
and so on, justList a
in Elm orList<T>
in Java.So similarly,
Html Msg
would be likeHtml<Msg>
in Java. This is some data structure for holding HTML nodes, where some of it's operations (related to event listeners in this case) can produce values that have the typeMsg
. (Msg is not a special type, you defined it yourself withtype Msg = Change String
!).And still similarly, Program is a generic type with 3 type parameters, so
Program Flags Model Msg
in Elm is likeProgram<Flags, Model, Msg>
in Java. It's a Program that uses some type calledFlags
in some of it's operations that deal with program startup, some type calledModel
in some other operations that deal with state, and some type calledMsg
in other operations that deal with messaging. (AgainModel
andMsg
are simply defined by you!)All that's left to explain is
()
, which is also less special than it might first appear. It's just a type that can only have one value -()
is the name of the type, and of the value. It just gets called the "unit" type, and the value is actually an empty Tuple. So operations on aProgram () Model Msg
can only ever get one value, the empty Tuple()
, in its operations that deal with starting up - and so a Program generated byBrowser.sandbox
always starts up the same.So all of this is a bit like
public static void main(String[] args)
in Java. As a beginner, you just write it because somebody told you to and the language demands it. Eventually you learn about when you should make thingspublic
and whenstatic
and when should they returnvoid
and how to useString[]
arrays, and then you look at the boilerplate code and understand that those things really do (have to) apply tomain
. Same with Elm's boilerplate eventually, but don't worry too much about it as a beginner.Really nice to read something from your point of view of being new to Elm.
For the Int vs String with your
value
you can do, as you said, and usecase .. of
. Or you could use things fromMaybe
. Withcase .. of
you might end up withThis means that the value would only change when it gets valid input. Another option would be to have a default when a bad input is given, like so
which could be done instead with
Maybe
functions like soor if you wanted to write it without parentheses
I also completely agree with you when you said
Elm really shines when you're building something medium to large and not something tiny like an input with a message. Hope your journey with Elm continues to be fun.
Seems like you have grasped a lot of the essence. And you are right, such a simple application would probably be simpler in Svelte.
Elm excels when your app reaches a certain complexity and it keeps on being fun :)
Sandbox pretty much gives you the minimal of what Elm can offer. To describe it in JavaScript terms, it lets you set and update state, but nothing else, like it can't make Http requests or talk with the outside world in any way.
First of all, it's a great job you're doing.
Just one thing, the range is not at the right place at start, thus you can do this in the view: [ input [ type_ "range", value model.value, onInput Change ] []