2018년 8월 17일 금요일

[haskell] 함수를 이용한 개발-03

출처 : 하스켈로 배우는 함수형 프로그래밍

함수를 이용한 개발

- 알맞는 부분을 만들거나 알맞은 부품만 맞추는 능력


아래 소스는 책에 있는 내용을 거의 그대로 베낀 것이다.
한가지 바뀐 점은 타입을 생성하고 사용하지 않는 부분이 있어서 그 부분만 치환했으나 중요한 부분은 아니다.
-- Coord.hs
-- $runghc Coord.hs

import Text.Printf

-- 좌표의 타입
type Coord = (Double, Double)

-- 좌표 변환 설정
data Config = Config { rotAt :: Coord            -- 회전 중심 좌표
                     , theta :: Double           -- 회전량[라디안]
                     , ofs   :: (Double, Double) -- 병행 이동량
                     }

-- 좌표 변환 함수의 타입은 "좌표를 별도의 좌표로 이동"하는 함수
-- "좌표를 받아서 좌표를 뱉는 함수"타입
type CoordConverter = Coord -> Coord

-- 병행 이동의 프리미티브
trans :: Coord -> CoordConverter
trans (dx, dy) = \(x, y) -> (x+dx, y+dy)

--회전의 프리미티브
rotate :: Double -> CoordConverter
rotate t = \(x, y) -> (cos t * x - sin t * y, sin t * x + cos t * y)

-- 설정을 베이스로 한 병행 이동
transByConfig :: Config -> CoordConverter
transByConfig config = trans (ofs config)

-- 설정을 베이스로 한 회전
rotateByconfig :: Config -> CoordConverter
rotateByconfig config = postTrans . rotate (theta config) . preTrans 
    where rotateAt = rotAt config
          preTrans = trans(rotate pi $ rotateAt)
          postTrans = trans rotateAt

convertByConfig :: Config -> CoordConverter
convertByConfig config = transByConfig config . rotateByconfig config

main :: IO ()
main = do
  let config = Config { rotAt = (0.5, 0.5), theta = pi / 4, ofs = (-0.5, -0.5)}
  let unitRect = [(0,0),(0,1),(1,1),(1,0)]
  
  let convertedRect = map (convertByConfig config) unitRect
  mapM_ (uncurry $ printf "(%.6f, %.6f)\n") convertedRect
  -- mapM_ is useful for executing something only for its side effects.
  -- https://stackoverflow.com/questions/27609062/what-is-the-difference-between-mapm-and-mapm-in-haskell

하스켈에서는 (.) 을 이용해서 함수를 합성한다.
Prelude> let a = (*2) . length
Prelude> a [1,2,3,4]
8
이 형태는 수학적인 표기법과 아주 비슷하다.

또한 javascript에서 compose는 일단 함수끼리 합성을 시켰을 때, 제대로된 타입들로 합성이 된건지 알 수가 없다.
정말 g의 치역과 f의 정의역이 연결되는지 (g의 리턴타입과 f의 인풋타입이 잘 맞는지) 알 수가 없다.
실행 시에 함수를 만들 수 있으므로 실행 시에 만든 함수들끼리 합성할 수 있는지의 여부를 테스트 등을 통해서 직접 봐야 알 수 있다.

한편 Haskell은 타입 검사가 자체적으로 존재하므로 합성 가능 여부가 타입에 따라 결정된다.

여기서 중요하게 봐야 할 곳은 CoordConverter 같다. CoorConverter는 Coord를 받아서 Coord를 리턴하는 함수를 말한다.
CoordConverter타입인 함수들은 서로 결합이 쉽다.
왜냐하면 Coord가 정의역이고 Coord가 치역이기 때문에 서로 잘 맞는다. (합성을 해도 무난하다)

하스켈에서 uncurry는 curry된 녀석들을 다시 풀어버린다고 생각하면 된다.
(즉 내용을 한 번에 받는 함수가 된다. 하지만 하스켈은 기본적으로 curried function 이기 때문에 튜플로 한번에 받게 된다.)
curry는 그 반대이다. 함수를 일단 커리하여 받을 수 있는 함수로 바꿔버린다.
Prelude> curry fst 1 2
1
Prelude> :t curry
curry :: ((a, b) -> c) -> a -> b -> c
Prelude> :t fst
fst :: (a, b) -> a
Prelude> :t curry fst
curry fst :: c -> b -> c
Prelude> curry fst
error!!
Prelude> curry fst 1 2
1

하스켈에서 uncurry는 curry된 녀석들을 다시 풀어버린다고 생각하면 된다.
Prelude> :t uncurry
uncurry :: (a -> b -> c) -> (a, b) -> c
Prelude> :t (+)
(+) :: Num a => a -> a -> a
Prelude> :t uncurry (+)
uncurry (+) :: Num c => (c, c) -> c
Prelude> (+) 1 2
3
Prelude> uncurry 1 2
error!!
Prelude> uncurry (+) (1, 2)
3

댓글 없음:

댓글 쓰기