Вот как это работает и работает.Вы правы, что это:
do
a <- [1, 2, 3]
b <- [4, 5, 6]
return $ a * 10 + b
Desugars к этому:
[1, 2, 3] >>= \a ->
[4, 5, 6] >>= \b ->
return $ b * 10 + a
Который, в свою очередь, использует экземпляр списка Monad
, чьи определения >>=
и return
(или pure
) мы можем встроить:
concatMap
(\a -> concatMap
(\b -> [b * 10 + a])
[4, 5, 6])
[1, 2, 3]
Мы можем разбить concatMap
на concat
и map
:
concat
(map
(\a -> concat
(map
(\b -> [b * 10 + a])
[4, 5, 6]))
[1, 2, 3])
Теперь мы можем уменьшитьэто, и я думаю, что именно здесь вы столкнулись с трудностями: сокращение происходит извне и не дает частично примененных функций в этом случае;скорее, захватывает a
в закрытии внутренней лямбды (\b -> …)
.Сначала мы отображаем (\a -> …)
поверх [1, 2, 3]
:
concat
[ (\a -> concat
(map
(\b -> [b * 10 + a])
[4, 5, 6])) 1
, (\a -> concat
(map
(\b -> [b * 10 + a])
[4, 5, 6])) 2
, (\a -> concat
(map
(\b -> [b * 10 + a])
[4, 5, 6])) 3
]
==
concat
[ let a = 1
in concat
(map
(\b -> [b * 10 + a])
[4, 5, 6])
, let a = 2
in concat
(map
(\b -> [b * 10 + a])
[4, 5, 6])
, let a = 3
in concat
(map
(\b -> [b * 10 + a])
[4, 5, 6])
]
Затем мы можем уменьшить внутренние map
s:
concat
[ let a = 1
in concat
[ (\b -> [b * 10 + a]) 4
, (\b -> [b * 10 + a]) 5
, (\b -> [b * 10 + a]) 6
]
, let a = 2
in concat
[ (\b -> [b * 10 + a]) 4
, (\b -> [b * 10 + a]) 5
, (\b -> [b * 10 + a]) 6
]
, let a = 3
in concat
[ (\b -> [b * 10 + a]) 4
, (\b -> [b * 10 + a]) 5
, (\b -> [b * 10 + a]) 6
]
]
==
concat
[ let a = 1
in concat
[ let b = 4 in [b * 10 + a]
, let b = 5 in [b * 10 + a]
, let b = 6 in [b * 10 + a]
]
, let a = 2
in concat
[ let b = 4 in [b * 10 + a]
, let b = 5 in [b * 10 + a]
, let b = 6 in [b * 10 + a]
]
, let a = 3
in concat
[ let b = 4 in [b * 10 + a]
, let b = 5 in [b * 10 + a]
, let b = 6 in [b * 10 + a]
]
]
, которые мы можем затем упростить, заменив переменные наих значения:
concat
[ concat
[ [4 * 10 + 1]
, [5 * 10 + 1]
, [6 * 10 + 1]
]
, concat
[ [4 * 10 + 2]
, [5 * 10 + 2]
, [6 * 10 + 2]
]
, concat
[ [4 * 10 + 3]
, [5 * 10 + 3]
, [6 * 10 + 3]
]
]
и сокращение вызовов до concat
:
concat
[ [ 4 * 10 + 1
, 5 * 10 + 1
, 6 * 10 + 1
]
, [ 4 * 10 + 2
, 5 * 10 + 2
, 6 * 10 + 2
]
, [ 4 * 10 + 3
, 5 * 10 + 3
, 6 * 10 + 3
]
]
==
[ 4 * 10 + 1
, 5 * 10 + 1
, 6 * 10 + 1
, 4 * 10 + 2
, 5 * 10 + 2
, 6 * 10 + 2
, 4 * 10 + 3
, 5 * 10 + 3
, 6 * 10 + 3
]
И, конечно, отдельные выражения:
[ 41, 51, 61
, 42, 52, 62
, 43, 53, 63
]
Случай, когда вы будет видеть список частично примененных функций при использовании экземпляра Applicative
списков, например, эквивалент вашего кода:
(\a b -> b * 10 + a) <$> [1, 2, 3] <*> [4, 5, 6]
Определение <$>
/fmap
для списков - просто map
, поэтому мы частично применяем первый аргумент лямбда-выражения, получая список типа [Int -> Int]
, затем (<*>) :: (Applicative f) => f (a -> b) -> f a -> f b
, здесь, в типе [Int -> Int] -> [Int] -> [Int]
, применяет каждую функцию в своем левом операндекаждому значению в правом операнде.