관리 메뉴

HAMA 블로그

Golang 에서 함수형(?) 프로그래밍 (Map,Filter 등) 본문

Go

Golang 에서 함수형(?) 프로그래밍 (Map,Filter 등)

[하마] 이승현 (wowlsh93@gmail.com) 2018. 2. 8. 18:23



Golang 에서의 Map, Filter 등


Python 으로 코딩하다가 Golang 으로 바꿔보면 가장 크게 불편한점은 List Comprehension 의 부재라고 느꼈다.
나도 Newbie라 Golang 에서는 어떻게 리스트 조작을 할까 궁금해서 자료들을 찾아서 정리 해보았다. 


기본 

가장 기본적인것은 Python 이나 Scala등에서 제공하는 synthetic sugar 마법을 사용하지 않고, Golang 답게 직접 해당 함수를 만들어서 호출하는 것이다. 아래 예를 보자.

func Map(vs []int, f func(int) int) []int {
vsm := make([]int, len(vs))
for i, v := range vs {
vsm[i] = f(v)
}
return vsm
}

func add5(n int) int {
return n + 5
}
func main(){
var strs = []int{1,2,3}

fmt.Println(Map(strs, add5))
}

Map 이라는 함수를 만들었다. 첫번째 인자로 배열이 들어가고, 두번째 인자로 그 배열을 조작할 함수가 매개변수로 들어갔다.사용법은 간단한 위의 코드가 설명해 줄 것이다.

 Filter 같은 경우는 아래와 같이 코딩 하면 될 것이다.

func Filter(vs []string, f func(string) bool) []string {
vsf := make([]string, 0)
for _, v := range vs {
if f(v) {
vsf = append(vsf, v)
}
}
return vsf
}


func main(){
var strs = []string{"peach", "apple", "pear", "plum"}

fmt.Println(Filter(strs, func(v string) bool {
return strings.Contains(v, "e")
}))
}

모든 것이 직관적이고 심플하다.!!  (아쉬운 점은 제네릭하지 않다는 것인데, 사실 공용 라이브러리 개발자가 아닌 이상 경험상 제네릭은 그닥 필요 없다.)

다른 예제들은 다음 링크를 참고하자. https://gobyexample.com/collection-functions


고급 (go-funk) 

자신의 필요성에 맞게 빠륵 개발 할 경우에는 제네릭이 거의 필요 없지만, 공용으로 사용될 라이브러리 차원이라면 좀 말이 달라진다. golnag 의 경우는 리플렉션,인터페이스를 통해서 제네릭을 흉내내는 것으로 보이는데, go-funk 라이브러리가 그 것이다. 자바스크립트이 유명한 유틸리티 라이브러리인 lodash와 비슷한 구석이 있지만 차별되는 로드맵을 가지고 있다고 한다. 

사용법을 간단히 살펴보면 (go get github.com/thoas/go-funk 을 통해 패키지 설치한다)

r := funk.Map([]int{1, 2, 3, 4}, func(x int) int {
return x * 2
}) // []int{2, 4, 6, 8}

r := funk.Map([]string{"1", "2"}, func(x string) string {
return "ID-" + x
})
// [ID1 ID2]

와 같이 다양한 타입으로 사용 할 수 있다.

참고로 Map 의 내부는 이렇다.


// Map manipulates an iteratee and transforms it to another type.
func Map(arr interface{}, mapFunc interface{}) interface{} {
if !IsIteratee(arr) {
panic("First parameter must be an iteratee")
}

if !IsFunction(mapFunc) {
panic("Second argument must be function")
}

var (
funcValue = reflect.ValueOf(mapFunc)
arrValue = reflect.ValueOf(arr)
arrType = arrValue.Type()
)

kind := arrType.Kind()

if kind == reflect.Slice || kind == reflect.Array {
return mapSlice(arrValue, funcValue)
}

if kind == reflect.Map {
return mapMap(arrValue, funcValue)
}

panic(fmt.Sprintf("Type %s is not supported by Map", arrType.String()))
}

먼저 순회가능한 타입과 함수가 매개변수로 각각 들어왔는지 확인 한후에, 순회가능 타입이 배열인지, 맵타입인지를 리플렉션으로 확인해서 mapSlice 혹은 mapMap 을 호출해 주고있다.



func mapSlice(arrValue reflect.Value, funcValue reflect.Value) interface{} {
funcType := funcValue.Type()

if funcType.NumIn() != 1 || funcType.NumOut() == 0 || funcType.NumOut() > 2 {
panic("Map function with an array must have one parameter and must return one or two parameters")
}

arrElemType := arrValue.Type().Elem()

// Checking whether element type is convertible to function's first argument's type.
if !arrElemType.ConvertibleTo(funcType.In(0)) {
panic("Map function's argument is not compatible with type of array.")
}

if funcType.NumOut() == 1 {
// Get slice type corresponding to function's return value's type.
resultSliceType := reflect.SliceOf(funcType.Out(0))

// MakeSlice takes a slice kind type, and makes a slice.
resultSlice := reflect.MakeSlice(resultSliceType, 0, 0)

for i := 0; i < arrValue.Len(); i++ {
result := funcValue.Call([]reflect.Value{arrValue.Index(i)})[0]

resultSlice = reflect.Append(resultSlice, result)
}

return resultSlice.Interface()
}

if funcType.NumOut() == 2 {
// value of the map will be the input type
collectionType := reflect.MapOf(funcType.Out(0), funcType.Out(1))

// create a map from scratch
collection := reflect.MakeMap(collectionType)

for i := 0; i < arrValue.Len(); i++ {
results := funcValue.Call([]reflect.Value{arrValue.Index(i)})

collection.SetMapIndex(results[0], results[1])
}

return collection.Interface()
}

return nil
}

배열의 타입과 배열을 조작할 맵함수의 파라미터 타입을 비교하여 같을 경우에 한해서,
배열을 순회하며 맵함수를 적용시켜서 

result := funcValue.Call([]reflect.Value{arrValue.Index(i)})[0]

새 배열에 추가해서 리턴한다.

resultSlice = reflect.Append(resultSlice, result)

레퍼런스:

https://gobyexample.com/collection-functions
go-funk


Comments