우석
연습문제 10.1
data class Logger<T>(val value: T, val log: List<String>) {
fun <U> transform(f: (T) -> U): Logger<U> = Logger(f(value), log)
fun <U> bind(f: (T) -> Logger<U>): Logger<U> = Logger(newValue, log + newLog)
}
@Test
fun testBind() {
val f: (Int) -> Logger<Int> = { value -> Logger(value * 2, listOf("multiplied by 2")) }
val g: (Int) -> Logger<Int> = { value -> Logger(value + 1, listOf("increased by 1")) }
val m = Logger(2, emptyList())
// Left identity
expectThat(m.bind(f)).isEqualTo(f(2))
// Right identity
val pure = { value: Int -> Logger(value, emptyList()) }
expectThat(m.bind(pure)).isEqualTo(m)
// Associativity
expectThat((m.bind(f)).bind(g)).isEqualTo(m.bind { x -> (f(x).bind(g)) })
}
@Test
fun testTransform() {
val f: (Int) -> Int = { value -> value * 2 }
val g: (Int) -> Int = { value -> value * 3 }
val m = Logger(2, emptyList())
// Left identity
expectThat(m.transform(f).value).isEqualTo(f(2))
// Right identity
val pure: (Int) -> Int = { value -> value * 1 }
expectThat(m.transform(pure)).isEqualTo(m)
// Associativity
expectThat((m.transform(f)).transform(g)).isEqualTo(m.transform(g).transform(f))
}
연습문제 10.2
class SystemConsole : ConsoleContext {
override fun printLine(msg: String): String = msg.also { println(msg)}
val reader = BufferedReader(InputStreamReader(System.`in`))
override fun readLine(): String = reader.readLine()
}
연습문제 10.3
fun main() {
consoleRpnCalculator().runWith(SystemConsole())
}
tailrec fun consoleRpnCalculator(): ContextReader<ConsoleContext, String> =
contextPrintln("Write an RPN expression to calculate or Q to quit.")
.bind { _ -> contextReadln() }
.bind { input ->
if (input == "Q")
contextPrintln("Bye!")
else
contextPrintln("The result is: ${RpnCalc.calc(input)}")
}
.bind { msg ->
if (msg == "Bye!")
contextPrintln("")
else
consoleRpnCalculator()
}
만혁
연습문제 10.1
data class Logger<T>(val value: T, val log: List<String>) {
companion object {
fun <T> pure(value: T): Logger<T> = Logger(value, emptyList())
}
fun <U> transform(f: (T) -> U): Logger<U> {
return Logger(f(value), log)
}
fun <U> bind(f: (T) -> Logger<U>): Logger<U> {
val nextLogger = f(value)
return Logger(nextLogger.value, log + nextLogger.log)
}
}
class LoggerMonadTest {
@Test
fun `Logger Monad - Left Identity Law`() {
val x = 10
val f: (Int) -> Logger<Int> = { a -> Logger(a * 2, listOf("Doubled $a")) }
val result1 = Logger.pure(x).bind(f)
val result2 = f(x)
assertEquals(result2, result1)
}
@Test
fun `Logger Monad - Right Identity Law`() {
val m = Logger(5, listOf("Initial value"))
val pureF: (Int) -> Logger<Int> = { a -> Logger.pure(a) }
val result1 = m.bind(pureF)
val result2 = m
assertEquals(result2, result1)
}
@Test
fun `Logger Monad - Associativity Law`() {
val m = Logger(3, listOf("Start"))
val f: (Int) -> Logger<Int> = { a -> Logger(a + 1, listOf("Added 1 to $a")) }
val g: (Int) -> Logger<String> = { a -> Logger("Value is $a", listOf("Formatted $a")) }
val result1 = m.bind(f).bind(g)
val result2 = m.bind { x -> f(x).bind(g) }
assertEquals(result2, result1)
}
@Test
fun `Logger usage example`() {
val process: Logger<Int> = Logger.pure(5)
.bind { value -> Logger(value * 2, listOf("Multiplied by 2")) }
.bind { value -> Logger(value + 1, listOf("Added 1")) }
.bind { value -> Logger(value / 3, listOf("Divided by 3")) }
println("Final Value: ${process.value}")
println("Log: ${process.log}")
assertEquals(3, process.value)
assertEquals(listOf("Multiplied by 2", "Added 1", "Divided by 3"), process.log)
}
}
연습문제 10.2
data class ContextReader<CTX, T>(val runWith: (CTX) -> T) {
fun <U> transform(f: (T) -> U): ContextReader<CTX, U> =
ContextReader { ctx -> f(runWith(ctx)) }
fun <U> bind(f: (T) -> ContextReader<CTX, U>): ContextReader<CTX, U> =
ContextReader { ctx -> f(runWith(ctx)).runWith(ctx) }
}
interface ConsoleContext {
fun println(msg: String): Unit
fun readLine(): String
}
class SystemConsole : ConsoleContext {
private val scanner = Scanner(System.`in`)
override fun println(msg: String): Unit {
System.out.println(msg)
}
override fun readLine(): String {
return scanner.nextLine()
}
}
fun contextPrintIn(msg: String): ContextReader<ConsoleContext, Unit> =
ContextReader { ctx -> ctx.println(msg) }
fun contextReadIn(): ContextReader<ConsoleContext, String> =
ContextReader { ctx -> ctx.readLine() }
fun main() {
val program = contextPrintIn("Hello, what's your name?")
.bind {
contextReadIn()
}
.bind { name ->
contextPrintIn("Hello, $name!")
}
.bind {
contextPrintIn("Nice to meet you.")
}
program.runWith(SystemConsole())
}
class ConsoleMonadTest {
private val originalIn = System.`in`
private val originalOut = System.out
private lateinit var testOut: ByteArrayOutputStream
@BeforeEach
fun setUp() {
testOut = ByteArrayOutputStream()
System.setOut(PrintStream(testOut))
}
@AfterEach
fun tearDown() {
System.setIn(originalIn)
System.setOut(originalOut)
}
@Test
fun `should print and read correctly`() {
val testInput = "Alice\n"
System.setIn(ByteArrayInputStream(testInput.toByteArray()))
val program = contextPrintIn("Enter your name:")
.bind {
contextReadIn()
}
.bind { name ->
contextPrintIn("Hello, $name!")
}
program.runWith(SystemConsole())
val expectedOutput = "Enter your name:\nHello, Alice!\n"
assertEquals(expectedOutput, testOut.toString())
}
}
연습문제 10.3
fun calculateRpn(input: String, currentStack: Stack<Int>): ContextReader<ConsoleContext, Stack<Int>> {
val tokens = input.split(" ")
val stack = currentStack
return ContextReader { ctx ->
try {
for (token in tokens) {
when (token) {
"+" -> {
val b = stack.pop()
val a = stack.pop()
stack.push(a + b)
}
"-" -> {
val b = stack.pop()
val a = stack.pop()
stack.push(a - b)
}
"*" -> {
val b = stack.pop()
val a = stack.pop()
stack.push(a * b)
}
"/" -> {
val b = stack.pop()
if (b == 0) throw IllegalArgumentException("Division by zero")
val a = stack.pop()
stack.push(a / b)
}
else -> {
stack.push(token.toInt())
}
}
}
stack
} catch (e: Exception) {
ctx.println("Error: ${e.message}")
Stack<Int>()
}
}
}
fun rpnReplLoop(currentStack: Stack<Int>): ContextReader<ConsoleContext, Unit> =
contextPrintIn("Enter RPN expression (or 'q' to quit): ")
.bind {
contextReadIn()
}
.bind { input ->
if (input.toLowerCase() == "q") {
contextPrintIn("Exiting RPN Calculator.")
} else {
calculateRpn(input, currentStack)
.bind { newStack ->
if (!newStack.isEmpty()) {
contextPrintIn("Result: ${newStack.peek()}")
}
// 재귀적으로 자신을 호출하여 다음 계산을 기다림
rpnReplLoop(newStack) // 업데이트된 스택을 다음 루프에 전달
}
}
}
fun main() {
println("Welcome to RPN Calculator!")
val initialStack = Stack<Int>()
rpnReplLoop(initialStack).runWith(SystemConsole())
}