커링을 사용하면 어떤 이점이 있나요?

899 views
Skip to first unread message

Outsider

unread,
Oct 9, 2010, 3:38:55 AM10/9/10
to 한국 스칼라 사용자 모임
Scala 의 커링을 공부하고 있는데요.
그럭저럭 문법적으로 어떻게 커링을 사용해야 하는 것인지는 어느정도 이해했는데요.
커링을 왜 사용해야 하는 것인지를 잘 모르겠습니다.

제가 생각했을때는 많은 파라미터들 중에서 논리적으로 구분되는 파라미터끼리
따로 구분을 해주어서 보기 좋은(혹은 이해하기 좋은) 정도의 이점밖에는 모르겠는데
커링을 사용하는 다른 장점은 어떤것이 있나요?

fupfin

unread,
Oct 10, 2010, 8:32:22 PM10/10/10
to scala...@googlegroups.com
그냥 번역하면서 배운 거라서 감이 확실히 오지는 않지만 대략 알고 있는
걸 정리해보겠습니다.

커링은 인자가 여럿인 함수 하나를 인자가 하나인 함수 여럿으로 나누는 기법
입니다. 하스켈 카레(응?) 아저씨가 정리한 수학 이론이라더군요.

암튼 우리가 객체지향에서 객체 추상화를 할 때 변하는 부분과 변하지 않는
부분을 나눠서 상속 구조를 만들듯이 함수 인자의 변하는 부분과 변하지 않는
부분을 나눠 변하지 않는 부분을 미리 전달하고 변하는 부분을 열어두는 함수
추상화 기법이라고 합니다.

스칼라에서는 커링을 쓰면서 (...)를 {..}로 대체할 수 있어서 마치 언어를
확장하는 느낌을 주게 됩니다.

10. 10. 9. 오후 4:38, Outsider 쓴 글:

fupfin

unread,
Oct 10, 2010, 8:46:54 PM10/10/10
to scala...@googlegroups.com

Outsider ..

unread,
Oct 25, 2010, 11:27:24 AM10/25/10
to scala...@googlegroups.com
이 질문을 하면서 scala 유저그룹에도 메일링을 날렸었는데요...
커링을 왜 쓰는건지 모르겠다는 질문을요...

그 메일링에 글이 많이 달려서... 참고하시라고 공유해 드립니다. ㅎㅎ



HamsterofDeath
Am 09.10.2010 0

*making up an example*

class MagicLogger
def logCurried(a:String)(b:String) = {
println(a+b)
}

class SomeLogger {
     val logger = new MagicLogger
def getLogger(withPrefix:String) {
       logger.logCurried(withPrefix)
  }
}

usage:
val somelogger = new SomeLogger
...
val curriedMagicLoggingFunction = somelogger.getLogger("myprefix")
...
curriedMagicLoggingFunction("my message")



HamsterofDeath

 basically, you can use currying if you want to plug a function together
at different places


Jason Zaugg

Arguments passed one parameter section can be used to infer the types
of a formal parameter in a subsequent parameter section:

http://gist.github.com/618151



HamsterofDeath

 imo, it's a disadvantage that you need parameter lists for this


Joel Neely

Currying lets you bind some parameters earlier than others. Here's one use case.

  1. You have a general computation scheme (function) that can be used in different contexts.
  2. Each of those contexts needs a slightly different set-up (parameterization).
  3. Each of those cases allows for re-use after the set-up.
A simple concrete example is sales tax, where a general scheme might be "bas rate on the first cap dollars, and ext rate after that, where ext is less than bas". In pseudo-code:

    salestax (bas)(cap)(ext)(amt) = amt * ext + (bas - ext) * min(cap, amt)

(Obviously for this example I'm ignoring real-world issues such as type, rounding, precision, etc.) If you know the rates and caps for three cities, A, B, and C, you can create specialized sales tax functions for each city by currying over the per-city parameters. For example:

    salestaxA(amt) = salestax(0.08)(500.00)(0.05)
    salestaxB(amt) = salestax(0.09)(1000.00)(0.025)
    salestaxC(amt) = salestax(0.075)(750.00)(0.03)

Hope this helps.
-jn- 


Ingo Maier

On 10/9/10 2:49 PM, HamsterofDeath wrote:
 imo, it's a disadvantage that you need parameter lists for this

How else would you do it?

Here are a few other considerations for or against currying in Scala.

1) As Jason already mentioned, type inference is one. Another related thing is overloading, which is based on the first argument list only:

