Core: Add (?) to Maybe?

Created on 15 Apr 2015  路  15Comments  路  Source: elm/core

With lots of core functions becoming total, it makes sense to consider an infix version of withDefault.

(?) : Maybe a -> a -> a

firstNumber =
    head numberList ? 0

If you want to get the head of a list of numbers _or_ just go with zero if it is empty. This is really cool, but here are two main concerns:

  1. Are we adding too many infix operators?
  2. What precedence this operator should have?

Can you share some examples that make the case for adding this function? Do you have any examples that help address concerns 1 or 2?

Most helpful comment

I'll try to help give context for 1) by listing all the non-mathy/non-logic-y infix operators currently in Elm:

  • :: (cons)
  • |> (apply right)
  • <| (apply left)
  • >> (compose right)
  • << (compose left)
  • <~ (Signal.map)
  • ~ (Signal.mapN)
  • ++ (List.append or String.append)
  • := (Json.Decode.decodeField)

All 15 comments

I'll try to help give context for 1) by listing all the non-mathy/non-logic-y infix operators currently in Elm:

  • :: (cons)
  • |> (apply right)
  • <| (apply left)
  • >> (compose right)
  • << (compose left)
  • <~ (Signal.map)
  • ~ (Signal.mapN)
  • ++ (List.append or String.append)
  • := (Json.Decode.decodeField)

I just saw that something similar seems to exists in C#. What other languages have something like this?

Coffeescript (see #Existence:)

There's a long list here: https://en.wikipedia.org/wiki/Null_coalescing_operator

Apparently it's called the "null coalescing operator".

They don't all agree on the exact syntax. But here's a list of the versions I got from just skimming through the wiki page:

  • ?
  • ??
  • :?
  • ?:
  • //

Usually, there's an avoidance of the first one I'm guessing because of the existence of the ternary operator x == null ? x : 0 in those languages. But Elm doesn't have the ternary operator, so it can just pick whatever. :D

Maybe it could be:

(?) : Maybe a -> Bool

(?:) : Maybe a -> a -> a

which would looke like this in use:

if (head xs)? then f xs else g 

a = head xs ?: x

To address point 2, if it's supposed to chain you should take that into account:

head numberList1 ? head numberlList2 ? 0

And you could consider it for record access:

task : Maybe Task
task?.description : Maybe String

Works nicely together:

task?.description ? "" : String

In reply to both this and #215, I just thought I'd share Rust's take on Maybe (called Option).

  • .unwrap() forces the value from the Option (and fails if it is None (aka Nothing)).
  • .expect("Message explaining what was expected.") does the same as .unwrap(), however it also provides an error message in the case that the value was None and failed (this is usually preferred to unwrap as it lets the user provide clarification for the error, though both methods are normally avoided if possible).
  • .unwrap_or(default) does the same as .unwrap(), but rather than failing on None it provides the default value (same as Elm's withDefault).

There's a bunch of other handy methods here too if you're interested (I'm not particularly advocating any of these, just thought I'd share).

In my opinion the (?) operator is clever, though perhaps a little less clear than withDefault and maybe not common enough to justify it's own operator?

Kotlin language for JVM/JS has a smart way of dealing with nulls, probably inspired by Groovy:

  • Elvis operator ?: is a visual shortcut for ternary operator, "if null then this"
  • Save dot operator ?. which returns null itself if applied to null
  • Unsafe take operator from #215 is !!. Unsafe dot !!. is just a combination of the two infix operators, not a special operator.

Originally they've named such unsafe function sure(), like "I'm sure there will be value", but exclamation marks grabs attention much better than any text. And it's an antonym to question mark operators.

Question mark alone sounds like "if", but we are looking for "if not, then", "orElse".

According to the Kotlin grammar:

  • ?. and !! have the same semantics and highest precedence as . itself.
  • Elvis operator occupies dedicated precedence level between default infix operators and comparison.

I would like to have (?) for Bool as well
C#, F# has
test ? if_true : If_false

as a shortcut to

if test then
if_true
else
if_false

In Elm if is an expression, i.e. it returns a value. Therefore there is no ternary operator condition ? then : else, because ordinary if works fine in this role:

answer = if powerLevel > 9000 then "OVER 9000!!!" else "meh"

I'd say it is _much_ easier to understand for beginner programmers.
Also, the idiomatic way of dealing with such conditions and, especially, control flow is different from C.

May I suggest to simply close this issue? Infix operators have lost popularity around here, so if no compelling need was felt to _nevertheless_ add this ? operator over the course of the last 15 months the issue has been open, then probably this is not going to change.

Also, closing the issue will curb desires to have the operator available in even more situations. :smile:

elm-format (following style guide) breaks one line
answer = if powerLevel > 9000 then "OVER 9000!!!" else "meh"
to six. It makes me _unhappy_ as a beginner Elm programmer.

And I am OK with
x |> Maybe.withDefault 0

I find that control flow with Maybe is more "complex" than control flow with Bool and needs to be handled with pattern matching. Substituting Maybe with a default value looks like a hack for most of the cases for me.

But I found that infix operator with lazy evaluation will be helpful

`(??) : Maybe a -> (()->a) -> a'

or syntax sugar for

result = tryCalculateFirst ?? tryCalculateSecond ?? failWithDefaultValue

@yuri-martynov, if I understand correctly, you are proposing two things now:

  1. A separate feature: ? as an operator version of if-then-else. Note that this is not something that can be done in core. Instead, it will have to be wired into the compiler (to get the short-circuiting behavior). So it seems something that you should bring up elsewhere, not here in the library repo.
  2. An alternative to Maybe.withDefault and (?) : Maybe a -> a -> a with different type and slightly different semantics.

Note: F# does not have the ternary operator ? like C#. If / else expressions are written the same as Elm.

I don't think it makes sense to go with this now that a bunch of things have been returning Maybe for quite some time now, and the following things are true:

  1. Maybe.withDefault works nicely with (|>) and has an explicit name.
  2. Writing code that way is nicer to read, especially for beginners, and encourages better style in teams.
  3. Outside of (<|) and (|>) I have never added an infix operator that was _actually_ a good idea in the end.
Was this page helpful?
0 / 5 - 0 ratings