«случай»странной последовательности выполнения в haskell

#haskell

#haskell

Вопрос:

Я пишу функцию для фильтрации первого заданного значения из списка в Haskell.

Я опробовал функцию, основанную на функции filter, но я не знаю, как выполнялась функция.

Поэтому я добавляю трассировку к каждому случаю.

 filter' :: (a -> Bool) -> [a] -> [a]
filter' p (x:xs) | p x       = trace "{1}" x : (filter' p xs)
                 | otherwise = trace "{2}" filter' p xs
filter' p [] = []


filterFirst :: (a->Bool) -> [a]->[a]
filterFirst p xs = case (trace "{3}" (filter' p xs)) of 
  (x : xs) -> trace "{4}" [x]
  otherwise -> trace "{5}" []
  

при тестировании:

filterFirst even [2,2] оказывается

 {3}
{4}
[{1}
2]
  

filterFirst even [1,2] оказывается

 {3}
{2}
{4}
[{1}
2]
  

filterFirst even [1,1,2] оказывается

 {3}
{2}
{2}
{4}
[{1}
2]
  

Результат [2] правильный, но последовательность выполнения является проводной.

  1. Почему {4} выполняется раньше {1}?
  2. {3}, похоже, выполняется только один раз, почему case of знает, когда тестировать (x : xs) ?
  3. Как case of выполняется точно?

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

1. Если вы попытаетесь скомпилировать этот код, вы получите предупреждение о том, что otherwise это неиспользуемая переменная. Вы имеете в виду _ .

Ответ №1:

 trace "{1}" x : (filter' p xs)
  

разбирается как

 (trace "{1}" x) : (filter' p xs)
  

Аналогично,

 trace "{2}" filter' p xs
  

разбирается как

 (trace "{2}" filter') p xs
  

Если вы хотите другую интерпретацию, вам нужно написать trace "{1}" (x : (filter' p xs)) и trace "{2}" (filter' p xs) соответственно.


Я не уверен в вашем вопросе № 3. Зачем {3} выполнять несколько раз? Это не часть цикла / рекурсии.

Давайте пройдемся по оценке

 filterFirst even [2,2]
  

Интерпретатор хочет напечатать результат, поэтому мы должны вычислить результат.

Мы вводим определение filterFirst (внешняя часть выражения всегда вычисляется первой), выдавая

 case (trace "{3}" (filter' p xs)) of 
  (x : xs) -> trace "{4}" [x]
  otherwise -> trace "{5}" []

where
    p = even
    xs = [2,2]
  

Здесь нам нужно принять решение. case необходимо знать, какую ветвь оценивать, поэтому ему нужно знать, какой шаблон соответствует ( x : xs или otherwise ). Итак, первое, что он делает, это оценивает выражение, которое мы анализируем:

 trace "{3}" (filter' p xs)

where
    p = even
    xs = [2,2]
  

Здесь самым внешним выражением является вызов trace , который теперь печатается {3} и передается

 filter' p xs

where
    p = even
    xs = [2,2]
  

Теперь самым внешним выражением является вызов filter' , который определяется как

 filter' p (x:xs) | p x       = trace "{1}" x : (filter' p xs)
                 | otherwise = trace "{2}" filter' p xs
filter' p [] = []
  

И снова мы должны принять решение: какое уравнение мы используем? Выбор зависит от значения второго аргумента, поэтому это запускает вычисление (самого внешнего уровня) xs = [2,2] . Ну, здесь ничего интересного не происходит, потому что [2,2] уже полностью оценена.

[2,2] является синтаксическим сахаром для 2 : (2 : []) , так что это соответствует первому уравнению, связывающему

 p = even
x = 2
xs = 2 : []  -- same as [2]
  

Теперь нам нужно оценить защиту, стоящую за шаблоном:

 p x
even 2
True
  

Успех! Нашим возвращаемым значением является trace "{1}" x : (filter' p xs) , которое (как упоминалось выше) является применением (:) к двум аргументам, trace "{1}" x и filter' p xs .

Дальнейшая оценка здесь не выполняется. Напомним, что мы все еще пытаемся оценить

 case ... of 
  (x : xs) -> trace "{4}" [x]
  otherwise -> trace "{5}" []
  

итак, нас волнует только, является ли самый внешний конструктор списка : или [] , и мы знаем, что это : сейчас. Мы выбираем первую ветвь, которая выдает

 trace "{4}" [x]

where
    x = trace "{1}" x_inner
    xs = filter' p xs_inner
    x_inner = 2
    xs_inner = 2 : []
    p = even
  

Это немного сбивает с толку, потому что вы назвали все свои переменные x и xs . Если мы встроим все переменные, значения которых мы уже знаем, привязки уменьшатся до:

 trace "{4}" [x]

where
    x = trace "{1}" 2
    xs = filter' even (2 : [])
  

xs не используется вообще и x используется только один раз, так что это действительно эквивалентно

 trace "{4}" [trace "{1}" 2]
  

Оценка этого сначала выводит {4} (из trace ), затем выдает

 [trace "{1}" 2]
-- syntactic sugar for
(trace "{1}" 2) : []
  

Здесь начинает работать код печати списка: у нас есть список, состоящий по крайней мере из одного элемента, поэтому он выводит [ (начало списка). Чтобы напечатать сам первый элемент, нам нужно его оценить:

 trace "{1}" 2
  

Это, наконец, печатает {1} и выдает 2 результат, который позволяет выводить код для печати списка 2 и ] (для конца списка).

И это все!

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

1. Большое спасибо. У меня более глубокий вопрос. Для анализа выше, 1. почему filter’ возвращается обратно к «случаю» после выполнения {1}, а не рекурсивно вызывает «(filter’ p xs)» в «x: (filter’ p xs)»? 2. Кто приводит к этому возврату? Сам «(x: xs) -> «Случай или фильтр»? На этот раз мы анализируем шаг «filterFirst even [1,2]»: после печати «{3}» программа переходит в filter’. Затем мы выбираем вторую защиту. Затем выполняется и печатается «trace «{2}» filter'»{2}», Затем выполняется «filter’ p xs». На данный момент, почему программа не возвращается обратно к «case of» и не выбирает второй вариант?

2. @zichaoliu 1. filter' не возвращается обратно в case / of после печати {1} . На самом деле, {1} печатается последним, намного позже, чем filter' и case выполнены. Я не понимаю этого вопроса. 2. Какой результат? Ситуации, которую вы описали в # 1, не существует.

3. @zichaoliu case может выбрать ветвь только после того, как узнает, какой шаблон соответствует. Для этого он должен вычислять выражение до тех пор, пока не сможет определить результат каждого сопоставления с шаблоном. Например, чтобы проверить, x : xs совпадает ли, выражение должно вычисляться до тех пор, пока не будет известен самый внешний конструктор (который для списков является либо : , либо [] ).

4. Моя ошибка, filter’ не выполняет {1} перед возвратом обратно. На самом деле я хочу спросить так: у меня есть две возможности для перехода к неквалифицированному значению, но я не знаю, какая из них правильная. Первая возможность: когда фильтр «выберите вторую защиту, filter» не возвращается. Таким образом, filter’ сохраняет рекурсивное выполнение. Другая возможность, когда filter’ выбирает вторую защиту, filter’ также возвращается. Но «случай»выполнения «трассировки «{2}» filter’ p xs» выполняется. Итак, программа снова возвращается к filter’. Какая возможность верна?

Ответ №2:

Я не все проверил, но обратите внимание, что

 trace "{1}" x : (filter' p xs)
  

означает

 (trace "{1}" x) : (filter' p xs)
  

таким образом, {1} будет напечатано при доступе к содержимому первого элемента, а не при его простом создании. Я думаю, вы хотели

 trace "{1}" (x : (filter' p xs))
  

Я бы также переписал {2} строку как

 trace "{2}" (filter' p xs)
  

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

1. Моя ошибка, я изначально так и думал.