-
Notifications
You must be signed in to change notification settings - Fork 5
타입으로 견고하게 다형성으로 유연하게 3주차 - 하업서 #469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
hemil0102
merged 1 commit into
main
from
463-타입으로-견고하게-다형성으로-유연하게-3장-총-73페이지-2025-02-07-Harry
Mar 7, 2025
The head ref may contain hidden characters: "463-\uD0C0\uC785\uC73C\uB85C-\uACAC\uACE0\uD558\uAC8C-\uB2E4\uD615\uC131\uC73C\uB85C-\uC720\uC5F0\uD558\uAC8C-3\uC7A5-\uCD1D-73\uD398\uC774\uC9C0-2025-02-07-Harry"
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
149 changes: 149 additions & 0 deletions
149
2025/RobustWithTypeFlexibleWithPolymorphism/hemil0102/3장.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
# 3장 매개변수에 의한 다형성 | ||
|
||
이쯤해서 다시 한번 짚어보는 다형성이란? | ||
프로그램 언어 각 요소들(상수, 변수, 식, 객체, 메소드 등)이 | ||
다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킨다. | ||
|
||
어떤 타입을 사용할지 미리 알려줘야한다. 즉 타입을 매개변수처럼 입력을 받는다. | ||
|
||
### 3.1 제네릭 함수 | ||
매개변수에 의한 다형성은 타입 매개변수를 통해 다형성을 만드는 기능으로, 제네릭스라고도 부른다. | ||
|
||
Any와 제네릭의 차이? | ||
Any는 아무 타입이나 될 수 있기에 타입이 결정되지 않는 불확실함으로 컴파일 이전에 타입 유추를 할 수 없다. | ||
타입 유추를 할 수 없기에 어떤 타입에서 활용할 기능을 미리 정의할 수 없다. | ||
|
||
반면 제네릭은 어떤 타입이든 될 수 있는 것은 같지만, 미리 타입을 지정하므로써 해당 타입의 기능을 가져다 쓸 수 있다. | ||
|
||
T가 타입 매개변수일 때 함수 안에서 T타입의 부품은 아무 특별한 능력도 요구되지 않는 곳에만 사용될 수 있다. | ||
아무 특별한 능력을 요구하지 않는다는 말은 타입에 따라서 연산을 할 수 없는 행위들을 말한다. | ||
|
||
### 힌들리-밀너 타입 추론 | ||
제네릭 함수를 정의할 때 추론을 한다. | ||
|
||
### 3.2 제네릭 타입 | ||
타입 매개변수를 추가할 수 있는 곳은 함수뿐이 아니다. 타입에 타입 매개변수를 추가하면 제네릭 타입이 된다. | ||
|
||
```swift | ||
struct IntBox { | ||
var value: Int | ||
} | ||
|
||
struct StringBox { | ||
var value: String | ||
} | ||
|
||
위와 같은 비슷한 구조 타입들에 제네릭을 적용한다. | ||
|
||
struct Box<T> { | ||
var value: T | ||
} | ||
|
||
let intBox = Box(value: 42) | ||
let stringBox = Box(value: "Hi") | ||
``` | ||
|
||
제네릭 타입은 Json 디코더에서 사용해본 기억이 있음 | ||
|
||
### 3.3 무엇이든 타입 | ||
제네릭 함수의 타입, 제네릭 함수를 값(매개변수)로 사용하면 그 타입이 무엇이든 타입이 되고, 무엇이든 타입의 값을 사용할 때는 제네릭 함수를 사용하듯이 하면 된다. | ||
|
||
```swift | ||
func identity<T>(_ value: T) -> T { | ||
return value | ||
} | ||
|
||
let intFunction: (Int) -> Int = identity | ||
let stringFunction: (String) -> String = identity | ||
|
||
print(intFunction(10)) // 10 | ||
print(stringFunction("Hello")) // Hello | ||
|
||
func apply<T>(_ function: (T) -> T, to value: T) -> T { | ||
return function(value) | ||
} | ||
|
||
print(apply(identity, to: 100)) // 100 | ||
print(apply(identity, to: "hello")) // hello | ||
``` | ||
|
||
```swift | ||
protocol ApplyFunction { | ||
func apply<T>(_ function: (T) -> T, to value: T) -> T | ||
} | ||
|
||
class ApplyOne: ApplyFunction { | ||
func apply<T>(_ function: (T) -> T, to value: T) -> T { | ||
let transformedValue = function(value) | ||
|
||
if let number = transformedValue as? Int { | ||
return (number + 1) as! T | ||
} else if let text = transformedValue as? String { | ||
return (text + "!") as! T | ||
} else { | ||
print("Unsupported type: \(T.self)") | ||
return transformedValue | ||
} | ||
} | ||
} | ||
|
||
class ApplyTwo: ApplyFunction { | ||
func apply<T>(_ function: (T) -> T, to value: T) -> T { | ||
let transformedValue = function(value) | ||
|
||
if let number = transformedValue as? Int { | ||
return (number + 2) as! T | ||
} else if let text = transformedValue as? String { | ||
return (text + "!!") as! T | ||
} else { | ||
print("Unsupported type: \(T.self)") | ||
return transformedValue | ||
} | ||
} | ||
} | ||
|
||
func simulate(r: ApplyFunction) { | ||
print(r.apply(identity, to: 100)) | ||
print(r.apply(identity, to: "hello")) | ||
} | ||
|
||
let applyOne = ApplyOne() | ||
let applyTwo = ApplyTwo() | ||
|
||
simulate(r: applyOne) // 101, hello! | ||
simulate(r: applyTwo) // 102, hello!! | ||
``` | ||
|
||
### 3.4 무엇인가 타입 | ||
|
||
```swift | ||
protocol Timestamp { | ||
associatedtype T | ||
func initValue() -> T | ||
func next(_ t: T) -> T | ||
func cmp(_ t1: T, _ t2: T) -> Bool | ||
} | ||
|
||
class Timestamper: Timestamp { | ||
typealias T = Int | ||
|
||
func initValue() -> Int { return 0 } | ||
func next(_ t: Int) -> Int { return t + 1 } | ||
func cmp(_ t1: Int, _ t2: Int) -> Bool { return t1 < t2 } | ||
} | ||
|
||
func create() -> some Timestamp { | ||
return Timestamper() | ||
} | ||
|
||
let ts = create() // some Timestamp 타입이 반환되며 Timestamper()는 숨겨진다. | ||
let t0 = ts.initValue() // let t1: (some Timestamp).T | ||
let t1 = ts.next(t0) // let t1: (some Timestamp).T | ||
|
||
print(t1) // 1 | ||
print(ts.cmp(t0, t1)) // true | ||
``` | ||
|
||
### 논제 ### | ||
1. 힌들리-밀너 타입 추론은 하스캘과 오캐멀 언어 예제만 책에 소개되었는데. 하당 타입 추론 방식을 다른 언어에서는 잘 활용하지 않는 것으로 보인다. 그렇다면 그 이유는 무엇일까? | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그렇다기 보다는 책의 예제가 하스켈, 오캐멀일 뿐이고
제네릭을 사용하는 모든 프로그래밍 언어라면 힌들리-밀러 타입 추론이 가능합니다.
이미 업서님이 swift로 올려준 제네릭 코드들의 예제들이 모드 타입 추론이 적용된 코드들을 올려 주셨습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
음 타입 추론이 된다는 것은 알고 있었는데...
제가 이해한 힌들리-밀너는 아래 코드처럼 함수를 작성할 때 매개변수에 타입을 명시하지 않아도(제네릭을 명시하지 않아도)
내부 코드와 인자에 따라 타입 추론한다는 것으로 이해했어요. 이게 힌들리-밀러가 아닌가요 'ㅅ'?
func name(v1, v2) { v1 * v2 } 이면 v1 = 1, v2 = 2를 넣으면 숫자니 타입에러 없이 값을 연산하는 것으로 이해했는데요.

