이와 같은 폴더 정의를 작성하려면 어떤 지식이나 훈련이 필요합니까? [닫은]


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
하스켈은 언어입니다. 그것은 많은 단어를 가지고 있으며, 그 단어의 대부분은 다양한 다른 방식으로 사용될 수 있습니다. 새로운 언어를 배울 때 수년간 많은 문장과 숙어가 의미가 없습니다. 그러나 더 많이 사용할수록 친숙한 패턴을 볼 수 있으며, 한때 협박하고 발전했다고 생각한 것들이 자연스럽게 나옵니다. 편하게 하다.
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 구성의 사용을 기대하지 않았는지 여부를 알아 내려고 노력했습니다. 이 답변은 정말 내 의심을 해결합니다.
Theodora

천만에요. :)
Will Ness

4

그것을 이해하는 가장 좋은 방법은 그것을하는 것입니다. 아래는 대신에 foldlM사용 하는 구현 foldlfoldr있습니다. 그것은 좋은 운동이며, 시도하고 나중에 제안 할 솔루션으로옵니다. 이 예제에서는 달성하기 위해 수행 한 모든 추론을 설명합니다. 이는 여러분과 다를 수 있으며 이미 함수 누산기 사용에 대해 알고 있었기 때문에 편견 일 수 있습니다.

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 단계에서와 같이 컴파일되지 않으므로 OKf>>= 라고 생각할 수 있습니다. 적용하고 싶지 않습니다. 그런 계산을 잡고로 작성하십시오 . 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. 

initFunc2 단계 (재귀 적 정의)의 지식 유형 과 사용을 통해이를 추론 할 수 있습니다 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

보시다시피 그렇게하기가 어렵지 않습니다. 그것은 연습이 필요하지만, 나는 전문적인 하스켈 개발자가 아니며 스스로 할 수 있습니다, 그것은 연습의 문제입니다


1
나는 여기에서 오른쪽 배보다 왼쪽 배를 이해하기 쉽게 만드는 것을 알지 못합니다. 오른쪽 접기는 무한 구조에 유용한 결과를 생성하고 일반적인 Monad경우에 효율적일 가능성이 훨씬 높습니다 .
dfeuer

@dfeuer 이것의 요점은 더 쉬운 예를 보여주기위한 것이 아니라, OP를위한 적절한 연습을 제안하고 해결책에 대한 합리적인 논증을 드러내고 그런 해결책. 효율성에 대한
차이점

3

와 같은 함수를 작성하기 위해 수학에 대한 특정 지식이 필요하지 않습니다 foldM. 저는 이미 4 년 동안 프로덕션에서 Haskell을 사용하고 있으며이 정의를 이해하는 데 어려움을 겪고 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(인수의 모양을 알 수 있음)

이러한 기능을 본 후에는 적도 추론 기법 을 수행 하여 정의를 단계별로 확장하고 작동 방식을 확인할 수 있습니다. 이러한 기능을 갖춘 능력은 경험과 함께 제공됩니다. 나는 강력한 수학자 능력이 없으며이 기능은 전형적인 하스켈 기능이 아닙니다. 그러나 연습이 많을수록 더 좋습니다. :)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.