Як "concatMap" з монопрохідної здатний "витягнути" загальний аргумент?


9

Я навчаюсь Haskell і робив просту програму з насінням БД для Yesod, коли натрапив на таку поведінку, яку мені важко зрозуміти:

testFn :: Int -> Bool -> [Int]
testFn a b = if b then replicate 10 a else []

Сесія GHCI в Yesod:

$ :t concatMap testFn [3]
concatMap testFn [3] :: Bool -> [Int]
$ (concatMap testFn [1,2,3]) True
[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3]

Якось вдалося «витягнути» той другий «Бул» з кожного зі відображень у єдиний викривлений аргумент.

Стандартний базовий сеанс Prelude GHCI відмовляється навіть складати цей вираз:

$ :t concatMap testFn [3]
error:
     Couldn't match type 'Bool -> [Int]' with '[b]'
      Expected type: Int -> [b]
        Actual type: Int -> Bool -> [Int]
     Probable cause: 'testFn' is applied to too few arguments
      In the first argument of 'concatMap', namely 'testFn'
      In the expression: concatMap testFn [3]

Виявляється, Yesod використовує однодорожну бібліотеку, яка має власну concatMap:

$ :t concatMap
concatMap
  :: (MonoFoldable mono, Monoid m) =>
     (Element mono -> m) -> mono -> m

На моєму нинішньому рівні розуміння Haskell я не міг зрозуміти, як тут розподіляються типи. Чи міг би хтось пояснити мені (наскільки орієнтований на початківців), як робиться цей трюк? Яка частина зазначеного testFnвище відповідає Element monoтипу?

Відповіді:


6

Почнемо з перерахування відомих нам типів. (Ми робимо вигляд, що цифри Intдля простоти - це насправді не актуально.)

testFn :: Int -> Bool -> [Int]
[1,2,3] :: [Int]
True :: Bool

(concatMap testFn [1,2,3]) Trueтакий же, як concatMap testFn [1,2,3] True, тому concatMapповинен мати тип, що відповідає всім цим аргументам:

concatMap :: (Int -> Bool -> [Int]) -> [Int] -> Bool -> ???

де ???тип результату. Зауважте, що через правила ->асоціативності асоціюється праворуч, тому наведене вище введення те саме:

concatMap :: (Int -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)

Запишемо загальний тип над цим. Я додаю кілька пробілів для позначення подібності.

concatMap :: (MonoFoldable mono, Monoid m) =>
             (Element mono -> m              ) -> mono  -> m
concatMap :: (Int          -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)

А-ха! У нас є відповідність, якщо ми обираємо mяк Bool -> [Int]і monoяк [Int]. Якщо ми це робимо, ми задовольняємо обмеження MonoFoldable mono, Monoid m(див. Нижче), і ми також маємо Element mono ~ Int, тому все перевіряємо.

Ми припускаємо , що ???це [Int]з визначення m.

Про обмеження: бо MonoFoldable [Int]мало що сказати. [Int]явно список подібного типу з Intтипом елемента, і цього достатньо , щоб зробити це в MonaFoldableс Intяк його Element.

Бо Monoid (Bool -> [Int])це трохи складніше. Ми маємо на A -> Bувазі, що будь-який тип функції є моноїдом, якщо Bє моноїдом. З цього випливає операція точково. У нашому конкретному випадку ми покладаємось на [Int]те, щоб бути моноїдом і отримуємо:

mempty :: Bool -> [Int]
mempty = \_ -> []

(<>) :: (Bool -> [Int]) -> (Bool -> [Int]) -> (Bool -> [Int])
f <> g = \b -> f b ++ g b

1
Чудове пояснення! Те, що ви пояснили та вирівняли, - це саме те, що я хотів побачити, дякую!
dimsuz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.