Выполнение предложения where в Haskell

#haskell #where-clause

#haskell #where-предложение

Вопрос:

Я получил следующий фрагмент кода, который, как я знаю, работает, но я совершенно новичок в Haskell и получил 2 вопроса о предложении where.

 f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status --- Base Case
f3 ([p1,p2]:tail) status
 | status !! (p1-1) == 0  = f3 tail status  --- Case 1
 | status !! (p2-1) == 1  = f3 tail newStatus1 --- Case 2
 | otherwise = f3 tail newStatus2 --- Case 3
  where newStatus1 = set status p1 0                    --- Line 7
        newStatus2 = set newStatus2Temp p1 1            --- Line 8
        newStatus2Temp = set status p2 0                --- Line 9
 

Таким образом, в основном предикат f3 имеет 2 аргумента :

  • Список списков целых чисел, таких как : [[1,2],[2,3],[3,2]]
  • Список целых чисел

Его вывод является последним обновленным вторым аргументом.

Как вы видите, помимо базового варианта, я получил 2 случая (2) и (3), где аргумент status/[Int] используется через стандартный предикат set.

Вопрос 1) :

  • Допустим, случай 2 имеет значение true. Выполняет ли Haskell строки 8 и 9 ?
  • Допустим, случай 3 имеет значение true. Выполняет ли Haskell строку 7?

Вопрос 2) :

  • Может ли у guard быть свой собственный where?
  • Есть ли лучший способ сделать это на самом деле?

Комментарии:

1. Это домашнее задание?

2. @WillemVanOnsem Фактическим домашним заданием было «перевести» предикат f3 из Prolog в Haskell. Именно так я и придумал, и мне было интересно, не экономит ли то, как я это делаю, время или что-то в этом роде. Кстати, мне приходится постоянно переносить аргумент status и изменять его, но мой вопрос в основном касается временного воздействия предложения where.

Ответ №1:

В результате отложенной оценки код в каждой из строк 7-9 выполняется только в том случае, если значение соответствующей привязки вычисляется / используется в ходе оценки кода для случая, который соответствует. Итак:

  • Если случай 1 имеет значение true, то ни одна из строк 7-9 не выполняется.
  • Если случай 1 имеет значение false, но случай 2 имеет значение true, то вычисление newStatus выполняется в строке 7, но строки 8-9 не выполняются.
  • Если случаи 1 и 2 являются ложными, но случай 3 является истинным, то оценка newStatus2 запускает строку 8, которая вычисляет newStatus2Temp , вызывая выполнение строки 9. Строка 7 не выполняется.

Сами where предложения могут быть присоединены только ко всем привязкам шаблонов (например, ко всему f3 ([p1,p2]:tail) status | ... | ... = ... выражению), а не к отдельным защитным элементам, поэтому у защитного элемента не может быть своего собственного where предложения. Вы можете либо повторить шаблон для каждого защитника:

 f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status
f3 ([p1,p2]:tail) status | status !! (p1-1) == 0  = f3 tail status
f3 ([p1,p2]:tail) status | status !! (p2-1) == 1  = f3 tail newStatus1
  where newStatus1 = set status p1 0
f3 ([p1,p2]:tail) status | otherwise              = f3 tail newStatus2
  where newStatus2 = set newStatus2Temp p1 1
        newStatus2Temp = set status p2 0
 

или использовать let ... in ... блоки:

 f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status
f3 ([p1,p2]:tail) status
 | status !! (p1-1) == 0  = f3 tail status
 | status !! (p2-1) == 1
  = let newStatus1 = set status p1 0
    in f3 tail newStatus1
 | otherwise
  = let newStatus2 = set newStatus2Temp p1 1
        newStatus2Temp = set status p2 0
    in f3 tail newStatus2
 

Я не думаю, что с вашей where версией -clause что-то не так, и нет ничего необычного в написании кода на Haskell, где используется только подмножество привязок в where -clause (или даже допустимых / значимых) для каждого случая. С такими маленькими помощниками этот конкретный пример может быть более четко написан без каких-либо помощников, хотя:

 f3 :: [[Int]] -> [Int] -> [Int]
f3 [] status = status
f3 ([p1,p2]:tail) status
 | status !! (p1-1) == 0  = f3 tail $ status
 | status !! (p2-1) == 1  = f3 tail $ set status p1 0
 | otherwise              = f3 tail $ set (set status p2 0) p1 1
 

С помощью GHC и -O2 все четыре из них (ваш исходный код и эти три варианта) компилируются в идентичный низкоуровневый код, поэтому используйте тот, который вы считаете наиболее понятным.