it-swarm.com.ru

сделать запись и связать подпись

Я новичок в Haskell и в функциональном программировании, и мне было интересно, почему работает такой пример («вложенный цикл»):

do
  a <- [1, 2, 3]
  b <- [4, 5, 6]
  return $ a * 10 + b

Некоторые вещи ниже являются своего рода псевдо-Haskell синтаксисом, но я надеюсь, что это иллюстрирует мое понимание.

Насколько я понимаю, это превратилось в нечто подобное

[1, 2, 3] >>= \a -> 
          ([4, 5, 6] >>= \b -> 
                     return $ b * 10 + a)

Я думаю, что это выражение

[4, 5, 6] >>= \b -> return $ b * 10 + a

Производит список частично примененных функций

[[40 + a], [50 + a], [60 + a]]

Объединено с

[40 + a, 50 + a, 60 + a]

На последнем этапе что-то выглядит так

[1, 2, 3] >>= \a -> [40 + a, 50 + a, 60 + a]

Становится

[41, 51, 61, 42, 52, ... ]

Моя дилемма в том, что тип return $ b * 10 + a, похоже, отличается от типа [40 + a, 50 + a, 60 + a].

Разве подпись не должна быть такой?

 (>>=)  :: m a -> (a -> m b) -> m b

В этом примере, похоже,  

[int] -> (int -> [int -> int -> int]) -> [int -> int]

А также  

[int] -> (int -> [int -> int]) -> [int]
6
jack malkovick

Вот как это работает и работает оперативно. Вы правы, что это:

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])
  ]

Тогда мы можем уменьшить внутренние maps:

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], применяет каждую функцию в своем левом операнде к каждому значению в своем правый операнд.

2
Jon Purdy

Я думаю, причина этого сбивает с толку, потому что вы работаете над этим наизнанку, пытаясь думать о внутренней привязке как о создании списка частично примененных функций. Он не: a и b закрываются, а не аргументы, ожидающие применения. Вместо этого начните с внешней стороны выражения и работайте внутрь:

[1, 2, 3] >>= \a -> (...)

Для каждого элемента в списке создайте список каким-либо образом с доступом к a в качестве имени элемента в исходном списке.

... [4, 5, 6] >>= \b -> (...)

Чтобы создать список, необходимый для предыдущего шага, создайте новый список с доступом к a и b, по одному из каждого из двух нумерованных списков.

... return $ b * 10 + a

Чтобы создать список, необходимый для предыдущего шага, создайте список из одного элемента со значением b * 10 + a.

Вы спрашиваете, почему тип return $ b * 10 + a отличается от типа [40 + a, 50 + a, 60 + a], но это не так: оба типа [Int]. Ни один не включает никаких функций. Скорее, они оба представляют собой списки чисел, построенные путем обращения к уже замкнутым переменным. И действительно, (>>=) имеет именно тот тип, который должен: он берет список int и функцию для создания списка int из одного int и возвращает другой список int:

(>>=) :: [Int] -> (Int -> [Int]) -> [Int]
3
amalloy

Имейте в виду, что return x = [x] и xs >>= f = concatMap f xs в списке монада. таким образом

[1, 2, 3] >>= \a -> 
      ([4, 5, 6] >>= \b -> 
                 return $ b * 10 + a)

превращается в

concatMap (\a -> (concatMap (\b -> [b*10+a]) [4,5,6])) [1,2,3]

который становится (с a в качестве свободной переменной в функции b)

concatMap (\a -> [4*10+a, 5*10+a, 6*10+a]) [1,2,3]

Не существует частично примененных функций, только одна функция, которая возвращает значение списка, используя его аргумент 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]
1
chepner