A simple list recursion problem: how do I do this?

I’m currently working my way through Elm in Action and (tentatively, hoping it’s here for the long run) learning the basics of Elm. I do have some educational experience of functional programming, albeit in a different language (Racket lang).

I have a silly but confusing example I’m trying to figure out which is super simple in Racket:

(define daysOfWeek (list "Monday" "Tuesday" "Wednesday"))
(define-struct day (index string))

(define (makeDays list index)
  (cond
    [(empty? list) '()]
    [else (cons (make-day index (first list))
                (makeDays (rest list) (add1 index)))]))

> (makeDays daysOfWeek 0)
(list (make-day 0 "Monday") (make-day 1 "Tuesday") (make-day 2 "Wednesday"))

It’s based on this case expression example by “Beginning Elm” book

  1. I know I need a Tuple and a pair function (Int, String);
  2. I know the base case is something like [] -> [];
  3. I know I could check the length and if == 1 return the last day;
  4. I know you can do 1 :: [2, 3] and List.append;
  5. I know it’s probably wasteful doing it this way there are better data types/methods;
  6. I’ve also seen (but don’t understand) “destructuring” in the case expression like head :: tail -> or something like this.

So in short, I’d like a beginners guide (if one is available) on recursion, deconstruction, best practices, etc. I’m still wrestling with the syntax (and Elm conceptually) so any help would be much appreciated.

  • What advice would you give for a novice when it comes to simple recursive functions?
  • How to you cover all branches, especially the last loop of the list?
  • When is iteration preferable over recursion?
  • When is this method deemed bad?
  • What other methods are preferable?

I don’t know Racket and I don’t know if it helps, but I tried to translate your Racket code as directly as possible to Elm: https://ellie-app.com/qcmZvXS6fcDa1

module Main exposing (main)

import Html


daysOfWeek = ["Monday", "Tuesday", "Wednesday"]
type Day = MakeDay Int String

makeDays list index =
    case list of
        [] -> []
        first :: rest -> MakeDay index first
                         :: makeDays rest (index + 1)

result = makeDays daysOfWeek 0

main =
    Html.text (Debug.toString result)

I’ll take a stab at explaining destructuring or pattern matching.

Here is a more “naive” version of the code in my previous post:

module Main exposing (main)

import Html


daysOfWeek = ["Monday", "Tuesday", "Wednesday"]
type Day = MakeDay Int String

makeDays list index =
    if List.isEmpty list then []
    else MakeDay index (List.head list)
         :: makeDays (List.tail list) (index + 1)

result = makeDays daysOfWeek 0

main =
    Html.text (Debug.toString result)

It results in compiler errors though. Why? Because List.head returns a “Maybe” because the list might be empty. Some languages throw an exception in such a case, but not Elm.

But we know that the list is not empty, right? Because we checked with that if statement. That’s not how Elm works though. List.isEmpty does not prove that the list is not empty on the type level in Elm. I think that would require “program flow analysis” but Elm does not do that.

Luckily, using a case expression solves the problem. It uses pattern matching to both check if the list is empty at runtime and at compile time. I think of it as a “type safe” if-else where you can “preserve knowledge” on the type level from each condition through “structure”.

case list of
   [] -> something
-- ^^ this means that the compiled code is going to check if `list` is the empty list
   first :: rest -> something
-- ^^^^^^^^^^^^^ this means that the compiled code is going to check if `list` is a list with a first item followed by something (which could an empty list or a couple of items)
-- as a bonus, while it did those checks and had the first item available anyway, it gives you access to it, in a variable that we decided to call `first`
-- and you get the rest of the list in a variable called `rest`

The :: syntax is pretty weird. It depends on which programming language you come from, but this hypothetical syntax would have been more familiar to me:

case list of
    [] -> something
    [first, ...rest] -> something
1 Like

Hey @lydell seeing as how you don’t know Racket, that’s pretty spot on! But let me step through it to make sure I understand it fully (I’m sorry there’s a lot of questions):

On Maybe and an [] empty list

  1. I’ve come across Maybe while using Array such as Array.get 3 (Array.fromList [1, 2, 3]) which would return Nothing`.
    • I understand you can use a case expression for each type variant.
  2. I understand that [] is pattern matching on exactly the type [] (which I guess is saying List a?)
    • I’m surprised an empty? branch isn’t possible like my Racket example

In Lisp, cond is a bit more flexible, and the function example implicitly expects a list. You can also use it to check types, for example:

(define (checkType type)
  (cond
    [(number? type) 'number]
    [(list? type) 'list]))

It seems Elm is more explicit (strict) about what types it takes in, and what it allows a case statement to give out — the compiler is telling me “All branches in a case must produce the same type of values.”

Questions:

  1. If using List.isEmpty doesn’t work in your example (because of a compiler error) — what’s it useful for?
  2. Surely you don’t need to case on every function that uses Maybe?
  3. I still don’t entirely understand the runtime/compile time problem here.

On destructuring in a case expression

  1. From your example, it would seem to me that first :: rest uses List.head and List.tail which:
    • First checks that the list argument is a List type;
    • Then returns Just [1] and Just [2, 3, 4] (given a List number).

The operator :: is indeed weird. I thought (::) was only for adding an item to a list? The equivalent of (cons ...) in lisp.

I guess it’s some special Haskell-type expression.

Question:

  1. It would seem head :: tail is unpacking the list and assigning List.head and List.tail to those variables? I’ve already tried changing those variable names (like x :: y) to the left of the second -> branch, so they’re not special names right?
  2. I’ve also seen it written (in one of my links on the original post) like:
[x] -> x       -- #1
(x::xs) ->    -- #2

Is (1) saying “last in list” and (2) equivalent to your version head :: tail?

General recursion stuff

With Lisp it’s explicit that if you step through that function it:

  1. Checks that list is empty '()
  2. If not, calls first on some helper function
  3. Then passes rest to original function
  4. Continues until list is empty '()
  5. The base condition is met so recursion ends.

That makes sense to me. With Lisp everything is essentially a pair, so:

> (cons 1 (cons 2 '()))
(list 1 2)

Question:

  1. It’s not 100% clear to me in your example the same thing is happening, as trying to do [] :: [1, 2, 3, 4] throws an error. Is Elm somehow running append [] [1, 2,3] in the background on that [] -> [] base case?

It looks deceivingly simple so perhaps I’m just not thinking about it correctly.

More on case expressions

So I understand case expressions properly, what can go in between case ... of and what goes before the ->? I’ve once seen a function before in between case ... of such as:

testing a =
  case List.isEmpty a of
    True -> "Empty"
    False -> "Not empty"

testing []

But seems like you can’t treat it like an if statement with a conditional like this:

List.isEmpty list -> "Empty"

I guess I understand Lisp’s cond better than I do Elm’s case!

Finally, to be clear of my original intention:

I originally wanted to use a simple function, rather than the “Beginning Elm” one that uses a case expression for every index.

  1. The idea was to create a List Day where Day was a tuple (0, "Monday").
  2. I was then hoping to create a simple getDay index function to find the corresponding day (the “string” part of the tuple).

I realise now there’s probably a much saner way to do that, which doesn’t involve having to loop through a list again. You could use List.filter I suppose, but you’re still looping the list. I guess Array might be possible, but it’s a list of tuples. You could use a record, but you wouldn’t be able to use an index.

I realise I’m overcomplicating things, and there’s probably better ways to do this, but it’s good to experiment to see what’s possible and what’s the “Elm way” of doing things.

(Final) Questions!

So, taking my (possibly poor) example and the way you wrote it:

  1. What’s the benefit of using a Day type over a Tuple type? How do you grab the index to retrieve the string?
  2. Is there a good link to using the right data types you could forward me? A set of days is a finite list so I’m guessing there’s a better way.

I’ve just realised how long that was!! Functional programming can take really a lot of explaining. Sorry :stuck_out_tongue:

If using List.isEmpty doesn’t work in your example (because of a compiler error) — what’s it useful for?

Here’s an example:

viewCart products =
    if List.isEmpty products then
        div [] [ text "Your cart is empty" ]
    else
        ul [] (List.map viewProduct products)

Surely you don’t need to case on every function that uses Maybe?

There are lots of helper functions like Maybe.map, Maybe.andThen and Maybe.withDefault which you’ll use often, but a good old case on a Maybe is still a common pattern.

it would seem to me that first :: rest uses List.head and List.tail

It’s the other way around: List.head and List.tail source code.

First checks that the list argument is a List type

Since Elm is statically typed, it doesn’t need to do a runtime check if it’s a list. It knows it’s a list, and the compiled code assumes that.

Then returns Just [1] and Just [2, 3, 4] (given a List number).

There is no Maybe (which means no Just) involved when pattern matching on a list.

The operator :: is indeed weird. I thought (::) was only for adding an item to a list? The equivalent of (cons ...) in lisp.

The idea with the pattern matching syntax is that it looks a pretty much like the regular syntax for building that thing. For example, you can have myNumber = 123 and pattern match with:

case myNumber of
    123 -> "It's 123!"
 -- ^^^ this looks like the regular syntax for building a number
    _ -> "It's something else"

And you can have:

anEmptyList = []
oneItem = [1]
twoItems = [1, 2]

And pattern match:

case someList of
    [] -> "It's an empty list"
    [imSingle] -> "It's a list with one item, which is " ++ String.fromInt imSingle
    [first, second] -> "It's a list with two items, which are " ++ String.fromInt first ++ " and " ++ String.fromInt second
    _ -> "It's a list with more than two items"

Then we have the :: a.k.a. “cons” operator, which lets you construct lists in a more verbose way:

anEmptyList = []
oneItem = 1 :: []
twoItems = 1 :: 2 :: []

Elm has pattern matching syntax using :: too, for symmetry (and if you want to match on a list of unknown length this is the only way of doing it):

case someList of
    [] -> "It's an empty list"
    first :: [] -> "It's a list with one item, which is " ++ String.fromInt first
    first :: second :: [] -> "It's a list with two items, which are " ++ String.fromInt first ++ " and " ++ String.fromInt second
    _ -> "It's a list with more than two items"

That’s the only operator in Elm that has a pattern matching syntax, as far as I know. So this is not a thing:

case 5 of
    1 + 4 -> "It's 5"
 -- ^^^^^ a hypothetical language feature 
    _ -> "It's something else"

I’ve already tried changing those variable names (like x :: y) to the left of the second -> branch, so they’re not special names right?

Correct, you can name them whatever you want.

is (1) saying “last in list”

No, it’s saying “the only item in a list of length 1”.

(2) equivalent to your version head :: tail?

Yes, parentheses, spaces and choice of names don’t matter.

trying to do [] :: [1, 2, 3, 4] throws an error

That’s because the left side of :: is one item of the list, and the right side is the rest of the list. But you have a list on the left. So 0 :: [1, 2, 3, 4] would be valid (it evaluates to [0, 1, 2, 3, 4]), and [] :: [[1], [2], [3], [4]] would be valid (the list on the right contains lists, and the left side is one such list, and it evaluates to [[], [1], [2], [3], [4]]).

what can go in between case ... of

Anything. However, some things are so called “opaque types” which you can’t do anything with. And if you put a function between case and of, there isn’t any meaningful way to pattern match on it. However, there’s nothing stopping you from doing:

case List.map of
    _ -> 1

(It evaluates to 1.)

what goes before the ->? But seems like you can’t treat it like an if statement with a conditional like this: List.isEmpty list -> "Empty"

The thing before -> is special pattern matching syntax. You can’t put any piece of code there. There’s pattern matching syntax for booleans, numbers, strings, lists, tuples, records, custom types, and a few things more, but not much.

What’s the benefit of using a Day type over a Tuple type? How do you grab the index to retrieve the string?

None in this case I think, that’s just me trying to produce some Elm code that looked really similar to your Racket code. Didn’t realize your Racket code had tuples.

Is there a good link to using the right data types you could forward me? A set of days is a finite list so I’m guessing there’s a better way.

The elm/time package has a Weekday type: Time - time 1.0.0

rather than the “Beginning Elm” one that uses a case expression for every index. […] I was then hoping to create a simple getDay index function to find the corresponding day (the “string” part of the tuple).

The “Beginning Elm” way is the idiomatic way, I’d say.

The following code is very readable and performant:

import Time

getDay : Int -> Maybe Time.Weekday
getDay index =
    case index of
        0 -> Just Time.Mon
        1 -> Just Time.Tue
        2 -> Just Time.Wed
        3 -> Just Time.Thu
        4 -> Just Time.Fri
        5 -> Just Time.Sat
        6 -> Just Time.Sun
        _ -> Nothing

I hope I managed to answer most of your questions somewhat well!

Btw, do you know any other programming languages? Sometimes I find it nice to explain Elm features in terms of another programming language, but I don’t know Racket so I can’t do that here.

1 Like

@lydell Oh man. I think I understood about 50% of your answers, so I’m going to have to re-read it a few times to wrap my head around it :exploding_head: — in the meantime, I got pretty much working what I’d imagined:

A (not so) simple recursive weekday

Reading the link’s code back to myself, yes it’s trying to be too clever and overcomplicating things — it’s a good way of exploring how Elm works though.

The day Type and time case examples you linked to do seem to be a far more readable and sane way to do things. I guess I’ve got to get use to writing more case expressions (over the way I’d do things in Lisp).

FYI the original Lisp code I wrote doesn’t include tuples, it sets a struct — kinda like a named record.

As for the bits of your answers I did understand right now:

  1. So x :: y is more a pattern matching thing, than calling the list functions? (when I think pattern matching, I think regex)
  2. Case branches (before the ->) should be thought of as pattern matching on a Type/Type variant/variable/etc?
  3. Is the base case [] -> [] more like a break statement then?***
  4. I guess I’m still not 100% sure how recursive functions end in Elm.

I know a bit of Python, but I’m rusty as I’ve not used it in a long time (I take it Elm favours recursion over iteration/loops?). Otherwise just plain old HTML and CSS.

I’m not a javascript guy and picked Elm primarily because Javascript language design really doesn’t appeal to me. Racket Lang is just a Lisp and pretty functional, but I think it’s easier to “get” than Haskell.


*** In Lisp you’re actually consing an '() empty list as the last pair of a list. So (cons 1 '()) -> (list 1) – there’d be a bunch of other (cons ...)s before that one in the recursive function.

If it helps, the Elm code I posted in my first reply basically compiles to (but using Python instead of JavaScript, and lists in Elm are actually linked lists, not JavaScript arrays):

daysOfWeek = ["Monday", "Tuesday", "Wednesday"]

def makeDays(list, index):
    if len(list) == 0:
        return []
    else:
        first = list[0]
        rest = list[1:]
        return [(index, first)] + makeDays(rest, index + 1)

print(makeDays(daysOfWeek, 0))

Fun thing though: Modern Python also has pattern matching:

def makeDaysModern(list, index):
    match list:
        case []:
            return []
        case [first, *rest]:
            return [(index, first), *makeDays(rest, index + 1)]
  1. x :: y is both valid pattern matching syntax, and a valid expression. In pattern matching there are no function calls involved – it compiles to straight up if statements and stuff like that in JavaScript. As an expression, it calls the (::) function with the arguments x and y.

  2. Yes!

  3. [] -> [] means “if the list is empty, return an empty list”. See the Python code above.

  4. Maybe it got clearer with the Python code that has explicit return statements? Other than that I’d say that recursive functions works like in other languages: You need a base case that doesn’t recurse, otherwise you get an infinite loop.

Welcome to Typed Functional Programming! I also learned Common LISP and Racket first on my FP journey as well (working through Structure and Interpretation of Computer Programs, books by Paul Graham and Peter Norvig, books on Macro writing Macros, etc…). I think you will enjoy a different kind of formal rigor with typed FP but you are exchanging infinite expressivity of homoiconic macro languages for formal specification and verification. The latter becomes more valuable as projects grow in size, take longer periods of time, and are worked on by more team members. I might use a LISP if I needed to code a fairly small (< 500 lines), really complex thing by myself in a weekend. Otherwise I turn to Elm, PureScript, Haskell, or OCaml.

That said there are very large codebases written in Clojure (also a LISP) so… you know… to each their own.

  • What advice would you give for a novice when it comes to simple recursive functions?

You need recursion a lot less often that you might originally think! I could buy a :video_game: PS5 and a nice collection of games if I had a dollar for every time I wrote a recursive function that I later realized could be more simply and elegantly expressed as a library function. :man_facepalming:

For a beautiful discussion of the theory see Adventures in Uncertainty: An Introduction to Recursion Schemes. That discusses the idea that

recursion schemes are just as essential to idiomatic functional programming as for and while are to idiomatic imperative programming

What that amounts to concretely is that you probably want a library function about 95% of the time. I wish someone had stressed this to me when I started so I am relaying it to you in the hopes that it saves you some missteps.

  • map - Any time you iterate over some collection of things and produce another collection of the same kind (List, Array, Dict, etc.) with the same number of elements but changing the type of the element.
  • [zip[(Elm Search - 0.19.1) - You need to pair elements at the same position in two separate collections.
  • traverse - This is like map except that the mapping function can have an “effect”. For example, a pure mapping function might be Widget -> Gadget. That function indicates that you can always go from a Widget to a Gadget i.e. it can never fail. However, if the mapping function were instead Widget -> Maybe Gadget that means that it is possible for the conversion to fail. In that case you use traverse instead of map. Some data types call their traverse by another name ex. Result.combineMap.
  • foldl and foldr - Essentially a pure functional equivalent of a for-loop!!! :tada: This allows you to visit every element of a collection updating state (often called the accumulator). When I first learned functional programming I thought that maybe dynamic algorithms (which are typically bottom-up) could not be done in FP since recursion is more top-down). That was wrong. You can implement dynamic algorithms directly (if not space efficiently) using fold.
  • Elm libraries typically don’t the last major utility which is foldM but you can see the PureScript and Haskell type signatures. The PureScript and Haskell versions are abstract both over the collection and over the “effect” which makes it a little hard to read so I will give a concrete type signature in Elm
    foldM : (state -> element -> Maybe state) -> state -> List element -> Maybe state
    
    It is possible to make this yourself. I have included examples at this Ellie link. HOWEVER, be warned that those are not the most efficient.
  • How to you cover all branches, especially the last loop of the list?

In FP there is a beautiful symmetry between value constructors and value eliminators that mirrors the symmetry between function generalization (definition) and application (calling) in the lambda calculus. The case statement eliminator takes the sum type apart and ensures you cover all branches. It works with an “initial encoding” Api style. If you want to instead work more with functions you can use a “final encoding” Api style by using functions such as

2 Likes

Yes I’ve looked at more complex programs in Racket lang and it wouldn’t make sense to me to build a big content site in that language, for instance, where Python (or the languages you’ve mentioned) would probably win out.

Some things are really amazing about Lisp, but you’re right that larger programs can be difficult. When I look back at some of my Racket-based projects, there’s definitely some head-scratching going on. I’ve looked into Clojure before but the Java platform put me off.

I worked my way through “How to Design Programs” and made a start on SICP but it wasn’t working learning “Elm In Action” at the same time! I’m a maths novice (with no intention to learn), so hoping SICP goes easy on the maths in later chapters and isn’t too difficult.

My background is predominantly in design (graphic, web) and frontend (HTML, CSS, not much javascript) and have met many a good programmer. I definitely feel there’s some wiring that’s gone into their young(er) brains that shaped them in a way I’m not built for.

@john_s I really appreciate your post and will check out those functions. I had to look up homoiconic(!) and don’t pretend to understand everything you said … but seems helpful!

I feel there’s two major things I struggle with, and think a lot of young programmers-to-be struggle with, which I feel could really do with different approaches to education and learning:

  1. Academic and densely worded concepts. I really can’t keep the terminology in-mind and constantly forget. It sometimes seems like a competition for who can out-word the next guy!

  2. Visualising or understanding concepts with functional programming can be tough too. I admire what Evan’s trying to achieve, whilst keeping it accessible for beginners. I still don’t know what a macro does or what a monad is, and probably said best with this Stackoverflow question:

I have found most explanations I’ve come across to be fairly inaccessible and lacking in practical detail.

I could say that about many, many things programming-related. Especially for those of us who don’t want to learn a physics, maths, data science, or the like. I think there’s a space for learners that’s more visual, easily accessible, especially with AI around the corner. Not all of us want to learn programming so deeply and I’m hoping Elm allows me to build things pragmatically.

Alas, some things CompSci are just hard, unfortunately. I do find it piques my curiosity but we need more teachers like Richard Feynman who can make kids/adults give a shit, in a way that describes things at different levels.

I’m glad to hear you can lean on libraries rather than worrying too much about recursion. You build recursive functions like map, filter, and reduce when studying, so I guess you don’t have to reason about them so much when you have good libraries. Recursion for nested lists, and deeply nested lists isn’t much fun :wink:

@lydell I understand the first Python example better than the second. I still feel like the base case [] -> [] isn’t as clear as Lisp is (in how it works) so I’m going to think of it as either append [] [1,2,3], [] ++ [1,2,3] or Nothing which fits my mental model better.

So to sum up:

  1. Use library functions wherever possible (95% of the time)
  2. Pattern match on case often, trying not to be too clever with data structures or functions
  3. Use recursion where it’s simple, or when nothing else will do.
  4. Try to explain things in as simple as possible a way to new Elm learners! (With concrete examples)
  5. Buy a :video_game: PS5 and Grand Theft Auto (I haven’t played in years!)

Thanks for the help, sorry for the waffle! Good to hear other people’s opinions.

3 Likes
  1. Academic and densely worded concepts. I really can’t keep the terminology in-mind and constantly forget. It sometimes seems like a competition for who can out-word the next guy!

I am so sorry! I made an assumption and you know what they say about assumptions.

Racket is about the most computer-sciencey / academic language I know (pretty much up there with Coq and Idris) and so I assumed certain knowledge. That was stupid and insensitive of me and my bad. I just got excited because I spent a lot of time in that space (LISP family languages). I just have to say that Racket is the deep end of the pool.

One should always use the smallest and simplest word that is sufficiently descriptive and precise. However, what is simplest is not universal. It is a function of one’s audience and their background. Everyone is at a different point on their journey and it is absolutely true that education material would benefit from an explicit declaration of prerequisites. I have personally spent a lot of time feeling dumb when I wandered into material aimed at PhD candidates in computer science. So I feel you.

The Elm community is a fantastic place to be for those relatively new to functional programming or those more interested in pragmatic benefits of FP. They absolutely do connect every concept to practical usage in accessible ways and without pretention.

You are going to get a lot of good work done and have a lot of fun doing it with Elm.

I will say that if you ever do want to learn about the big FP concepts then I would highly recommend Functional Programming Made Easier. It basically holds your hand and skips no steps as it moves from zero FP knowledge to some moderately advanced material. The book is crazy long but you don’t have to read the entire thing. I would recommend the first three parts. Even still that is like 1000 pages. That sounds like a lot but I wish that I had read it when I was first starting. It took me about 12 years, eight books, countless online articles, and a lot of feeling stupid and frustrated to learn on my own what that book spells out in very simple and accessible terms.

1 Like

Once I read Elm in action, I felt I had passed the beginner stage alright, but still had a lot of FP related questions. So I turned to other resources to fill in the gap.

I can recommend 2 OCaml resources which helped grok recursion. OCaml and Elm’s type systems are very similar so this knowledge is transferable IMO.

Firstly have a look at this chapter: https://johnwhitington.net/ocamlfromtheverybeginning/split07.html

In it, you study what lists are and start to implement the most basic recursive functions.

Then, you can find around 30 exercises related to lists and recursion on Ocaml’s website: Exercises

I had a blast going through them. As mentioned on the website, those “99 problems” came from the LISP world, and Prolog before that.

Anyhow I found these exercises extremely good and you can work on them in plain Elm.

As been mentioned, most of the times you’ll use higher order functions to loop, but still I think it’s good knowledge to be able to do so without relying on library functions.

Hope that helps, and remember it takes time and repetition for that stuff to sink in (the brain wiring you talked about)

Good to hear praise of that book, I’ve been hesitating buying it

I’m sorry if that came off as a criticism; perhaps I should’ve tried to rein it in a bit. It’s wasn’t an attack on any individual, more a critique on the programming world in general. You’re right to assume some programming knowledge of course — it’s good to see someone else who’s spent time in the Lisp world!

I have personally spent a lot of time feeling dumb when I wandered into material aimed at PhD candidates in computer science. So I feel you.

Yup. Looking at the old lectures of SICP and many of the audience look in their 40s, so they’ll all have the "prerequisite knowledge` like maths, engineering, so on. HTDP is definitely an easier text, but still. Academic.

I’ve spent about 10 years all-in-all trying to learn programming and gave up on doing that professionally a while ago, so I’m more of a hobbyist in the sense that I want to build my own projects for a side hustle, for teaching, or just for fun. I was about 30 when I started so you can guess my age. Still doesn’t come easy :man_shrugging:

I have to remind myself of what it was like as a beginner for my own little learning-aid tool. I’ve taught coding to kids around 8—12 and have learned there’s no such thing as a stupid question! I’m still trying to figure out which things just take time to learn and which things are needlessly complex. Even “simple” CSS can be a royal pain in the ass, for instance. I guess I’d just like it all to be a bit easier (wishful thinking) :upside_down_face:

Definitely Racket and Lisp are more college-level materials, it’s been about 3 years since I touched it so have forgotten a lot. I think I reached the end of chapter 02 “Arbitrarily large data” so there’s stuff I haven’t learned yet. In general though I’m quite happy standing on the sidelines and letting people smarter than me figure out the tough stuff! AI is coming.

There’s a good word called an “argot” for when groups have their own vernacular. It’s a natural thing I guess, and happens in any profession, but can definitely be off-putting. Racket and Elm do seem like friendly communities, like you said, which is great. I hope Elm grows again.

Cool books to check out:

@john_s “I will say that if you ever do want to learn about the big FP concepts then I would highly recommend Functional Programming Made Easier

That seems to be written in PureScript? Is it similar to Elm? Popular? Any visual guides? I’m a little hesitant to add another programming language into the mix.

I found HTDP exercises took quite a while to get through. 1000 pages is a lot! Is it scaffolded learning or can you safely skip some of the exercises? Is it all transferable knowledge for other FP languages?

@benjamin-thomas “Firstly have a look at this chapter: https://johnwhitington.net/ocamlfromtheverybeginning/split07.html

Oh my god [1; 2; 3] semi-colons for list dividers? That sounds insane, but the article does look good. last : 'a list -> 'a option does this mean you can pass code around like data (as you can in Lisp (using 'quote)?

Is the syntax for OCaml something you can wrap your head around in a day? It sounds like you had no problem translating them into standard Elm.

It’s nice to hear we all felt stupid at one time or another!

You know, to my ears that sounds like “OMG LISP has so many parentheses” :wink:

Jokes aside, Elm chose , as a list seperator, OCaml ; and LISP white space. To me that’s equivalent and not really worth being too bothered about.

But I do remember being slightly annoyed by a few syntax choices early on, but that quickly faded away. Overall I would say Elm’s syntax is more pleasant though.

In OCaml, the ' character has completely different meaning than in LISP, it indicates a “type variable” (in other words a generic type)

-- Elm
reverse : List whatever -> List whatever
reverse lst =
    let
        loop accum rest =
            case rest of
                [] ->
                    accum

                x :: xs ->
                    loop (x :: accum) xs
    in
    loop [] lst
(* OCaml *)
let reverse (lst : 'whatever list) : 'whatever list =
  let rec loop accum rest =
    match rest with
    | [] -> accum
    | x :: xs -> loop (x :: accum) xs
  in
  loop [] lst
;;

I believe the equivalent function would look like this in Racket (I’ve only been toying with LISP languages)

(define (reverse lst)
  (let loop ([accum '()] [rest lst])
    (cond
      [(null? rest) accum]
      [else (loop (cons (car rest) accum) (cdr rest))])))

Maybe not in a day, but these days I would say Chat GPT can help bridge that gap providing you double check its answers.

But if you just want to build apps, you don’t need to learn all that. You can just focus on solving your problem at hand and learn by doing.

1 Like

@lydell Just for posterity, I finally get it. You can create a List Tuple like this:

(0, "Monday") :: (1, "Tuesday") :: (2, "Wednesday") :: []

No need for pair. In the recursion loop the [] -> [] case pattern means the empty list gets added to the end so 1 :: 2 :: 3 :: [] is equal to [1,2,3]. It’s basically the same as Lisp’s (cons 1 (cons 2 (cons 3 '()))).

For some reason I didn’t make the connection until now with that :: operator.

Original answer:

1 Like

Touché! Ok so OCaml isn’t a million miles away. So let for a function and let rec for a recursive function? I haven’t worked much with let but yeah I could ask Chat GPT to translate the functions for me.

I swing between wanting to have a good high-level view of functional programming to wanting to get things done quickly. I’ll skim both your suggestions and see what works out. Lots of food for thought.

My reading list is already fit to bursting :wink:

I hope, this little piece does not burst it yet :slight_smile:
Nice clever piece of recursion which @lydell has just presented.
I would also use recursion more often, if I could but I do still prefer map-functions and other List functions.
elm repl is a nice tool as a playground while learning or teaching elm.
Here follow some simple examples…
Do you know Julian date number? You can easily calculate the weekdays for the given Julian date

---- Elm 0.19.1 ----------------------------------------------------------------
Say :help for help and :exit to exit! More at <https://elm-lang.org/0.19.1/repl>
--------------------------------------------------------------------------------
> modBy
<function> : Int -> Int -> Int
> modBy 7 2460345
6 : Int
> modBy 7 2460346
0 : Int
> modBy 7 2460311
0 : Int
> modBy 7 2460317
6 : Int

Number 0 is for Monday and 6 is Sunday
You get the the date numbers e.g. using this calculator.
I know, people who love Lisp do know the lists, so here a little elm lists.
I define first a list of day numbers and second one of the day names.

> wdNrs = List.range 1 7
[1,2,3,4,5,6,7]
    : List Int
> wdNames = ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]
["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]
    : List String

Next I’ll put them together. I must import the function zip from the library.

> import List.Extra exposing (zip)
> zip wdNrs wdNames
[(1,"Mon"),(2,"Tue"),(3,"Wed"),(4,"Thu"),(5,"Fri"),(6,"Sat"),(7,"Sun")]
    : List ( Int, String )

You can do following instead, using the function map2:

> List.map2 (\nr name -> String.fromInt nr ++ " " ++ name) wdNrs wdNames
["1 Mon","2 Tue","3 Wed","4 Thu","5 Fri","6 Sat","7 Sun"]
    : List String
> 

I have made a look to other languages like Haskell and Pure Script, how ever always returned to Elm ,
just because I like using it as hobbyist, not professionally. Pitty that elm is not more popular.

You can simplify even further by using List.indexedMap.

1 Like

Yes we can, but which is quicker search, an indexed list or a list converted first to an array?
I guess, array search is quicker if the conversion time from list to array is not counted.

Sorry for continuing this already solved task.

I meant that any time you do List.map2 f (List.range 0 (List.length myList)) myList you could just as well do List.indexedMap f myList. It’s simpler, and faster.

Correction: It’s not faster. Turns out it’s literally the definition of List.map2 (except my off-by-1-error-that-really-doesn’t-matter): core/src/List.elm at 65cea00afa0de03d7dda0487d964a305fc3d58e3 · elm/core · GitHub