뭔가 잘못 이해한 부분이 있는 것 같아서 여쭤봅니다 @.@;;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제네릭 함수를 쓰려면 우선 아래와 같이 정의를 해야 합니다.
그러면 원래 제네릭 함수를 Int 타입으로 사용한다면 아래와 같이 써야 하는데,
하지만 실제 컴파일 해보면 명시적으로 Int를 지정할 수 없다고 에러가 나옵니다.
하지만 타입 추론이 적용되므로 아래와 같이 사용하는 거죠
그리고 책에 힌들리-밀너 타입 추론의 각주에 다음과 같은 설명이 있습니다.
따라서 저는 제네릭 함수로 타입 추론이 되는 것 역시 힌들리-밀너 추론이라고 했던 겁니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
얘기를 들어보고 자료를 찾아보니 힌들리-밀너 타입 추론이 포괄하는 범위가 조금 넓은 것 같습니다. 책에서 설명하는 부분은 아래와 같은데 조금은 이야기하는 방향이 다른 느낌이 듭니다.
저는 아래와 같은 상황으로 이해를 하고 있는 상황인데요.
하스켈 (완전한 타입 추론 가능)
함수 설계, 타입 추론은 제네릭 명시하지 않아도 제네릭일 경우 제네릭으로 타입 추론
함수 호출, 타입 추론 가능
Swift (일부만 타입 추론 가능)
함수 설계, 타입 추론은 제네릭을 명시해야만 가능
함수 호출, 타입 추론 가능
아래는 직접 코드를 작성해본 것인데,
하스켈은 제네릭을 명시 안해도 함수 설계가 가능한데,
Swift는 불가능합니다.
다른 언어는 되는데 Swift만 이런 것일까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 읽은 미디엄 글에 따르면,
함수형 언어는 기본적으로 타입 추론 알고리즘을 통해 정적 타입을 구현하는데, 일반적으로 힌들리 밀너 타입 시스템을 사용한다고 나와있는데요
각 언어 마다 설계 철학이 다르기 때문에, 함수형 언어가 아니라면, 굳이 위 개념을 도입할 이유가 없기 때문이 아닐까 라고 생각�했습니다 꼭 힌들리 밀너 타입 시스템(책과 다르게 엄밀한 의미라고 할 때,)을 쓰지 않더라도 제네릭을 해당 언어에서 사용하게 할 수 있는 방법은 다양하기 때문이고, 힌들리 밀너 타입 시스템 처럼 극단적으로 타입추론을 제공해야한 마땅한 이유가 없기 때문이기도 할 것 같습니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AI의 답변을 첨부합니다..
힌들리–밀너(Hindley–Milner, HM) 타입 추론 알고리즘은 순수 함수형 언어(예: ML, Haskell)에서 매우 효과적으로 동작하는 강력한 타입 추론 시스템입니다. 그러나 객체지향 언어에서는 HM 타입 추론을 완전히 채택하기 어려운데, 그 이유는 주로 다음과 같습니다.
서브타이핑(Subtyping)과 다형성의 복잡성
함수형 언어에서는 주로 **전역적인 파라메트릭 다형성(parametric polymorphism)**을 사용하며, 이 경우 HM 알고리즘이 “주요 타입(principal type)”을 보장하면서 완전한 타입 추론을 할 수 있습니다. 반면, 객체지향 언어는 클래스 계층 구조, 상속, 인터페이스, 오버라이딩 등 서브타이핑과 관련된 복잡한 개념을 포함합니다.
서브타이핑은 타입 간의 포함 관계를 도입하는데, 이는 HM 알고리즘이 전제로 하는 단순한 파라메트릭 다형성과는 상충됩니다.
서브타이핑이 포함되면 타입 추론이 모호해지거나 결정 불가능한 경우가 발생할 수 있습니다.
가변 상태와 부작용
함수형 언어는 순수함수를 지향하여 부작용이 없거나 최소화된 환경에서 타입 추론을 수행할 수 있습니다. 그러나 객체지향 언어는 객체의 상태 변경(가변 상태)과 부작용을 허용하는 경우가 많습니다.
가변 상태는 타입 추론을 복잡하게 만들어, HM 알고리즘이 적용되기 어려운 상황을 만듭니다.
동적 바인딩과 런타임 특성
객체지향 언어에서는 런타임에 결정되는 동적 바인딩이나 다형적 호출 등의 특성이 있습니다. 이는 컴파일 타임에 완전한 타입 정보를 추론하기 어렵게 만듭니다.
언어 설계 목표와 타협
객체지향 언어들은 보통 명시적인 타입 표기(explicit typing)를 통해 개발자가 코드의 의미를 명확하게 표현하도록 설계되었습니다. 이는 타입 추론의 범위를 제한하는 동시에, 코드의 가독성과 유지보수성을 향상시키려는 의도도 있습니다.
예를 들어, Java나 C#에서는 제한적인 형태의 타입 추론(예: Java의 var 또는 C#의 var)만 지원하며, 이는 HM 알고리즘처럼 전역적이고 완전한 추론은 아닙니다.
요약하면,
순수 함수형 언어는 부작용이 적고, 파라메트릭 다형성이 주된 특징이기 때문에 HM 타입 추론이 잘 맞고 효과적으로 적용됩니다.
객체지향 언어는 서브타이핑, 가변 상태, 동적 바인딩 등 복잡한 개념들이 포함되어 있어 HM 알고리즘이 요구하는 제약(예, 주된 다형성과 순수성)을 만족시키지 못하며, 이로 인해 완전한 HM 타입 추론을 구현하기 어렵습니다.
이러한 이유들로 인해 함수형 언어에서는 힌들리–밀너 타입 추론이 널리 사용되는 반면, 객체지향 언어에서는 그 대신 명시적인 타입 표기나 부분적인 타입 추론 기법(또는 전혀 다른 타입 시스템)을 채택하게 됩니다.