함수를 이용한 개발
- 알맞는 부분을 만들거나 알맞은 부품만 맞추는 능력
아래 소스는 책에 있는 내용을 거의 그대로 베낀 것이다.
한가지 바뀐 점은 타입을 생성하고 사용하지 않는 부분이 있어서 그 부분만 치환했으나 중요한 부분은 아니다.
-- 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
댓글 없음:
댓글 쓰기