You can do

class A {
 def f(x: Int, y: Int) {}
 def f(x: Int, s: String) {}
}

but not

class A {
 def f(x: Int)(y: Int) {}
 def f(x: Int)(s: String) {}
}

Well, actually, looks like I hit a bug:

scala> class A {
    |   def f(x: Int)(y: Int) {}
    |   def f(x: Int)(s: String) {}
    | }
defined class A

scala> (new A).f(0)
<console>:7: error: ambiguous reference to overloaded definition,
both method f in class A of type (x: Int)(s: String)Unit
and  method f in class A of type (x: Int)(y: Int)Unit
match argument types (Int)
      (new A).f(0)
              ^

Same for (new A).f(0)(1) and (new A).f(0)("")


2) Default arguments and dependent method types can only use arguments from previous argument lists (at least for now).

You can do:

def f(x: Int)(y: Int = g(x))

but not

def f(x: Int, y: Int = g(x))

Similarily for dependent method types.

3) Convenient closure syntax (especially useful for DSLs):

xs.foldLeft(x) { (a,b) => f(a,b) }

instead of

xs.foldLeft(x, { (a,b) => f(a,b) })


Some of these considerations sometimes work against each other though. The nice dangling code block syntax can be at odds with type inference. For example:

scala> def f[A](init: A)(iter: Int=>A) {}
f: [A](init: A)(iter: (Int) => A)Unit

scala> f(Nil) { i => List(i) } 
<console>:7: error: type mismatch;
 found   : List[Int]
 required: object Nil
      f(Nil) { i => List(i) }
                        ^

scala> def g[A](init: A, iter: Int=>A) {}
g: [A](init: A,iter: (Int) => A)Unit

scala> g(Nil, { i => List(i) })

Ingo



Brian Maso

Not actually a bug -- been documented on this list before. Recall that

def fun(param1:T1)(param2:T2) = body

Is sugar for

def fun(param1:T1) = {
 (param2:T2) => { body }
}

So if the number and type of the first param list of two functions in
the same class are the same, you have ambiguous method overloading.

Brian Maso


HamsterofDeath

 the "usual way" java does it

void/def x(x:T, y:Function[String,T])
the java compiler understands that the second paramter has to be
Function<String, Integer> if the first parameter is an integer. the
scala compiler doesn't


Randall R Schulz

How come everyone's answer described multiple
argument lists and not curried functions?


Randall Schulz


Repain Alex

One of the direct use you can think for curried function is that they can be used as very flexible function factories.

The following function f :
def f(a: Int)(b: Int)(c: Int) { (a-b)*c }

can be called so as to produce a lot of different functions.

For instance :

val f32 = f(3)(2) is a function that, given 



Repain Alex

... sorry for the break.

For instance :

val f32 = f(3)(2) is a function that, given c:Int, returns (3-2)*c .

Various usage can be made of this. Among them is the fact that thanks to currying you can calculate the final result of a multi-parameters function in different steps in time. This is linked to the concept of partially-applied functions, which f32 is an example.


Ingo Maier




On 10/9/10 3:58 PM, Brian Maso wrote:
Not actually a bug -- been documented on this list before. Recall that

def fun(param1:T1)(param2:T2) = body

Is sugar for

def fun(param1:T1) = {
  (param2:T2) =>  { body }
}

I refuse to recall that :)


class A {
 def f(x: Int)(y: Int) {}
 def f(x: Int)(s: String) {}
}

gets compiled to:

public class A extends java.lang.Object implements scala.ScalaObject{
public void f(int, int);
 Code:
  0:   return

public void f(int, java.lang.String);
 Code:
  0:   return

public A();
 Code:
  0:   aload_0
  1:   invokespecial   #21; //Method java/lang/Object."<init>":()V
  4:   return

}

You might be referring to eta expansion which happens only at call site, which you can also observe by noticing that


class B {
 def f(x: Int)(y: Int) {}
 val x = f(0) _
 val y = f(1) _
}

generates two function classes.

As I demonstrated, you can't call any of the f's in class A above, at least not from Scala it seems, and I am wondering why we don't give an error at definition site similar to:

scala> class A {
    | def f(x: Int) {}
    | def f(x: Int) {}
    | }
<console>:7: error: method f is defined twice
      def f(x: Int) {}
          ^

Cheers,
Ingo


Ingo Maier

On 10/9/10 4:57 PM, Ingo Maier wrote:
As I demonstrated, you can't call any of the f's in class A above, at
least not from Scala it seems, and I am wondering why we don't give an
error at definition site similar to:

scala> class A {
| def f(x: Int) {}
| def f(x: Int) {}
| }
<console>:7: error: method f is defined twice
def f(x: Int) {}

... or

scala> class A {
    |   def f(x: Int, y: Int) {}
    |   def f(x: Int)(y: Int) {}
    | }
<console>:7: error: double definition:
method f:(x: Int)(y: Int)Unit and
method f:(x: Int,y: Int)Unit at line 6
have same type after erasure: (x: Int,y: Int)Unit

        def f(x: Int)(y: Int) {}
            ^

but that's a different code path :)

Ingo



Ingo Maier

On 10/9/10 4:09 PM, HamsterofDeath wrote:
 the "usual way" java does it

void/def x(x:T, y:Function[String,T])
the java compiler understands that the second paramter has to be
Function<String, Integer>  if the first parameter is an integer. the
scala compiler doesn't

That's not always what you want in a language with subtyping. Javac also doesn't do what you think it does:

public class J {
 <T> T f(T t, T s) { return s; }

 void g() {
   Number n = f(new Integer(0), new Float(0));
 }
}

Compiles for me.

Cheers,
Ingo


Paul Phillips

On Sat, Oct 09, 2010 at 04:57:41PM +0200, Ingo Maier wrote:
> As I demonstrated, you can't call any of the f's in class A above, at
> least not from Scala it seems, and I am wondering why we don't give
> an error at definition site similar to:
>
> scala> class A {
>      | def f(x: Int) {}
>      | def f(x: Int) {}
>      | }
> <console>:7: error: method f is defined twice
>        def f(x: Int) {}
>            ^

It has been opened more than once, and closed wontfix.  See the comments
in this ticket:

 https://lampsvn.epfl.ch/trac/scala/ticket/2628
 "Ambiguous method overloading compiles, but should not"


Ingo Maier

On 10/9/10 6:01 PM, Paul Phillips wrote:
On Sat, Oct 09, 2010 at 04:57:41PM +0200, Ingo Maier wrote:
As I demonstrated, you can't call any of the f's in class A above, at
least not from Scala it seems, and I am wondering why we don't give
an error at definition site similar to:

scala>  class A {
     | def f(x: Int) {}
     | def f(x: Int) {}
     | }
<console>:7: error: method f is defined twice
       def f(x: Int) {}
           ^

It has been opened more than once, and closed wontfix.  See the comments
in this ticket:

  https://lampsvn.epfl.ch/trac/scala/ticket/2628
  "Ambiguous method overloading compiles, but should not"


Thanks. I found an even older ticket in the meanwhile :)

Ingo


Brian Clapper

In addition to the answers you've already received, I documented my own
epiphany on this topic in June:

http://brizzled.clapper.org/id/102/
--
-Brian


Raoul Duke

random by the way, seems like currying != partial application?
http://lambda-the-ultimate.org/node/2266


Repain Alex

Currying is the principle that allows you to use partial application of a function. In the example you gave, there is effectively a difference between making a currified function (i.e. , partial-applicable) from a function taking a parameter list, and partially applying this currified function. The concepts are closely linked, yet the difference is good to be understood from the start.

Alex

PS : I might be at a learning level way lower than many people here, I will whip myself in shame if someone bothers to take on its own time to correct my intrusions in these lists.



Roland Tepp

Hi,

 

I just recently made use of currying in Java.

 

 

Basically what I needed was to pass a function to a method that had to perform some arbirtaty changes to the data contained in a state wrapper.

 

As I needed a way to essentially parametrize the function passed based on the data to be modified, I created another function that given a (new) value, produced a function that updated a required field with the provided value.

 

So in short – one reason for using currying is when you will need to create a function that creates another function.

It is all about composability of functionality.

 

You chop up the task you need to perform into a small composable bits and combine them as needed.











2010/10/11 fupfin <fup...@gmail.com>
--
Google 그룹스 '한국 스칼라 사용자 모임' 그룹에 가입했으므로 본 메일이 전송되었습니다.
이 그룹에 게시하려면 scala...@googlegroups.com(으)로 이메일을 보내세요.
그룹에서 탈퇴하려면 scala-korea...@googlegroups.com로 이메일을 보내주세요.
더 많은 옵션을 보려면 http://groups.google.com/group/scala-korea?hl=ko에서 그룹을 방문하세요.


Reply all
Reply to author
Forward
0 new messages