[ Architecture ] .NET Polyglot: 객체지향 C#과 함수형 F#의 공존 전략

Lutica_·2025년 11월 22일

1. 서론: 왜 굳이 두 언어를 섞는가?

  • C#은 충분히 훌륭한 언어다. 객체지향(OOP)을 기반으로 하며, Unity 엔진의 주력 언어로서 UI 처리나 상태 관리(State Management)에 최적화되어 있다.
  • 하지만 데이터 검증, 복잡한 수식 계산, 불변성(Immutability)이 중요한 비즈니스 로직을 다룰 때는 가끔 C#의 장황한 문법이 피로하게 느껴질 때가 있다.
  • 이때 눈을 돌린 것이 F#이다.
  • F#은 .NET 위에서 돌아가는 함수형 프로그래밍(FP) 언어로, 간결한 문법과 강력한 타입 추론, 그리고 기본적으로 불변성을 보장한다.

"UI와 상태는 C#에게, 순수 로직과 계산은 F#에게."

이 글은 소위 "Functional Core, Imperative Shell (함수형 코어, 명령형 껍데기)" 패턴을 .NET 생태계에서 실현하기 위한 아키텍처링 기록이다.


2. 기술적 난관: .csproj와 .fsproj의 동거

  • 가장 먼저 부딪힌 문제는 프로젝트 구조였다. C# 파일(.cs)과 F# 파일(.fs)은 컴파일러가 다르기 때문에 하나의 프로젝트 파일(csproj) 안에 공존할 수 없다.
  • 하지만 다행히도, .NET의 설계 철학이 여기서 빛을 발한다.
  • C#과 F#은 문법은 다르지만, 결국 컴파일되면 동일한 IL (Intermediate Language) 코드로 변환되어 CLR (Common Language Runtime) 위에서 실행된다.
  • 즉, 물리적인 프로젝트(Assembly)만 분리한다면, 논리적으로는 하나의 프로그램처럼 동작할 수 있다는 뜻이다. 나는 이를 이용해 솔루션을 다음과 같이 분리했다.
    • Core.Logic (F# Project): 순수 계산 로직, 알고리즘, 데이터 검증
    • Client.Unity (C# Project): UI, 입력 처리, Unity 생명주기 관리

3. 연결 고리: Interface를 통한 의존성 역전

  • 프로젝트를 나눴다고 끝이 아니다. C#에서 F#의 함수를, 혹은 F#에서 C#의 객체를 어떻게 호출할 것인가? 단순히 참조를 걸면 강한 결합(Tight Coupling) 이 발생하고, F# 특유의 타입 시스템이 C# 쪽에서 깨질 위험이 있다.

여기서 필요한 것이 인터페이스(Interface)를 활용한 계약(Contract) 이다.

설계 전략

  1. Define (정의): 공통 라이브러리 혹은 C# 쪽에 Interface를 정의한다. (예: ICalculator)
  2. Implement (구현): F# 프로젝트에서 이 인터페이스를 상속받아 구체적인 로직을 구현한다. F#의 type은 .NET 클래스로 컴파일되므로 C# 인터페이스를 완벽하게 구현할 수 있다.
  3. Inject (주입): C#에서는 구체적인 F# 구현체를 몰라도 된다. 단지 ICalculator 인터페이스만 바라보고, DI(Dependency Injection) 컨테이너나 팩토리를 통해 F# 인스턴스를 주입받는다.

이렇게 하면 C#은 "누가 계산하는지" 알 필요 없이 결과만 받아쓰면 되고, F#은 "누가 호출하는지" 신경 쓰지 않고 로직에만 집중할 수 있다.


4. Unity에서의 적용 (Practical Application)

  • "이론은 알겠는데, 이게 게임 엔진인 Unity에서도 돌아가나?"

    결론부터 말하면 "가능하다".

  • Unity 역시 Mono/IL2CPP 기반의 .NET 런타임을 사용하므로, F#으로 빌드된 .dll (어셈블리)을 Unity 프로젝트의 Plugins 폴더에 넣기만 하면 된다.

    • F# 프로젝트 설정: UnityEngine.dll을 참조로 추가하여 Unity API에 접근 가능하게 만든다.
    • 빌드 파이프라인: F# 코드가 수정될 때마다 자동으로 빌드되어 Unity 폴더로 복사되도록 스크립트를 짜두면(Post-build event), 마치 C# 스크립트를 고치듯 매끄러운 개발 경험(DX)을 얻을 수 있다.

5. 결론: 적재적소의 미학

  • .NET 생태계는 생각보다 훨씬 유연하다.
  • C# 하나만 고집할 필요도, 무리하게 모든 것을 F#으로 바꿀 필요도 없다.
    • C#: 사용자와 상호작용하는 최전선(Front)
    • F#: 데이터를 가공하고 검증하는 후방(Core)
  • 만약 당신이 복잡한 상태 관리와 스파게티 코드에 지쳐 있다면, F#이라는 "새로운 도구" 를 C# 프로젝트에 살짝 얹어보는 것은 어떨까? 생각보다 그 맛이 꽤 훌륭하다.
profile
해보고 싶고, 하고 싶은 걸 하는 사람

0개의 댓글