Может быть, вы не применили достаточно аргументов к функции?

Я изучаю Haskell на курсе в университете, и есть экзамен-упражнение, где нам нужно определить функцию, которая принимает список функций [(Int ->Int)] и другой параметр типа Int и возвращает Int. Таким образом, тип должен быть

compose :: [(Int ->Int)] -> Int -> Int.

Функция должна вернуть состав функций в списке слева направо и применить его ко второму параметру. Я пробовал следующее:

compose :: [(Int -> Int)] -> Int -> Int
compose [] x = x
compose (f:fs) x  
    |fs == [] = f x
    |f  : (compose fs x)

Но компилятор выдает ошибку:

003Exam.hs:24:22:
    Couldn't match expected type ‘Int’ with actual type ‘[Int -> Int]’
    In the expression: f : (compose fs x)
    In an equation for ‘compose’:
        compose (f : fs) x
          | fs == [] = f x
          | otherwise = f : (compose fs x)

003Exam.hs:24:28:
    Couldn't match expected type ‘[Int -> Int]’ with actual type ‘Int’
    In the second argument of ‘(:)’, namely ‘(compose fs x)’
    In the expression: f : (compose fs x)

Если я оставлю последнюю строку, например:

compose :: [(Int -> Int)] -> Int -> Int
compose [] x = x
compose (f:fs) x  
    |fs == [] = f x

то я также получаю сообщение об ошибке - это то, что я действительно не понимаю:

003Exam.hs:23:13:
    No instance for (Eq (Int -> Int))
      (maybe you haven't applied enough arguments to a function?)
      arising from a use of ‘==’
    In the expression: fs == []
    In a stmt of a pattern guard for
               an equation for ‘compose’:
      fs == []
    In an equation for ‘compose’: compose (f : fs) x | fs == [] = f x

Я был бы рад за любую помощь, которая проясняет, что я делаю неправильно.


person David Jambalaya    schedule 24.02.2016    source источник


Ответы (4)


Прежде всего, (:) предназначен только для добавления элементов в начало списка (или сопоставления шаблона с ними), поэтому вам нужно заменить

f : compose fs x

с

f (compose fs x)

Далее вы должны использовать выражение типа Bool или совпадение с образцом в стороже:

| fs == []  = -- this line is still wrong, see below
| otherwise = f (compose f xs)

Однако для функций нет экземпляра Eq. Эквивалентность двух функций неразрешима (в общем случае), поэтому используйте null :: [a] -> Bool вместо (==) :: Eq a => [a] -> [a] -> Bool:

compose :: [(Int -> Int)] -> Int -> Int
compose [] x = x
compose (f:fs) x  
  | null fs   = f x
  | otherwise = f (compose fs x)

Поскольку compose [] x совпадает с x, вы даже можете убрать галочку:

compose :: [(Int -> Int)] -> Int -> Int
compose []     x = x
compose (f:fs) x = f (compose fs x)

Упражнения

  • Расширьте compose [(+1), (+2)] 1 (замените его определением), не избавляясь от промежуточных терминов. Вы замечаете закономерность?
  • Этот паттерн напоминает вам библиотечную функцию?
  • Попробуйте использовать эту библиотечную функцию, чтобы написать compose.
person Zeta    schedule 24.02.2016
comment
вау, всем большое спасибо. Я только что съел спагетти, и когда я вернулся сюда, я получил так много качественных ответов. Теперь я считаю, что понимаю, в чем проблема/была. Спасибо. (Наверное, ты имеешь в виду фолд?) :D. Спасибо вам всем - person David Jambalaya; 24.02.2016
comment
@DavidJambalaya Пожалуйста. Обратите внимание, что вы хотите принять ответ, чтобы другие знали, что Ваш вопрос был дан ответ. Вы можете изменить принятый ответ в любое время. Ааааааа я только что заметил ошибку в своем ответе. И да, это складка. Хотя не скажу какой :D. - person Zeta; 24.02.2016
comment
Что значит, какая складка? foldMap обязательно сделает это несмотря ни на что! - person dfeuer; 25.02.2016
comment
@dfeuer foldMap Endo — ловкий трюк; / для новичков это темная магия. / С fold* вы почти на одном уровне / если заменить * на l или r. - person Zeta; 25.02.2016
comment
По крайней мере, до тех пор, пока в книгах по умолчанию не будет использоваться материал, отправленный по FTP. - person Zeta; 25.02.2016
comment
@Zeta, посмотри мой ответ, чтобы узнать, как это выходит за рамки :-) - person dfeuer; 25.02.2016

