Haskell vs. TypeScript vs. Python 코드 비교하기

Haskell vs. TypeScript vs. Python 코드 비교하기

세 가지프로그래밍 언어가 어떻게 소인수분해(factorization in prime factors) 문제를 푸는지 비교해봅니다.

하스켈 코드를 기준으로 타입스크립트와 파이썬에도 비슷한 코딩 스타일로 문제를 풀어봤습니다.

Haskell

하스켈은 강력한 정적 타이핑과 타입 검사기를 갖고 있는 순수 함수형 언어입니다. 강력한 타입 검사기 덕분에 컴파일에 앞서 타입 오류를 미리 식별할 수 있게 되죠. 그러면 오류가 코드에 들어가는 것을 막을 수 있습니다.

또한 하스켈은 if-then-else 같은 표현식을 지원할 뿐만 아니라 함수형 언어 대부분이 갖고 있는 가드(guard) 절을 사용할 수 있습니다. 가드는 경우에 따라 어떻게 식(expression)을 평가해야 하는지 명확하게 알려줍니다.

module Main
    where
divides :: Int -> Int -> Bool
divides n d =
    mod d n == 0
ldf :: Int -> Int -> Int
ldf k n | divides k n = k
        | k^2 > n = n
        | otherwise = ldf (k+1) n
ld :: Int -> Int
ld = ldf 2
factors :: Int -> [Int]
factors n | n < 1     = error "not a positive integer"
          | n == 1    = []
          | otherwise = p : factors (div n p) where p = ld n
main :: IO ()
main = print $ factors 6
// [2,3]

TypeScript

타입스크립트은 여러 가지 양식으로 타입을 선언할 수있습니다. 그 가운데 하스켈과 비슷한 코딩 양식을 따른 방법과 일반적으로 타입을 선언하는 방법으로 두 가지를 이용해봤습니다.

Type Signature Style

함수 타입을 명시적으로 제시하는 방식을 호출 시그니처(call signature) 또는 타입 시그니처(type signature)라고 합니다. 타입 별칭에 사용하는 type 키워드를 사용합니다.

type Divides = (n: number, d: number) => boolean
const divides: Divides = (n, d) => d % n == 0
type LDF = (k: number, n: number) => number
const ldf: LDF = (k, n) => {
  if (divides(k, n))
    return k
  else if (k ** 2 > n)
    return n
  else
    return ldf(k + 1, n)
}
type LD = (n: number) => number
const ld: LD = (n) => ldf(2, n)
type Factors = (n: number) => Array<number>
const factors: Factors = (n) => {
  const p = ld(n)
  if (n < 1)
    throw new Error('not a positive integer')
  else if (n === 1)
    return []
  else
    return [p].concat(factors(Math.floor(n / p)))
}
console.log(factors(6))
// [ 2, 3 ]

Normal Style

일반적으로 타입스크립트 코드를 작성할 때 많이 사용하는 양식입니다.

const divides = (n: number, d: number): boolean => d % n == 0
const ldf = (k: number, n: number): number => {
  if (divides(k, n))
    return k
  else if (k ** 2 > n)
    return n
  else
    return ldf(k + 1, n)
}
const ld = (n: number): number => ldf(2, n)
const factors = (n: number): Array<number> => {
  const p = ld(n)
  if (n < 1)
    throw new Error('not a positive integer')
  else if (n === 1)
    return []
  else
    return [p].concat(factors(Math.floor(n / p)))
}
console.log(factors(6))
// [ 2, 3 ]

Python

파이썬에서 값이 어떤 타입인지 알려주는 타입 힌트(type hint) 구문을 지원합니다. 하지만 Python 런타임은 함수와 변수 타입 힌트(또는 주석)을 적용하지 않아요. 타입 검사기, IDE, 린터 따위같은 타사 도구에서 활용할 수는 있답니다. docs.python.org/3/library/typing.html

이 타입 힌트 기능은 컴파일 타임에 검사를 하지않아 타입 오류를 찾을 수 없어요. 그래서 직접 실행해봐야 코드에 오류가 있는지 확인할 수 있습니다.

파이썬에는 가드(guard) 구문이 현재(버전 3.10.2) 없습니다. 그래서 if-elif-else 구문으로 처리했습니다.

def divides(n: int, d: int) -> bool:
    return d % n == 0


def ldf(k: int, n: int) -> int:
    if divides(k, n):
        return k
    elif k ** 2 > n:
        return n
    else:
        return ldf(k + 1, n)


def ld(n: int) -> int:
    return ldf(2, n)


def factors(n: int) -> list[int]:
    p = ld(n)
    if n < 1:
        raise Exception("not a positive integer")
    elif n == 1:
        return []
    else:
        temp = factors(n//p)
        temp.insert(0, p)
        return temp


if __name__ == "__main__":
    print(f'{factors(6)}')
// [2, 3]

참고한 페이지