Skip to content

Instantly share code, notes, and snippets.

@hikaMaeng
Created October 7, 2023 11:40
Show Gist options
  • Save hikaMaeng/42ec68d6fdf53166f38b59f797add699 to your computer and use it in GitHub Desktop.
Save hikaMaeng/42ec68d6fdf53166f38b59f797add699 to your computer and use it in GitHub Desktop.
@file:Suppress("NOTHING_TO_INLINE")
package kore.wrap
/**
* 성공, 실패를 내포하는 결과값 보고 객체. 최초 값을 람다로 설정하면 이후 모든 map연산이 지연연산으로 처리됨
*/
object W{
/** 예외 발생이 가능한 블록을 실행하고 그 결과에 따라 Wrap생성 */
inline fun <VALUE:Any>catch(throwableBlock:()->VALUE): Wrap<VALUE> = try {
Wrap(throwableBlock())
}catch(e:Throwable){
Wrap(e)
}
/** 정상인 값을 생성함 */
inline operator fun <VALUE:Any>invoke(value:VALUE): Wrap<VALUE> = Wrap(value)
/** 정상인 값을 람다로 생성함. 이후 모든 처리는 지연으로 처리되고 invoke까지 평가가 미뤄짐 */
inline operator fun <VALUE:Any>invoke(block:Thunk<VALUE>): Wrap<VALUE> = Wrap(block)
/** 실패인 값을 예외로 생성함. 반드시 타입파라메터를 지정해야 함*/
inline operator fun <VALUE:Any>invoke(value:Throwable): Wrap<VALUE> = Wrap(value)
@PublishedApi internal val END = invoke<Nothing>(Throwable("END"))
inline fun <VALUE:Any>end():Wrap<VALUE> = END
inline fun isEnd(target:Wrap<*>):Boolean = END == target
}
@file:Suppress("NOTHING_TO_INLINE", "FunctionName")
package kore.wrap
import kore.wrap.WList.Cons
import kore.wrap.WList.Nil
sealed class WList<out ITEM:Any>{
data object Nil: WList<Nothing>()
data class Cons<out ITEM:Any> @PublishedApi internal constructor(
@PublishedApi internal val head:ITEM,
@PublishedApi internal val tail:Wrap<WList<ITEM>>
): WList<ITEM>()
companion object{
inline operator fun <ITEM:Any> invoke(vararg items:ITEM): WList<ITEM>
= items.foldRight(invoke()){ item, acc ->Cons(item, W{acc})}
inline operator fun <ITEM:Any> invoke(): WList<ITEM> = Nil
}
val size:Int get() = when(this){
is Cons ->when(val t = tail()!!){
is Cons ->t.size + 1
is Nil -> 1
}
is Nil -> 0
}
}
inline fun <ITEM:Any> WList<ITEM>.toList():List<ITEM> = fold(listOf()){ acc, it->acc + it}
inline fun <ITEM:Any> WList<ITEM>.setHead(item:ITEM): WList<ITEM> = when(this){
is Cons -> Cons(item, tail)
is Nil -> this
}
inline fun <ITEM:Any> WList<ITEM>.setHeadW(item:Wrap<ITEM>): WList<ITEM> = when(this){
is Cons -> item()?.let{Cons(it, tail)} ?: WList()
is Nil -> this
}
inline fun <ITEM:Any> WList<ITEM>.addFirst(item:ITEM): WList<ITEM> = when(this){
is Cons -> Cons(item, W{this})
is Nil -> this
}
inline fun <ITEM:Any> WList<ITEM>.addFirstW(item:Wrap<ITEM>): WList<ITEM> = when(this){
is Cons -> item()?.let{Cons(it, W(this))} ?: WList()
is Nil -> this
}
//** base-----------------------------------------------------------------*/
tailrec fun <ITEM:Any, ACC:Any> WList<ITEM>.foldW(acc:Wrap<ACC>, block:(Wrap<ACC>, ITEM)->Wrap<ACC>):Wrap<ACC>
= when(this){
is Cons-> tail.getOrFailEffect{return W(it)}.foldW(block(acc, head).failEffect{return W(it)}, block)
is Nil-> acc
}
tailrec fun <ITEM:Any, ACC:Any> WList<ITEM>.fold(acc:ACC, block:(ACC, ITEM)->ACC):ACC
= when(this){
is Cons-> tail.getOrFailEffect{throw it}.fold(block(acc, head), block)
is Nil-> acc
}
@PublishedApi internal tailrec fun <ITEM:Any, ACC:Any> WList<ITEM>._foldIndexed(index:Int, acc:ACC, block:(Int, ACC, ITEM)->ACC):ACC
= when(this){
is Cons ->{
tail.getOrFailEffect{throw it}._foldIndexed(index + 1, block(index, acc, head), block)
}
is Nil -> acc
}
inline fun <ITEM:Any, ACC:Any> WList<ITEM>.foldIndexed(base:ACC, noinline block:(Int, ACC, ITEM)->ACC):ACC
= _foldIndexed(0, base, block)
@PublishedApi internal tailrec fun <ITEM:Any, ACC:Any> WList<ITEM>._foldIndexedW(index:Int, acc:Wrap<ACC>, block:(Int, Wrap<ACC>, ITEM)->Wrap<ACC>):Wrap<ACC>
= when(this){
is Cons ->tail.getOrFailEffect{return W(it)}._foldIndexedW(index + 1, block(index, acc, head).failEffect{return W(it)}, block)
is Nil -> acc
}
inline fun <ITEM:Any, ACC:Any> WList<ITEM>.foldIndexedW(base:Wrap<ACC>, noinline block:(Int, Wrap<ACC>, ITEM)->Wrap<ACC>):Wrap<ACC>
= _foldIndexedW(0, base, block)
inline fun <ITEM:Any> WList<ITEM>.reverseW(): Wrap<WList<ITEM>>
= foldW(W(WList())){ acc, it->W{Cons(it, acc)}}
inline fun <ITEM:Any> WList<ITEM>.reverse(): WList<ITEM>
= reverseW().getOrFailEffect{throw it}
inline fun <ITEM:Any, ACC:Any> WList<ITEM>.foldRight(base:ACC, crossinline block:(ITEM, ACC)->ACC):ACC
= reverse().fold(base){ acc, it->block(it, acc)}
inline fun <ITEM:Any, ACC:Any> WList<ITEM>.foldRightW(base:Wrap<ACC>, crossinline block:(ITEM, Wrap<ACC>)->Wrap<ACC>):Wrap<ACC>
= reverseW().flatMap{it.foldW(base){acc, item->block(item, acc)}}
fun <ITEM:Any, ACC:Any> WList<ITEM>.foldRightIndexedW(base:Wrap<ACC>, block:(Int, ITEM, Wrap<ACC>)->Wrap<ACC>):Wrap<ACC>
= reverseW().flatMap{it._foldIndexedW(0, base){index, acc, item->block(index, item, acc)}}
fun <ITEM:Any, ACC:Any> WList<ITEM>.foldRightIndexed(base:ACC, block:(Int, ITEM, ACC)->ACC):ACC
= reverse()._foldIndexed(0, base){index, acc, item->block(index, item, acc)}
inline fun <ITEM:Any, OTHER:Any> WList<ITEM>.map(crossinline block:(ITEM)->OTHER): WList<OTHER>
= foldRight(WList()){ it, acc->Cons(block(it), W(acc))}
inline fun <ITEM:Any, OTHER:Any> WList<ITEM>.flatMap(noinline block:(ITEM)->WList<OTHER>):WList<OTHER>
= foldRight(WList()) { it, acc ->
when(val list = block(it)){
is Cons -> list.foldRight(acc){item, acc2 -> Cons(item, W(acc2)) }
is Nil -> acc
}
}
fun <ITEM:Any> WList<WList<ITEM>>.flatten(): WList<ITEM>
= foldRight(WList()){it, acc->it.foldRight(acc){ it2, acc2->Cons(it2, W(acc2))}}
////** append-----------------------------------------------------------------*/
fun <ITEM:Any> WList<ITEM>.appendW(list:Wrap<WList<ITEM>> = W{WList()}): Wrap<WList<ITEM>>
= foldRightW(list){it, acc->W{Cons(it, acc)}}
fun <ITEM:Any> WList<ITEM>.append(list:WList<ITEM> = WList()): WList<ITEM>
= foldRight(list){it, acc->Cons(it, W(acc))}
fun <ITEM:Any> WList<ITEM>.copy():WList<ITEM> = append()
operator fun <ITEM:Any> WList<ITEM>.plus(list:WList<ITEM>):WList<ITEM> = append(list)
fun <ITEM:Any> WList<ITEM>.copyW():Wrap<WList<ITEM>> = appendW()
operator fun <ITEM:Any> WList<ITEM>.plus(list:Wrap<WList<ITEM>>):Wrap<WList<ITEM>> = appendW(list)
////** drop----------------------------------------------------------*/
tailrec fun <ITEM:Any> WList<ITEM>._dropWhileIndexed(index:Int, block:(Int, ITEM)->Boolean): WList<ITEM>
= if(this is Cons && block(index, head)) tail.getOrFailEffect{return WList()}._dropWhileIndexed(index + 1, block) else this
inline fun <ITEM:Any> WList<ITEM>.dropWhileIndexed(noinline block:(Int, ITEM)->Boolean):WList<ITEM> = _dropWhileIndexed(0, block)
tailrec fun <ITEM:Any> WList<ITEM>.drop(n:Int = 1): WList<ITEM>
= if(n > 0 && this is Cons) tail.getOrFailEffect{return WList()}.drop(n - 1) else this
tailrec fun <ITEM:Any> WList<ITEM>.dropWhile(block:(ITEM)->Boolean): WList<ITEM>
= if(this is Cons && block(head)) tail.getOrFailEffect{return WList()}.dropWhile(block) else this
////** dropLast----------------------------------------------------------*/
@PublishedApi internal tailrec fun <ITEM:Any> WList<ITEM>._dropLastWhileIndexed(index:Int, block:(Int, ITEM)->Boolean):WList<ITEM>
= when(this){
is Cons -> if(!block(index, head)) reverse() else tail.getOrFailEffect{return WList()}._dropLastWhileIndexed(index + 1, block)
is Nil -> reverse()
}
fun <ITEM:Any> WList<ITEM>.dropLastWhileIndexed(block:(Int, ITEM)->Boolean):WList<ITEM> = reverse()._dropLastWhileIndexed(0, block)
fun <ITEM:Any> WList<ITEM>.dropLastWhile(block:(ITEM)->Boolean):WList<ITEM> = reverse()._dropLastWhileIndexed(0){ _, it->block(it)}
fun <ITEM:Any> WList<ITEM>.dropLast(n:Int = 1):WList<ITEM> = reverse()._dropLastWhileIndexed(0){ index, _->index < n}
////** utils-----------------------------------------------------------------*/
fun <ITEM:Any> WList<ITEM>.filter(block:(ITEM)->Boolean):WList<ITEM>
= foldRight(WList()){ it, acc->if(block(it)) Cons(it, W(acc)) else acc}
tailrec fun <ITEM:Any> WList<ITEM>.sliceFrom(item:ITEM): WList<ITEM>
= if(this is Cons && head != item) tail.getOrFailEffect{return WList()}.sliceFrom(item) else this
tailrec fun <ITEM:Any> WList<ITEM>.slice(from:Int): WList<ITEM>
= if(this is Cons && from > 0) tail.getOrFailEffect{return WList()}.slice(from - 1) else this
inline fun <ITEM:Any> WList<ITEM>.slice(from:Int, to:Int): WList<ITEM> = slice(from).dropLast(to - from)
tailrec fun <ITEM:Any> WList<ITEM>.startsWith(target:WList<ITEM>):Boolean = when(this) {
is Cons -> when(target){
is Cons -> if(head == target.head) tail.getOrFailEffect{return false}.startsWith(target.tail.getOrFailEffect{return false}) else false
is Nil -> true
}
is Nil -> target is Nil
}
tailrec operator fun <ITEM:Any> WList<ITEM>.contains(target:ITEM):Boolean = when(this) {
is Cons -> if(head == target) true else target in tail.getOrFailEffect{return false}
is Nil -> false
}
tailrec operator fun <ITEM:Any> WList<ITEM>.contains(target: WList<ITEM>):Boolean = when(this) {
is Cons -> when (target) {
is Cons -> if(startsWith(target)) true else target in tail.getOrFailEffect{return false}
is Nil -> false
}
is Nil -> target is Nil
}
inline fun <ITEM:Any, OTHER:Any, RESULT:Any> WList<ITEM>.zipWith(other: WList<OTHER>, noinline block:(ITEM, OTHER)->RESULT): WList<RESULT> {
if(this is Nil || other is Nil) return WList()
val thisSize = size
val otherSize = other.size
return if(otherSize > thisSize){
foldRight(WList<RESULT>() to other.dropLast(otherSize - thisSize).reverse()){ it, (acc, target)->
when(target){
is Cons -> Cons(block(it, target.head), W(acc)) to target.tail{WList()}
is Nil -> acc to target
}
}
} else {
other.foldRight(WList<RESULT>() to dropLast(thisSize - otherSize).reverse()){ it, (acc, target)->
when(target){
is Cons -> Cons(block(target.head, it), W(acc)) to target.tail{WList()}
is Nil -> acc to target
}
}
}.first
}
package fd
import kore.wrap.*
import org.junit.Test
import kotlin.test.assertEquals
class WListTest{
@Test
fun test1(){
val list = WList(1, 2, 3)
val nil = WList<Int>()
assertEquals(list.size, 3)
assertEquals(nil.size, 0)
assertEquals(list.toList(), listOf(1, 2, 3))
assertEquals(list.setHead(5).toList(), listOf(5, 2, 3))
assertEquals(nil.setHead(5).toString(), "Nil")
assertEquals(list.setHeadW(W(5)).toList(), listOf(5, 2, 3))
assertEquals(nil.setHeadW(W(5)).toString(), "Nil")
assertEquals(list.setHeadW(W(Throwable(""))).toList(), listOf())
assertEquals(nil.setHeadW(W(Throwable(""))).toString(), "Nil")
assertEquals(list.fold(""){acc,it->acc + it}, "123")
assertEquals(list.foldW(W("")){ acc, item->acc.map{"$it$item"} }(), "123")
assertEquals(list.foldW(W("")){ acc, it->W.end() }.ok, null)
assertEquals(list.addFirst(4).toList(), listOf(4,1,2,3))
assertEquals(nil.addFirst(4).toString(), "Nil")
assertEquals(list.foldIndexed(""){index, acc,it->"$acc$index$it"}, "011223")
assertEquals(list.reverse().toList(), listOf(3,2,1))
assertEquals(list.reverseW()()?.toList(), listOf(3,2,1))
assertEquals(list.foldRight(""){it, acc->acc + it}, "321")
assertEquals(list.foldRightW(W("")){item, acc->acc.map{"$it$item"}}(), "321")
assertEquals(list.foldRightIndexed(""){index, it, acc->"$acc$index$it"}, "031221")
assertEquals(list.map{"${it*2}"}.toList(), listOf("2","4","6"))
assertEquals(list.flatMap{ if(it != 2) WList(it) else WList() }.toList(), listOf(1,3))
assertEquals(WList(WList(1,2), WList(3,4)).flatten().toList(), listOf(1,2,3,4))
assertEquals(list.appendW()()?.toList(), listOf(1,2,3))
assertEquals(list.append().toList(), listOf(1,2,3))
assertEquals(list.append(WList(4, 5)).toList(), listOf(1,2,3,4,5))
assertEquals((list + WList.Nil).toList(), listOf(1,2,3))
assertEquals((list + WList(4,5)).toList(), listOf(1,2,3,4,5))
assertEquals(list.copy().toList(), listOf(1,2,3))
assertEquals(list.appendW(W(WList(4, 5)))()?.toList(), listOf(1,2,3,4,5))
assertEquals((list + W(WList.Nil))()?.toList(), listOf(1,2,3))
assertEquals((list + W(WList(4,5)))()?.toList(), listOf(1,2,3,4,5))
assertEquals(list.copyW()()?.toList(), listOf(1,2,3))
assertEquals(list.drop(2).toList(), listOf(3))
assertEquals(list.dropWhile { it < 2 }.toList(), listOf(2, 3))
assertEquals(list.dropWhileIndexed { index, it -> index < 1 }.toList(), listOf(2, 3))
assertEquals(list.dropLast().toList(), listOf(1,2))
assertEquals(list.dropLast(2).toList(), listOf(1))
assertEquals(list.dropLastWhile { it > 2 }.toList(), listOf(1,2))
assertEquals(nil.dropLast().toList(), listOf())
assertEquals(nil.dropLast(2).toList(), listOf())
assertEquals(nil.dropLastWhile { it > 2 }.toList(), listOf())
assertEquals(list.dropLastWhileIndexed { index, it ->index < 1}.toList(), listOf(1, 2))
assertEquals(list.filter{it != 2}.toList(), listOf(1,3))
assertEquals(WList(1, 2, 3, 4).filter{it % 2 == 0}.toList(), listOf(2,4))
assertEquals(WList(1, 2, 3, 4).sliceFrom(3).toList(), listOf(3,4))
assertEquals(WList(1, 2, 3, 4).slice(1).toList(), listOf(2,3,4))
assertEquals(WList(1, 2, 3, 4).slice(1,2).toList(), listOf(2,3))
assertEquals(nil.sliceFrom(3).toList(), listOf())
assertEquals(nil.slice(1).toList(), listOf())
assertEquals(nil.slice(1,2).toList(), listOf())
assertEquals(list.startsWith(WList(2,3)), false)
assertEquals(list.startsWith(WList(1,2,3)), true)
assertEquals(list.startsWith(WList(1,3)), false)
assertEquals(WList(1,2,3).startsWith(WList(1,2)), true)
assertEquals(list.startsWith(WList()), true)
assertEquals(nil.startsWith(WList()), true)
assertEquals(nil.startsWith(WList(1,2)), false)
assertEquals(WList(1,2) in list, true)
assertEquals(WList(2,3) in list, true)
assertEquals(WList(1,2,3) in list, true)
assertEquals(WList(1,3) in list, false)
assertEquals(WList() in list, false)
assertEquals(WList(1,3) in nil, false)
assertEquals(WList() in nil, true)
assertEquals(1 in list, true)
assertEquals(2 in list, true)
assertEquals(3 in list, true)
assertEquals(4 in list, false)
assertEquals(1 in nil, false)
assertEquals(WList.invoke(1, 2, 3).zipWith(WList.invoke(1,1,1)){ a, b->a + b}.toList(), listOf(2,3,4))
assertEquals(WList.invoke(1, 2).zipWith(WList.invoke(1,1,1)){ a, b->a + b}.toList(), listOf(2,3))
assertEquals(WList.invoke(1, 2, 3).zipWith(WList.invoke(1,1)){ a, b->a + b}.toList(), listOf(2,3))
}
}
@file:Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
package kore.wrap
import kotlin.jvm.JvmInline
@JvmInline
value class Wrap<out VALUE:Any> @PublishedApi internal constructor(@PublishedApi internal val value:Any){
/** R타입을 유지한 상태로 내부의 상태를 바꾸는 연산. 지연연산 모드에서는 계속 지연함수합성이 됨.
* map에 전달되는 람다는 throw할 수 있으며 이를 통해 fail상태로 이전시킬 수 있음.
*/
inline fun <OTHER:Any> map(crossinline block:(VALUE)->OTHER):Wrap<OTHER> = when(value){
is Throwable -> this as Wrap<OTHER>
is Thunk<*> -> Wrap(Thunk{
val v = value.invoke()
if(v is Throwable) v else block(v as VALUE)
})
else -> Wrap(block(value as VALUE))
}
/** 최초의 값을 람다로 지정하지 않아도 이후 지연연산하게 변경함*/
inline fun <OTHER:Any> mapLazy(crossinline block:(VALUE)->OTHER):Wrap<OTHER> = when(value){
is Throwable -> this as Wrap<OTHER>
is Thunk<*> -> Wrap(Thunk{
val v = value.invoke()
if(v is Throwable) v else block(v as VALUE)
})
else -> Wrap(Thunk{block(value as VALUE)})
}
inline fun <OTHER:Any, ORIGIN:Any> List<ORIGIN>.flatMapList( block:(ORIGIN)->Wrap<OTHER>):Wrap<List<OTHER>>{
return W(fold(ArrayList(size)){acc, it->
block(it).isEffected{acc.add(it)}?.let {return W(it)}
acc
})
}
inline fun <OTHER:Any> List<String>.flatMapListToMap( block:(key:String, value:String)->Wrap<OTHER>):Wrap<HashMap<String,OTHER>>{
val result:HashMap<String, OTHER> = hashMapOf()
var key:String? = null
var i = 0
while(i < size){
val it = get(i)
if(key == null) key = it
else{
block(key, it).isEffected{result[key!!] = it}?.let {return W(it)}
key = null
}
i++
}
return W(result)
}
/** map내에서 오류발생 가능성이 있다면 flatMap을 사용해야 함. */
inline fun <OTHER:Any> flatMap(crossinline block:Wrap<VALUE>.(VALUE)->Wrap<OTHER>):Wrap<OTHER> = when(value){
is Throwable -> this as Wrap<OTHER>
is Thunk<*> ->{
val v = value.invoke()
if(v is Throwable) W(v) else block(v as VALUE)
}
else -> block(value as VALUE)
}
/** flatMap이 무조건 지연평가됨. */
inline fun <OTHER:Any> flatMapLazy(crossinline block:Wrap<VALUE>.(VALUE)->Wrap<OTHER>):Wrap<OTHER> = when(value){
is Throwable -> this as Wrap<OTHER>
is Thunk<*> -> Wrap(Thunk{
val v = value.invoke()
if(v is Throwable) v else block(v as VALUE).value
})
else -> Wrap(Thunk{block(value as VALUE).value})
}
/** 실패값을 반드시 복원할 수 있는 정책이 있는 경우 복원용 람다를 통해 현재 상태를 나타내는 예외로부터 값을 만들어냄. 지연연산이 해소됨 */
inline operator fun invoke(block:(Throwable)-> @UnsafeVariance VALUE):VALUE = when(value) {
is Throwable -> block(value)
is Thunk<*> ->{
val v = value.invoke()
if(v is Throwable) block(v) else v
}
else -> value
} as VALUE
/** 정상인 값은 반환되지만 비정상인 값은 null이 됨. 지연연산 설정 시 이 시점에 해소됨*/
inline operator fun invoke():VALUE? = when(value){
is Throwable -> null
is Thunk<*> ->{
val v = value.invoke()
if(v is Throwable) null else v as VALUE
}
else -> value as VALUE
}
/** 정상값 Wrap을 얻거나 null을 얻음. 지연연산이 해소된 새로운 Wrap을 얻음*/
inline val ok:Wrap<VALUE>? get() = invoke()?.let{W(it)}
/** 오류값 Wrap을 얻거나 null을 얻음. 지연연산이 해소된 새로운 Wrap을 얻음*/
inline val fail:Wrap<VALUE>? get() = when(value){
is Throwable -> this
is Thunk<*> ->{
val v = value.invoke()
if(v is Throwable) W(v) else null
}
else -> null
}
/** value를 얻어 사이드이펙트만 처리한 뒤 자신을 반환함. 지연연산이 해소된 Wrap을 얻음*/
inline fun effect(block:(VALUE)->Unit):Wrap<VALUE>{
block(when(value){
is Throwable -> return this
is Thunk<*> ->{
val v = value.invoke()
if(v is Throwable) return W(v)
v
}
else -> value
} as VALUE)
return this
}
inline fun getOrFailEffect(block:(Throwable)->Nothing):VALUE = when(value){
is Throwable -> block(value)
is Thunk<*> ->{
val v = value.invoke()
if(v is Throwable) block(v) else v
}
else -> value
} as VALUE
inline fun failEffect(block:(Throwable)->Unit):Wrap<VALUE>{
when(value){
is Throwable -> block(value)
is Thunk<*> ->{
val v = value.invoke()
if(v is Throwable) block(v)
}
}
return this
}
/** 부수효과가 실행되었다면 null 아니면 Throwable을 반환함. 지연연산이 해소됨 */
inline fun isEffected(block:(VALUE)->Unit= {}):Throwable?{
block(when(value){
is Throwable -> return value
is Thunk<*> ->{
val v = value.invoke()
if(v is Throwable) return v
v
}
else -> value
} as VALUE)
return null
}
}
inline fun <ITEM:Any, OTHER:Any, RESULT:Any> Wrap<ITEM>.map2(other:Wrap<OTHER>, block:(ITEM, OTHER)->RESULT):Wrap<RESULT>{
this.isEffected{a->
other.isEffected{b->
return W(block(a,b))
}?.let{
return W(it)
}
}?.let{
return W(it)
}
return W.end()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment