Які знання чи навчання потрібні, щоб хтось записав таке визначення foldlM? [зачинено]


9

Останнім часом я намагаюся використовувати Haskell в якійсь моїй реальній системі виробництва справ. Система типу Haskell дійсно пропонує мені велику допомогу. Наприклад, коли я зрозумів, що мені потрібна певна функція типу

f :: (Foldable t, Monad m) => ( a-> b -> m b) -> b -> t a -> m b

Є на насправді такі функції , як foldM, foldlMі foldrM.

Однак мене справді шокувало визначення таких функцій, як:

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldr f' return xs z0
  where f' x k z = f z x >>= k

тому функція f'повинна бути типу:

f' :: a -> b -> b

як цього вимагає foldr, тоді він bповинен бути добрим *-> m *, так що все визначення foldlMможе мати сенс.

Інший приклад включає визначення liftA2та<*>

(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)

Я спробував деякі власні рішення, перш ніж зазирнути у вихідний код. Але розрив настільки великий, що я не думаю, що я коли-небудь міг придумати це рішення, незалежно від того, скільки рядків коду я напишу в майбутньому.

Отже, моє запитання полягає в тому, які знання або яка конкретна галузь математики необхідні, щоб хтось міркував на такому високо абстрагованому рівні.

Я знаю, що теорія категорій може запропонувати певну допомогу, і я давно стежу за цією чудовою лекцією і все ще працюю над нею.


13
Haskell - це мова. У ньому багато слів, і більшість цих слів можна вживати різними способами. Коли ви вивчаєте нову мову, багато років речення та ідіоми не мають сенсу. Але чим більше ти його використовуєш, тим більше ти бачиш знайомі зразки, і речі, про які ти колись думав, залякують і розвиваються, приходять цілком природно. Розслабтесь.
luqui

Відповіді:


3

Загалом, логіка і т. Д., Я б міг собі уявити. Але ви також можете дізнатися це, зробивши це. :) З часом помічаєш деякі зразки, підбирай кілька хитрощів.

Як це foldrз додатковою аргументацією. Деякі вважають це складанням у функції, тому їх можна комбінувати через .і id(що іноді є справді <=<і return),

foldr g z xs  =  foldr ($) z . map g $ xs
              =  foldr (.) id (map g xs) z
         ~/=  foldr (<=<) return (map g xs) z
{-
  (\f -> f . f) :: (a -> a) -> (a -> a)

  (\f -> f <=< f) :: Monad m => (a -> m a) -> (a -> m a)
                            (still just a type, not a kind)
-}

Деяким легше зрозуміти це у більш простих, синтаксичних термінах, як

foldr g z [a,b,c,...,n] s =
     g a (foldr g z [b,c,...,n]) s

тому, коли gвін не суворий у своєму другому аргументі, sможе слугувати станом, що передається зліва, навіть якщо ми згортаємо праворуч, як один із прикладів.


1
Дуже дякую, я намагався з’ясувати, чи унікальне це визначення, і не очікував тут використання композиції Kleisli. Ця відповідь справді вирішує мої сумніви.
Теодора

Ласкаво просимо. :)
Буде Несс

4

Тож найкращий спосіб зрозуміти це, зробивши це. Нижче є реалізація foldlMвикористання foldlзамість foldr. Це хороша вправа, спробуйте і пізніше піти до рішення, яке я запропонував би. На прикладі пояснюються всі міркування, які я зробив для його досягнення, які можуть відрізнятися від ваших, і можуть бути упередженими, оскільки я вже знав про використання акумулятора функцій.

Крок 1 : Спробуємо написати foldlMв термінахfoldl

-- this doesn't compile because f returning type is (m b) and not just (b) 
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f z0 xs 

-- So let substitute f by some undefined f'
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
  where f' = undefined

-- cool, but f' should use f somehow in order to get the monadic behaviour
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
  where f' b a = f somethingIDontkNow 

Тут ви розумієте, що f'це чисто і вам потрібно буде отримати результат, fщоб набрати відповідність. Єдиний спосіб "витягнути" монадичне значення - це >>=оператор, але такий оператор потрібно завернути відразу після його використання.

Отже, як висновок: кожного разу, коли ви закінчитесь, я хотів би повністю розмотати цю монаду , просто відмовтеся. Це не правильний шлях

Крок 2 : Давайте спробуємо записати foldlMв термінах, foldlале спочатку використовувати []як складний, оскільки це легко узгоджувати візерунок (тобто насправді нам не потрібно використовувати fold)

