suggestions icon indicating copy to clipboard operation
suggestions copied to clipboard

If / else conditional

Open sporto opened this issue 5 years ago • 13 comments

Although pattern matching is great most of the time, there are a couple of cases where I rather use if/else (based on my experience with other languages).

  1. When I have to check unrelated conditions. E.g.
  if a == 1
    ...
  else if b == 2
    ...
  else
    ..

In Gleam, I am doing this (maybe there is a better way?)

  case a == 1 {
    True ->
      case b == 2 {
        True -> ...
        False -> ...
      }
    False -> ...
  }

I find the if/else alternative easier to understand. Specially if you have several levels of conditions.

  1. When I need to parse strings:
type Enum {
  One
  Two
}

fn one() {
  "one"
}

fn two() {
  "two"
}

fn enum_to_code(enum: Enum) -> String {
  case enum {
    One -> one()
    Two -> two()
  }
}

fn code_to_enum(code: String) -> Result(Enum, String) {
  // Invalid syntax:
  case code {
    one() -> Ok(One)
    two() -> Ok(Two)
    _ -> Err("Unknown " ++ code)
  }
}

In code_to_enum I cannot pattern match on these functions. So this is invalid. To make this valid I would need to pattern match on strings:

  case code {
    "one" -> Ok(One)
    ...
  }

This isn't great as the strings are now in two places. In the enum_to_code and the code_to_enum functions. In this case I find if/else a better alternative:

fn code_to_enum(code: String) -> Result(Enum, String) {
  if code == one()
	Ok(One)
  else if code == two()
    Ok(Two)
  else
    Err("Unknown " ++ code)
}

Using if/else here allows me to use have one source of truth.

Maybe in this second case there is a better solution if I could pattern match on something that doesn't need to be duplicated (e.g. a top level constant).

Thanks

sporto avatar May 16 '20 02:05 sporto

I think there are two different things here! First is constants- I think they would be a good addition to the language, please open an issue for them in the gleam-lang/gleam project.

As for if-else, you are right in the limitation of case here. I quite like the fact that Gleam currently only has a single form of flow control, but we can't represent if, else if, else if, else type code as cleanly. I would be interested in hearing what other people think here.

lpil avatar May 16 '20 18:05 lpil

In go and Ruby, you can have a classical switch / case (well, it's called case / when in Ruby), but it's possible to omit the variable after the switch and use several conditions:

switch {
case a == 1:
    fmt.Printf("a")
case b == 1:
    fmt.Printf("b")
default:
    fmt.Printf("c")
}

I don't know if it would be possible to something like that in Gleam:

case {
  a == 1 -> "a"
  b == 1 -> "b"
  _ -> "c"
}

nono avatar May 16 '20 19:05 nono

It could work! It's worth noting that if is already a keyword in Gleam, so it may be clearer to people coming from other languages if we use that keyword.

case {
  is_on() -> "It's on"
  is_broken() -> "It's broken"
  _ -> "I dunno"
}
if {
  is_on() -> "It's on"
  is_broken() -> "It's broken"
  _ -> "I dunno"
}
if is_on() {
  "It's on"
} else if is_broken() {
  "It's broken"
} else {
  "I dunno"
}

lpil avatar May 16 '20 19:05 lpil

if {
  is_on() -> "It's on"
  is_broken() -> "It's broken"
  _ -> "I dunno"
}

This one looks great, very clear and concise

sporto avatar May 17 '20 02:05 sporto

is the shorter option for nested if's something like:

case a == 1, b == 2 {
  True, True ->
  True, False ->
  False, _ ->
}

My 2 cents, I really like that there is only one control flow.

CrowdHailer avatar May 18 '20 01:05 CrowdHailer

Me too! I should point out that that isn't the same though as it evaluates both expressions, while if may short circuit.

lpil avatar May 18 '20 08:05 lpil

Why not just if from erlang?

if
    a == 1 -> do_stuff1()
    b == blah -> do_stuff2()
    c == blorp -> do_stuff3()
    some_valid_guards -> do_stuff4()
    true -> some_fallback()
end

Or in Elixir with cond:

cond do
    a == 1 -> do_stuff1()
    b == blah -> do_stuff2()
    c == blorp -> do_stuff3()
    even_non_guards_work -> do_stuff4()
    true -> some_fallback() 
end

Erlang's is a super fast decomposition into a case as it only supports guards, so the above erlang basically becomes:

case () of
    _ when a == 1 -> do_stuff1()
    _ when b == blah -> do_stuff2()
    _ when c == blorp -> do_stuff3()
    _ when even_non_guards_work -> do_stuff4()
    _ when true -> some_fallback() 
end

This is extremely fast to run. However Elixir's supports arbitrary expressions, not just guards, so you can do something like String.starts_with?(...) as the conditional expression and it works, but Elixir's decomposes to a tree of case calls.

The manual decomposition as erlang does into the case can already be done in gleam though, so that means that if macros get added then you could just define an if in the standard library that does the same or so. Elixir's is more powerful though slower, it would be useful to add, but could also just be a macro as well on top of a tree of case's.

OvermindDL1 avatar Jul 02 '20 15:07 OvermindDL1

I would like something like cond rather than Erlang's if, so that non-guard functions can be used.

I lean towards if else like syntax in order to make it more familiar to people from mainstream languages.

lpil avatar Jul 02 '20 16:07 lpil

I think the "traditional" if/else if/else syntax is best, because it is much more concise when there is only one branch:

if condition {
    do_something()
    do_something_else()
}

vs

if {
    condition -> {
        do_something()
        do_something_else()
    }
}

Aloso avatar Aug 03 '20 04:08 Aloso

Everything in Gleam is an expression, and Gleam is statically typed, so what would a if without an else return? All languages with these properties that I'm aware of don't permit if without else.

lpil avatar Aug 03 '20 10:08 lpil

Rust does if without an else as returning the unit / empty tuple (), and so if the 'then' branch doesn't return a unit tuple then it fails to unify, thus further enforcing. ^.^

OvermindDL1 avatar Aug 03 '20 14:08 OvermindDL1

We don't really have a unit type, though perhaps Nil could work. I could imagine it being annoying having to add the line returning Nil.

I wonder if perhaps it can return anything if it is not used.

lpil avatar Aug 03 '20 14:08 lpil

I like for if to always have an else at least. I makes you think about what you do.

inoas avatar Feb 22 '21 12:02 inoas