|f  : (compose fs x)
    ^

Я думаю, ваша проблема в том, что этого двоеточия здесь быть не должно. : в Haskell — это конструктор списка, поэтому компилятор сообщает вам, что вы пытаетесь вернуть [Int -> Int]. Если бы его там не было, просто применялась бы f, и вы бы возвращали Int.

person obadz    schedule 24.02.2016
comment
есть еще одна проблема с fs == [] и No instance for (Eq (Int -> Int)) - person max taldykin; 24.02.2016

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

Prelude> let comp :: [(Int->Int)] -> Int -> Int
Prelude|     comp [] x = x
Prelude|     comp (f:fs) x = comp fs (f x)
Prelude|
Prelude> comp [(^2),(+1)] 3
10

Я неправильно прочитал задачу, выше применяются функции слева направо. Если вы сочиняете слева направо (сначала будет применена крайняя правая функция), это должно быть так (теперь без точек).

Prelude> let comp2 :: [(Int->Int)] -> Int -> Int
Prelude|     comp2 [] = id
Prelude|     comp2 (f:fs) = f . comp2 fs
Prelude|
Prelude> comp2 [(^2),(+1)] 3
16
Prelude> comp2 [negate,(+2)] 3
-5
person karakfa    schedule 24.02.2016
comment
Это меняет семантику исходной функции, поскольку comp [negate, (+2)] 3 в вопросе равно -5, а в вашем варианте - -1. - person Zeta; 24.02.2016

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

Во-первых, некоторый шаблон:

import Data.Monoid (Dual (..))
import Control.Category (Category (..))
import Data.Foldable (Foldable (foldMap))
import Prelude hiding (id, (.))

Далее тип, объясняющий, что моноид — это категория с ровно одним объектом:

newtype Cat c a = Cat { getCat :: c a a }

instance Category c => Monoid (Cat c a) where
  mempty = Cat id
  mappend (Cat f) (Cat g) = Cat (f . g)

Наконец, функции, которые вы ищете, обобщены от -> до произвольного Category:

compose1 :: (Foldable f, Category c) => f (c a a) -> c a a
compose1 = getCat . foldMap Cat

compose2 :: (Foldable f, Category c) => f (c a a) -> c a a
compose2 = getCat . getDual . foldMap (Dual . Cat)

Упражнение для читателя: напишите эквивалентные функции, заменив Category на Semigroupoid и Foldable на Foldable1.

Еще одно упражнение для читателя: напишите экземпляр Monoid (Cat c a), используя Data.Coerce.coerce и/или #. и .# из Data.Profunctor.Unsafe, чтобы избежать ложного выделения замыкания.


P.S. Тип Cat объясняет только половину истории. Вот остальное:

newtype Mon m a b = Mon m

instance Monoid m => Category (Mon m) where
  id = Mon mempty
  Mon m . Mon n = Mon (m `mappend` n)

Возможно, было бы более точным использовать

data Mon m a b where
  Mon :: m -> Mon m a a

Но это было бы менее эффективно, не добавляя реальной ценности.

person dfeuer    schedule 24.02.2016
comment
Ваш ответ совершенно не соответствует действительности, / хотя жаль, что на это ушло много времени. / Пожалуйста, не рассматривайте это как критику / но ваш ответ не по теме. Я не знаю, как далеко они заходят в лекции Дэвида, но, учитывая синтаксические ошибки, я не думаю, что они обработали Foldable или Monoid. Также, учитывая, что вы не отвечаете на первоначальный вопрос, вы используете орбитальную пушку, чтобы стрелять в дерево, но слегка промахиваетесь. Ведь compose1 = foldr (.) id и compose2 = foldr (flip (.)) idControl.Category). - person Zeta; 25.02.2016
comment
Ну, не на 100% не по теме, вы предоставляете рабочий compose, но вы понимаете, что я имею в виду (и рифма на критику была действительно хороша, хотя понятия не имею, почему я рифмую в последнее время). - person Zeta; 25.02.2016
comment
@Zeta, это не совсем так, поскольку, скажем, бесконечный список snoc может дать результат в некоторых категориях с использованием моего кода, но никогда с использованием foldr. - person dfeuer; 25.02.2016
comment
Конечно, наверное, следовало добавить в контексте этого вопроса. Для того, как я могу сложить эндоморфизмы в Foldable в один, ваш ответ в значительной степени точен. (Разве тебе не нужно время от времени спать?) - person Zeta; 25.02.2016
comment
@Zeta, я стараюсь, но я не очень хорошо ложусь спать в разумные часы. - person dfeuer; 25.02.2016