Nice work! I love that you included both clickable buttons and keyboard support for letters.
I notice that you run two commands for to first randomly generate an index for the category and then to randomly generate the index for the word. One of the nice things about Elm is that it allows you to combo generators together such that you only to make a single call to Random.generate
.
In this program for example, you might create a Generator (String, String)
that generates a tuple of a random category name and a random word from within that category.
Single generator
wordGen : Generator (String, String)
wordGen =
Random.int 0 (Array.length categories_and_words - 1)
|> Random.andThen (\categoryIdx ->
case Array.get categoryIdx categories_and_words of
Just { category, words } ->
Random.int 0 (Array.length words)
|> Random.andThen (\wordIdx ->
case Array.get wordIdx words of
Just word ->
Random.constant (category, word)
Nothing ->
Random.constant ("", "")
)
Nothing ->
Random.constant ("", "")
)
While it does the job, this is a pretty intimidating piece of code!
We could clean it up by breaking it up into smaller functions but that won’t address the underlying issues: Handling those Maybe
values and needing to index into an Array
.
Using List
instead of Array
If you store the categories and words as a List
rather than an Array
, that allows the use of a convenient helper function Random.uniform
that will majorly simplify the generator.
wordGen : Generator (String, String)
wordGen =
Random.uniform { category = "", words = [] } categoriesAndWords
|> Random.andThen (\{category, words} ->
Random.uniform "" words
|> Random.andThen (\word -> Random.constant (category, word))
)
Much cleaner! No more messing around with indices, and handling cases where we couldn’t find a word or category is mostly gone.
Many languages build their programs around arrays + indices. In Elm that style of programming tends to be much rarer in favor of other constructs provided by the language.
The one sort-of ugly thing left is that first argument to Random.uniform
that’s there as a default just in case the list is empty. However, since we’re working with hard-coded data, we know the list will never be empty. If only there were a way to convince the complier…
Using List.NonEmpty
There are a variety of third-party packages that provide a non-empty list type. These guarantee at least one item and thus getting the head of the list does not return a Maybe
. I’m a fan of turboMaCk/non-empty-list-alias
.
With such a type, we could write a variation of uniform
that doesn’t require a default:
uniformNonEmpty : List.NonEmpty a -> Generator a
uniformNonEmpty (head, rest) =
Random.uniform head rest
using this we can simplify the wordGen
again:
wordGen : Generator (String, String)
wordGen =
uniformNonEmpty categoriesAndWords
|> Random.andThen (\{category, words} ->
uniformNonEmpty words
|> Random.andThen (\word -> Random.constant (category, word))
)
You may or may not want to bring in a third-party package for this project. If so, the List
+ Random.uniform
example shown earlier will work just fine.