F# 해시(Hashing)
F# 해시(Hashing)
⚠️ 확인 필요 — 이 문서의 원래 대상 페이지
https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/values/hashing은 현재 404(페이지 없음)로 확인되었어요. 아카이브 스냅샷과dotnet/docs·dotnet/fsharp저장소 트리 어디에도 이 주소의 문서가 존재하지 않아서, F# 공식 문서의 실제 해시 자료인hash연산자(Microsoft.FSharp.Core.Operators) 설명을 바탕으로 이 장을 구성했어요. 원문은 아래 출처를 참고해 주세요.
F#에서 어떤 값을 해시로 바꿔야 하는 일이 생기면, 대부분 hash 함수 하나면 충분해요. 이 함수는 말 그대로 '값을 해시 코드(int)로 바꿔 주는' 범용 해시 함수인데요, F#이 추구하는 구조적(structural) 접근이 그대로 담겨 있어요. 이번 장에서는 hash가 어떻게 동작하는지, 그리고 해시 코드를 다룰 때 놓치기 쉬운 부분까지 하나씩 살펴볼게요.
출처
- Microsoft Learn – Operators.hash<'T> Function (F#) (Previous Versions): https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/ee353770(v=vs.100)
- F# Core Docs – Operators (hash): https://fsharp.github.io/fsharp-core-docs/reference/fsharp-core-operators.html#hash
- 네임스페이스/모듈 경로:
Microsoft.FSharp.Core.Operators· 어셈블리:FSharp.Core(FSharp.Core.dll)
본문
hash 함수란
hash는 = 연산자로 같다고 판정되는 값들이 서로 같은 해시 값을 돌려주도록 설계된 범용 해시 함수예요. 기본적으로 F#의 공용체(union), 레코드(record), 튜플(tuple) 타입에 대해서는 **구조적 해시(structural hashing)**를 사용해서 타입의 내용물 전체를 해시해요.
// 시그니처:
hash : 'T -> int (requires equality)
// 사용법:
hash obj
- 매개변수
obj : 'T— 해시를 만들 입력 객체예요. - 반환값 — 계산된 해시 코드(
int)예요.
이 함수는 컴파일된 어셈블리에서는 Hash라는 이름으로 존재해요. F#이 아닌 다른 언어에서 접근하거나 리플렉션으로 접근할 때는 이 이름을 써야 해요.
타입별로 동작을 조정하고 싶다면, 각 타입에
GetHashCode를 구현해서 해시 동작을 바꿀 수 있어요. 구조적 해시가 기본값이고,GetHashCode구현이 개별 타입의 동작을 덮어쓰는 방식이에요.
실제 사용 예시
hash가 서로 다른 데이터 타입에 대해 어떤 해시를 만들어 내는지 한번 볼게요. show라는 함수가 값을 받아서 %A 형식(pretty-print)과 해시 값을 함께 출력해요.
let show a = printfn "hash(%A) : %d" a (hash a)
show 1;
show 2;
show "1"
show "2"
show "abb"
show "aBc" // 대소문자 구분
show None;
show (Some 1);
show (Some 0);
show [1;2;3];
show [1;2;3;4;5;6;7;8];
show [1;2;3;4;5;6;7;8;9;10;11];
show [1;2;3;4;5;6;7;8;9;10;11;12;13;14;15]
출력은 이렇게 나와요.
hash(1) : 1
hash(2) : 2
hash("1") : -842352753
hash("2") : -842352754
hash("abb") : 2103075711
hash("aBc") : 539088922
hash(<null>) : 0
hash(Some 1) : -1640531462
hash(Some 0) : -1640531463
hash([1; 2; 3]) : 1956583134
hash([1; 2; 3; 4; 5; 6; 7; 8]) : 922428386
hash([1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11]) : 1771492728
hash([1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15]) : -926589492
여기서 확인할 것들
- 정수는 자기 자신 그대로 —
hash(1)은1,hash(2)는2예요. 작은 정수는 해시 코드가 곧 값 자체인 경우가 많아요. - 문자열은 값에 따라 달라져요 —
"1"과"2"는 서로 다른 해시를 갖고,"abb"와"aBc"도 달라요. 대소문자를 구분한다는 점에 주의하세요. <null>은0— 기본적으로 null 값의 해시는 0이에요.- 구조가 같으면 해시도 같아요 —
Some 1과Some 0은 값이 달라서 해시도 다르죠. 반면[1;2;3]처럼 내용이 같은 리스트는 그 구조 전체가 해시의 재료가 돼요. - 해시 코드는 실행마다 달라질 수 있어요 — 아주 중요한 부분인데요,
hash가 돌려주는 숫자는 해시 기반 컬렉션의 빠른 조회를 위한 값이에요. 영구적인 식별자로 쓰거나 데이터베이스·파일에 저장해서 재사용하면 안 돼요. 프로세스나 실행 환경이 달라지면 값이 바뀔 수 있어요. - 같은 해시 ≠ 같은 객체 — 서로 같다고 판정되는 객체는 해시가 같지만, 그 반대는 성립하지 않아요. 다른 객체가 우연히 같은 해시를 가질 수 있어요(해시 충돌). 객체의 동등성 판정이 필요한 곳에서는 해시 코드를 비교하는 대신
=연산자나Equals를 써야 해요.
해시를 커스터마이즈하고 싶다면
F#의 hash는 구조적 해시를 기본으로 하지만, 특정 타입의 해시 동작을 직접 정하고 싶으면 그 타입에 GetHashCode 멤버를 구현하면 돼요. 다만 GetHashCode를 오버라이드할 때는 반드시 Equals도 함께 오버라이드해야 해요. 둘 중 하나만 바꾸면 해시 기반 컬렉션(예: Dictionary, Hashtable)이 제대로 동작하지 않을 수 있어요.
더 알아보기
- Operators.hash<'T> Function (F#)
- System.Object.GetHashCode method (.NET)
- F# Core Docs – Operators Module
- F#의 동등성·비교 제약(equality and comparison constraints)과 구조적 비교에 대한 자세한 내용은 Equality and Comparison Constraints in F# (dsyme 블로그) 를 참고해요.