-- This is not very hard. It is pretty standard recursion schema. :)
foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 []     = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs

Гаразд, це було легко. Дозвольте порівняти визначення зі звичайним foldlвизначенням для списків

foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 []     = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs

myfoldl :: (b -> a -> b) -> b -> [a] -> b
myfoldl f z0 []     = z0
myfoldl f z0 (x:xs) = foldl f (f z0 x) xs

Класно !! вони майже однакові. Тривіальний випадок - це приблизно те саме. Рекурсивний випадок трохи по- іншому, ви хотіли б написати що - щось подібне: foldlM' f (f z0 x) xs. Але це не компілюється, як на кроці 1, так що ви можете подумати нормально, я не хочу застосовувати f, просто провести таке обчислення і скласти його >>=. Я хотів би написати щось більше на кшталт, foldlM' f (f z0 x >>=) xs якби це мало сенс ...

Крок 3 Зрозумійте, що те, що ви хочете накопичити, - це функціональна композиція, а не результат. ( тут я, мабуть, упереджений тим, що я вже знав це, тому що ви його розмістили ).

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' initFunc xs
  where initFunc = undefined :: b -> m b
        f'       = undefined :: (b -> m b) -> a -> (b -> m b) -- This type signature can be deduce because f' should be applied to initFunc and a's from t a. 

За типом initFuncта використовуючи наші знання з кроку 2 (рекурсивне визначення) ми можемо зробити це висновком initFunc = return. Визначення f'можна доповнити, знаючи, що f'слід використовувати fі >>=.

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' return xs z0
--                        ^^^^^^
--                        |- Initial value
  where f' b a = \bvalue -> b bvalue >>= \bresult -> f bresult a -- this is equivalent to (b >=> \result -> f result a) which captures the sequence behaviour of the implementation
--         ^      ^^^^^^                  ^^^^^^^
--         |      |                       |- This is the result of previous computation
--         |      |- f' should return a function b -> m b. Any time you have to return a function, start writing a lambda  
--         |- This b is the accumulated value and has type b -> m b
-- Following the types you can write this with enough practise

Як бачите, зробити це не так складно. Це потрібна практика, але я не є професійним розробником haskell, і я міг би це зробити сам, це питання практики


1
Я насправді не бачу, що робить ліву складку легшою для розуміння, ніж праву складку. Права складка набагато частіше дає корисний результат для нескінченних структур та буде ефективною для типових Monadвипадків.
dfeuer

@dfeuer Сенс у цьому не в тому, щоб показати простіший приклад, а запропонувати відповідну вправу для ОП та викласти аргументовану аргументацію рішення, намагаючись довести, що не потрібно бути супер-майстром haskeller для того, щоб отримати таке рішення. Відступи щодо ефективності не враховуються
lsmor

3

Для написання такої функції вам не потрібні певні знання з математики foldM. Я використовую Haskell у виробництві вже 4 роки, і я також борюся з розумінням цього визначення foldM. Але це здебільшого тому, що це погано написано. Будь ласка, не сприймайте це як особисту провину, якщо ви не можете зрозуміти якийсь незрозумілий код. Ось більш читаема версіяfoldlM

foldlM
    :: forall t m a b .
       (Foldable t, Monad m)
    => (b -> a -> m b)  -- ^ Monadic action
    -> b                -- ^ Starting accumulator
    -> t a              -- ^ List of values
    -> m b              -- ^ Computation result inside a monad
foldlM f z xs = (foldr step pure xs) z
  where
    step :: a -> (b -> m b) -> b -> m b
    step cur next acc = do
        result <- f acc cur
        next result

Ця функція все ще не найпростіша. Переважно тому, що він має нетипове використання, foldrде функція проміжного акумулятора є функцією. Але ви можете побачити декількох дівчат, які роблять таке визначення більш зрозумілим:

  1. Коментарі щодо аргументів функції.
  2. Кращі назви аргументів (все ж короткі та ідіоматичні, але вони принаймні більш читабельні).
  3. Явна підпис типу функції всередині where(щоб ви знали форму аргументів).

Побачивши таку функцію, тепер ви можете виконати техніку Equational argumenting, щоб розширити визначення крок за кроком і подивитися, як воно працює. Можливість придумати такі функції походить із досвідом. У мене немає сильних навичок математика, і ця функція не є типовою функцією Haskell. Але чим більше у вас практики, тим краще вона стає :)

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