Приложение Ruby, использующее When/Case, дает Nil, когда не должно

Следующий код должен напечатать один из трех вариантов, заданных в зависимости от результатов любой из проверок ==, но это не так. Это дает nil для любого значения your_message, предоставленного через gets.chomp. Может кто-нибудь, пожалуйста, скажите мне, что не так с моим кодом?

def stupid_coach(your_message)
  case your_message
  when your_message == your_message.include?("?")
    p "Silly question, get dressed and go to work!"
  when !your_message == your_message.include?("?")
    p "I don't care, get dressed and go to work!"
  when your_message == "I am going to work now!"
    exit
  end
end

🤔 А знаете ли вы, что...
Ruby позволяет создавать красивый и чистый код благодаря принципам DRY (Don't Repeat Yourself) и Convention over Configuration.


124
2

Ответы:

В выражении casewhen сравнивает значение со значением.

В вашем случае вы сравниваете your_message, который предположительно является строкой, с логическими значениями.

Рассмотрим очень простой пример:

irb(main):001:1* case "hello"
irb(main):002:1* when true
irb(main):003:1*   puts "foo"
irb(main):004:1* else
irb(main):005:1*   puts "bar"
irb(main):006:0> end
bar
=> nil          

Вы можете использовать регулярные выражения или сопоставлять строки.

def stupid_coach(your_message)
    case your_message
    when "I am going to work now!"
        exit
    when /\?/
        p "Silly question, get dressed and go to work!"
    else
        p "I don't care, get dressed and go to work!"
    end
end

Хотя я не уверен, что вы на самом деле хотите выйти из программы так, как это сделает ваш код, если он нажмет exit, а сам метод вернет nil.

Если вы хотите, чтобы он возвращал строки, а не печатал их:

def stupid_coach(your_message)
    case your_message
    when "I am going to work now!"
        exit
    when /\?/
        "Silly question, get dressed and go to work!"
    else
        "I don't care, get dressed and go to work!"
    end
end

Решено

Падежные выражения и триквалы

Падежное выражение работает не так, как вы думаете. Это не просто еще один способ выразить условия if/then. Под капотом case реализует оператор === для выражения при передаче аргумента case или позволяет вам использовать его как набор операторов if, если вы не передаете начальный аргумент. Он также выполняет сопоставление с образцом, но это выходит за рамки этого вопроса.

Отбрасывание аргумента в управляющее выражение

Когда вы предоставляете аргумент case, такой как case your_message, тогда каждый оператор when будет оцениваться как выражение против аргумента с использованием тройки, специфичной для класса, а не условного выражения, которое, как вы думаете, вы ему даете. Это легко исправить, передав аргумент в case. Например:

your_message = "foo?"

case
when your_message.end_with?("?")
  "bar"
end

#=> "bar"

Теперь, вместо того, чтобы эффективно произносить "foo?" === true (что, очевидно, вернет false), Ruby просто оценивает, является ли когда-утверждение правдивым или нет. Основываясь на вашем существующем коде, это, вероятно, самый простой способ получить ожидаемое поведение.

Использование Proc, Lambda или Regexp с Threequals

В качестве альтернативы вы можете реструктурировать свой оператор case, чтобы использовать процедуры, лямбда-выражения или даже регулярные выражения, которые могут соответствовать тройному сравнению, поскольку оно реализовано классом. Например, рассмотрим следующий пример, в котором используется Proc#=== для сравнения результатов лямбда.

your_message = "foo? \n"

case your_message
when ->{ _1.strip.end_with? "?" }
  "baz"
end

#=> "baz"

Это в основном передает аргумент case (например, your_message) в лямбду, где вы затем можете вызывать методы для аргумента, чтобы оценить его истинность. Это более сложный подход, но он может быть полезен.

Поскольку регулярные выражения также поддерживают метод threequals, вы можете использовать и его:

your_message = "foo?"

case your_message
when /\?$/
  "quux"
end

#=> "quux"

Другие проблемы с вашим кодом

Вы также должны учитывать такие вещи, как зависимость от порядка. В вашем примере кода, даже если бы не было других проблем, "I am going to work now!" никогда не будет сопоставляться, потому что предыдущие операторы when (если они работают) будут возвращать значение, основанное на наличии или отсутствии знака вопроса, прежде чем вы когда-либо достигли этого конкретного сравнения . Выражения case не «проваливаются», как в других языках, поэтому вам необходимо учитывать приоритет операторов так же, как и в управляющем выражении if/elsif/else/end.

Рабочий пример

Следующее намного проще и отлично работает. Он возвращает действительные результаты и не возвращает ноль. Предполагается, что your_message может #respond_to?(:strip), но вы можете изменить код, если хотите.

def stupid_coach your_message
    case your_message.strip
    when "I am going to work now!"
      "This works when checked before matching for '?'."
    when /\?$/
      "Silly question, get dressed and go to work!"
    else
      "I don't care, get dressed and go to work!"
    end 
end                                                                             

Вы можете протестировать его на массиве выборочных входных данных. Результаты кажутся мне достоверными, но вы, безусловно, можете применить их к другим значениям String, если считаете, что есть крайний случай.

["Foo?", "Bar.", "I am going to work now!"]
  .map { stupid_coach _1 }

#=> 
["Silly question, get dressed and go to work!",
 "I don't care, get dressed and go to work!",
 "This can match if checked before mactching for '?'."]

Порядок, в котором вы выполняете образцы, не имеет значения. Если вы измените порядок тестового массива, вы все равно получите правильные результаты. Пока порядок ваших операторов when правильный и вы используете правильный тип выражения для сравнений, вы можете заставить выражение case делать то, что хотите, но не обязательно так, как вы изначально ожидали.