Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Simply Lift : Chapter 7 - Core Concepts

원문 : http://simply.liftweb.net/index-Chapter-7.html#toc-Chapter-7

7장
핵심 개념들(Core Concepts)


7.1 스니펫(Snippets)

Lift는 Scala 프로그래밍 언어로 만들어졌습니다. Scala는 함수형과 객체지향의 하이브리드 언어입니다. 함수형 프로그래밍 언어의 두가지 핵심 원리들은 불변성(immutability)과 변환(transformation)입니다.

불변성(immutability)은 일단 데이터구조가 인스턴스화 되면 생명주기 동안은 바뀌지 않는 것을 의미합니다. 더 구체적으로는 객체가 일단 인스턴스화 되면 자유롭게 객체는 넘겨질(pass)  수 있고 객체는 항상 객체의 모든 메서드에서 같은 값을 리턴합니다. Java의 String클래스는 immutable입니다. Python은 딕션어리의 인덱스로 immutable 클래스를 필요로 합니다. 객체들이 상태를 바꾸지 않을 것이라는 보장을 받을 수 있어서 immutable 객체에 대한 참조(reference)를 locking이나 동기화에 대한 걱정 없이 쓰레드 경계를 넘어서 넘길수 있기 때문에 불변성은 멀티쓰레드 애플리케이션에서 아주 강력합니다.



7.1.1 Snippet NodeSeq => NodeSeq

변환(Transformation)은 웹페이지를 작성하기 위한 “스트림으로 작성하기(writing to a stream)의 대안을 제공합니다. 응답의 일부로써 스트리밍될 캐릭터들을 일으키는 태그들을 가지는것 보다는 Lift는 뷰를 로드하고  뷰에서 발생한 각 “스니펫”에서 Lift는 단지 스니펫 호출과 연결된 마크업을 HTML의 새로운 셋으로 변환합니다.

더 구체적으로 만들어 보겠습니다. 다음의 마크업이 있습니다.

<span class="foo lift:WhatTime">The time is <span id="current_time">currentTime</span></span>

그리고 연결된 스니펫입니다:

object WhatTime {
   def render = "#current_time" #> (new Date).toString
}

결과 마크업은 다음과 같을 것입니다:

<span class="foo">The time is Mon Dec 06 21:01:36 PST 2010</span>

어떻게 동작하는지 차례대로 보겠습니다. 먼저, <span>에서 class속성은 2개의 클래스 foo와 lift:WhatTime을 가지고 있습니다. lift:로 시작하는 모든 class속성은 스니펫 호출을 의미합니다. 스니팻은 HTML을 HTML로 또는 Scala의 NodeSeq => NodeSeq로 변환하는 함수입니다.

Lift는 이 경우에 싱글톤으로  해결하고 render 메서드를 호출하는 WhatTime(23.1 참조)이라는 이름의 스니펫을 찾습니다. render 메서드는 Lift의 CSS Selector Transforms(7.10 참조)를 사용해서 만들어진 NodeSeq => NodeSeq를 리턴합니다.  함수에 대한 파라미터는 class 속성으로부터 제거된 실제 스니펫 호출과 함께 스니펫 호출을 일으키는 Element입니다:

<span class="foo">The time is <span id="current_time">currentTime</span></span>

그 후 함수는 적용되고 NodeSeq의 결과는 원래 Element가 있던 페이지의 위치에 인서트됩니다. 페이지가 immutable XML 객체들로 구성되었기 때문에 NodeSeq => NodeSeq를 변환할 수 있고 변경된 것을 가질 걱정을 하지 않아도 됩니다. 또한 전체 페이지 변환 프로세스에 통해서 유효한 마크업을 가졌다는 것을 알고 있습니다.

게다가 페이지를 well formed XML 문서로 유지하는 것은 <head>태그안에 넣을 특정한 태그와 </body>태그(7.17 참조)를 닫기 직전에 삽입할 다른 태그를 허용합니다.

하지만 변환의 간단함은 이해하기 쉽고 매우 강력합니다.



7.1.2 스니펫 인스턴스 (Snippet instances)

스니펫은 또한 다음과 같이 정의되었습니다:

class WhatTime {
   private var x = 0

   def render = {
      x += 1
      "#current_time" #> ((new Date).toString + " and you've seen this message "+x+" times)
   }
}




7.1.3 스니펫 클래스에서의 다중 메서드 (Multiple methods on a snippet class)

7.1.4 스니펫간의 통신 (Inter-snippet communication)

7.1.5 재귀적인 스니펫 (Recursive Snippets)


7.1.6 스니펫 파라미터 (Snippet parameters)




7.2 박스/옵션 (Box/Option)

Scala는 괜찮은 특징들을 많이 가지고 있습니다. Burak Emir가 여러번 친절하게 일깨워줄 때까지 제가 적응하는데 오래걸렸던 특징들 중 하나는 “Options”입니다. Options와 Boxes에 대해서 읽고 깔끔하고 오류가 적은 코드를 만들기 위해서 Lift가 어떻게 이것들을 잘 사용하도록  만들어주는지에 대해서 읽어보세요. 명령형(imperative)(Java, Ruby) 백그라운드를 가지고 있다면 아마도 다음코드를 이해할 수 있을 것입니다.

x = someOperation
if !x.nil?
   y = someOtherOperation
   if !y.nil?
      doSomething(x,y) return "it worked"
   end
end
return "it failed"

이것은 pseudo 코드이지만 많은 오퍼레이션, 가드(guard), 오퍼레이션, 가드, …... 생성자들이 있습니다.

게다가  null/nil은 실패(failures)로 넘겨집니다(pass) 리턴값이 “호출 실패(call failed)”가 될 수 있는 API의 소비자(consumer)에게 명확하지 않기 때문에 특히 null일때 좋지 않지만 nil일때는 아주 안좋습니다.

자바에서 null은 객체가 아닙니다. 메서드를 갖지 않습니다. 정적으로 타입규칙의 예외입니다.(null은 클래스를 갖지 않지만 어떤 클래스에 대한 어떤 참조도 null로 할당될 수 있습니다.) null에서 메서드를 호출하는 것은 딱 하나의 결과만 있습니다: 예외가 던져집니다. null은 종종 메서드에서 메서드가 성공적으로 수행되었다는 것을 가리키는 플래그로써 리턴되지만 아무 의미도 없는 값입니다. 예를 들면 CardHolder.findByCreditCardNumber("2222222222"). 사실 null을 만든 사람은 그것을 엄청난 실수라고 말했습니다.

Ruby는 null보다 약간더 나은 nil을 가지고 있습니다. nil은 실제 싱글톤 객체입니다. 전체시스템에서 nil의 인스턴스는 딱 하나만 있습니다. 이것은 메서드들을 가지고 있습니다. Object의 서브클래스입니다. Object는 nil 싱글톤이 true를 리턴하는 이 메서드를 오버라이드 하는 경우를 제외하고 false를 리턴하는 “nil?”을 호출하는 메서드를 가지고 있습니다.

Scala는 약간 다르게 합니다.

Option이라고 부르는 추상클래스가 있습니다. Options은 강력하게 형식화 되었습니다. Option[T]로 선언되었습니다. 이것은 Option가 어떤 타입도 될 수 있지만 한번 타입이 정의되면 그것은 바뀔수 없다는 것을 의미합니다. Option의 2개의 서브클래스가 있습니다: Some과 None. None는 싱글톤(nil처럼)입니다. Some은 실제 응답을 둘러싼 컨테이너입니다. 그래서 다음과 같은 메서드를 가질 것입니다:

def findUser(name: String): Option[User] = {
   val query = buildQuery(name)
   val resultSet = performQuery(query)
   val retVal = if (resultSet.next) Some(createUser(resultSet)) else None
   resultSet.close
   retVal
}

Some(User)나 None를 리턴하는 findUser메서드가 있습니다. 앞에서의 예제와는 많이 달라보이지 않습니다. 그래서 모두가 혼란스럽게 잠시 컬렉션에 대해서 이야기하겠습니다.

Scala에서 진짜 좋은 점은(물론 Ruby도 이것을 가지고 있습니다.) 풍부한 list 오퍼레이션입니다. 카운터를 생성하는 것과 list(array) 엘리먼트를 하나씩 빼는 것보다는 약간의 함수를 작성하고 list에 함수를 넘깁니다. list는 각 엘리먼트에서 함수를 호출하고 각 호출에서 리턴된 값으로 구성된 새로운 리스트를 리턴합니다. 코드에서는 그것을 보기가 더 쉽습니다:

scala> List(1,2,3).map(x => x * 2)
line0: scala.List[scala.Int] = List(2,4,6)

위의 코드는 각 리스트 아이텝에 2를 곱하고 “map”은 결과 리스트를 리턴합니다. 오! 그리고 필요하다면 더 간결해 질 수 있습니다:

scala> List(1,2,3).map(_ * 2)
line2: scala.List[scala.Int] = List(2,4,6)

map오퍼레이션을 중첩시킬 수 있습니다.

scala> List(1,2,3).map(x => List(4,5,6).map(y => x * y))
line13: scala.List[scala.List[scala.Int]] = List(List(4,5,6),List(8,10,12),List(12,15,18))

그리고 inner 리스트를 “flatten”할 수 있습니다.

scala> List(1,2,3).flatMap(x => List(4,5,6).map(y => x * y))
line14: scala.List[scala.Int] = List(4,5,6,8,10,12,12,15,18)

최종적으로 첫 리스트에서 짝수만 “filter”할 수 있습니다.

scala> List(1,2,3).filter(_ % 2 == 0). flatMap(x => List(4,5,6).map(y => x * y))
line16: scala.List[scala.Int] = List(8,10,12)

하지만 보이는것처럼 map / flatMap / filter는 꽤 장황합니다. Scala는 코드를 더 가독성있게 하기 위해서 “for” comprehension을 제공합니다:

scala> for {
   x <- List(1,2,3) if x % 2 == 0
   y <- List(4,5,6)} yield x * y
res0: List[Int] = List(8, 10, 12)

하지만 Option[T]로 무엇은 해야합니까?

Option가 map, flatMap, filter(‘for’ comprehension에서 사용할 Scala 컴파일러에 필요한 메서드들)를 구현한 것으로 밝혀졌습니다. 단지 추가적인 내용으로, 제가 처음 “‘for’ comprehension” 구문을 만났을 때 두려웠습니다. 수년동안 프로그래밍을 했고 ‘for’는 말할 것도 없이 “comprehension”에 대해서 듣지 못했습니다. fancy한게 거의 없지만 단지 “‘for’ comprehension”는 위의 construct을 위한 예술적인 단어인 것으로 밝혀졌습니다.

그래서 매력적인 부분은 이 construct를 아주 효율적으로 사용할 수 있다는 것입니다. 첫 예제는 간단합니다:

scala> for {x <- Some(3); y <- Some(4)} yield x * y
res1: Option[Int] = Some(12)

“3과 4를 곱하려고 많은 코드를 작성했습니다.”

“None”를 가졌을때 무슨 일이 발생하는지 보겠습니다:

scala> val yOpt: Option[Int] = None
yOpt: Option[Int] = None
scala> for {x <- Some(3); y <- yOpt} yield x * y
res3: Option[Int] = None

그래서 다시 “None”를 가졌습니다. 어떻게 이것을 기본값으로 바꿉니까?

scala> (for {x <- Some(3); y <- yOpt} yield x * y) getOrElse -1
res4: Int = -1


scala> (for {x <- Some(3); y <- Some(4)} yield x * y) getOrElse -1
res5: Int = 12

“getOrElse”코드는 “passed by name”입니다. 바꿔말하면 “else”구문이 유효할때만 코드가 실행됩니다.

Lift는 Box라고 부르는 유사한(analogous) construct를 가지고 있습니다.

Box Full이 되거나 그렇지 않습니다. non-Full Box는 Empty 싱글톤이나 Failure가 될 수 있습니다. Failure는 왜 Box가 값을 담고 있지 않은지에 대한 정보를 가지고 있습니다.

보여줄 오류의 정보를 가질수 있기 때문에 Failure는 아주 유용합니다. HTTP 응답코드, 메시지를 가지고 있습니다.

Lift에서 다음의 방법으로 이것들을 모두 함께 넣습니다.

  • 요청 파라미터를 리턴하는 메서드는 Box[String]를 리턴합니다.
  • model상에서 finder메서드들(모두 찾는 것이 아니라 싱글턴 인스턴스를 리턴하는 것들만)은 Box[Mode]을 리턴합니다.
  • Java에서 작성하였을 때 null을 리턴하는 메서드는 Lift에서는 Box[T]를 리턴합니다.
이것은 다음과 같은 코드를 갖는 다는 것을 의미합니다:

scala> for {id <- S.param("id") ?~ "id param missing"
u <- getUser(id) ?~ "User not found"
} yield u.toXml
res6: net.liftweb.common.Box[scala.xml.Elem] = Failure(id param missing,Empty,Empty)

“id” 파라미터가 넘겨졌는지 보기 위한 명시적인 guard/test가 없고 사용자가 발견되었는지 확인하기 위한 명시적인 테스트도 없습니다.

또한 이 코드는 완전히 타입세이프합니다. 명시적인 타입선언이 없기 때문에 컴파일러는 어떤 타입의 여러가지 객체들이 있는지 알 수 있습니다.

그래서 REST 핸들러내부 코드를 보겠습니다:

serve {
   case "user" :: "info" :: _ XmlGet _ =>
      for {
         id <- S.param("id") ?~ "id param missing" ~> 401
         u <- User.find(id) ?~ "User not found"
      } yield u.toXml
}

id 파라미터가 없다면 괜찮은 에러메시지를 보여주고 401(이것은 랜덤이지만 요점은 파악했을 것입니다.)을 리턴합니다. 그리고 기본적으로 사용자가 없다면 사용자를 찾을 수 없다는 에러와 함께 404를 리턴합니다.

여기 wget을 사용해서 어떻게 보이는지 보겠습니다:

dpp@bison:~/lift_sbt_prototype$ wget http://localhost:8080/user/info.xml
--2010-06-01 15:07:27-- http://localhost:8080/user/info.xml
Resolving localhost... ::1, 127.0.0.1
Connecting to localhost|::1|:8080... connected.
HTTP request sent, awaiting response... 401 Unauthorized
Authorization failed.

dpp@bison:~/lift_sbt_prototype$ wget http://localhost:8080/user/info.xml?id=2
--2010-06-01 15:07:44-- http://localhost:8080/user/info.xml?id=2
Resolving localhost... ::1, 127.0.0.1
Connecting to localhost|::1|:8080... connected.
HTTP request sent, awaiting response... 404 Not Found
2010-06-01 15:07:44 ERROR 404: Not Found.

dpp@bison:~/lift_sbt_prototype$ wget http://localhost:8080/user/info.xml?id=1
--2010-06-01 15:24:12-- http://localhost:8080/user/info.xml?id=1
Resolving localhost... ::1, 127.0.0.1
Connecting to localhost|::1|:8080... connected.
HTTP request sent, awaiting response...
200 OK Length: 274 [text/xml] Saving to: `info.xml?id=1'

dpp@bison:~/lift_sbt_prototype$ cat info.xml\?id\=1
<?xml version="1.0" encoding="UTF-8"?>
   <User id="1" firstName="Elwood" ... validated="true" superUser="false">
</User>

Box와 Option에 대해서 한가지 더 있습니다. 이것들은 덜 복잡하고 더 유지보수성이 있는(maintainable) 코드를 이끕니다. Scala나 Lift에 대해서 전혀 모른다고 하더라도 제공되는 XML코드와 console exchange를 읽을 수 있고 어떤 일이 왜 발생했는지 알수 있습니다. 이것은 if문에 깊게 내장된것보다 훨씬 가독성이 좋습니다. 그리고 가독성있다면 유지보수성이 있습니다.

Lift가 어떻게 이러한 도구들을 사용하고 Scala의 Option 클래스와 ‘for’ comprehension에 대한 이해하기 좋은 소개이기를 바랍니다.




7.3 S/SHtml

7.4 Boot

7.5 SiteMap

7.6 GUIDs

Lift의 핵심개념은 GUID입니다. GUID는 서버의 함수와 브라우저의 무언가를 연결하도록 사용된 전역적으로 유일한 식별자입니다. replay attack을 아주 어렵게 만들기 때문에 GUID는 LIFT를 더 안전하게 만듭니다. 또한 개발자가 비즈니스로직에 더 많은 시간을 소비하고 부수적인 부분에는 덜 시간을 소비하기 때문에 GUID는 복잡하고 상태보존적(stateful)이고 인터랙티브한 애플리케이션을 개발하기 더 쉽게 만듭니다.



7.6.1 어떻게 GUID가 생성되는가 (How GUIDs are generated)

7.6.2 그것들이 어디서 사용되는가 (Where they are used)




7.7 LiftRules

7.8 SessionVars와and RequestVars

7.9 Helpers

7.10 CSS Selector Transforms

Lift 2.2-M1은 XHTML을 변환하기 위한 새로운 메카니즘을 소개했습니다: CSS Selector Transforms(CssBindFunc). 새로운 메카니즘은 NodeSeq => NodeSeq를 변환하기 위해서 사용될수 있는 CSS 셀렉터의 서브셋을 제공합니다. 이 특징의 예제들은 다음을 포함합니다:

  • "#name" #> userName // userName변수와 id명을 가진 엘리먼트를 교체합니다
  • "#chat_lines *" #> listOfChats // listOfChats의 각 엘리먼트의 chat_lines의 내용을 교체합니다
  • ".pretty *" #> <b>Unicorn</b> // pretty라는 CSS class를 가진 각 엘리먼트와 <b>Unicorn</b>의 컨텐츠를 교체
  • "dog=cat [href]" #> "http://dogscape.com" // cat에 할당된 dog 속성을 가진 모든 엘리먼트의 href속성을 할당합니다
  • "#name" #> userName & "#age" #> userAge // name을 userName으로 age을 userAge으로 설정합니다
  • "li *" #> userName & "li [class]" #> "foo" // username과 class를 가진 모든 <li>엘리먼트의 컨텐츠를 foo로 설정합니다
  • "li *" #> userName & "li [class+]" #> "foo" // username을 가진 모든 <li> 엘리먼트의 컨텐츠를 설정하고 class에 foo를 추가합니다
  • "*" #> <span>{userName}</span> // 모든 엘리먼트를 <span>{userName}</span>로 설정합니다
CSS Selector Transforms는 NodeSeq => NodeSeq를 상속받습니다. 이것들은 문자그대로 함수이고 NodeSeq => NodeSeq를 기대하는 어떤 것에도 파라미터로써 넘겨지거나 NodeSeq => NodeSeq를 리턴하는 어떤 메서드에 대한 결과로써 리턴될 수 있습니다.

어떻게 동작하는지 보기 위해서 각 부분들을 보겠습니다.

먼저 import net.liftweb.util._와 import Helpers._를 해야 합니다. 이러한 팩키지들은 CSS Selector Transforms를 동작하게 만드는 클래스와 implicit conversion를 은 인클루드하고 있습니다.

변환은 다음과 같이 정의됩니다: String으로 표현된 셀렉터 #> 변환값

셀렉터는 CSS 셀렉터의 다음 서브셋을 구현한 String 상수입니다.

  • #id - 지정한 id를 가진 엘리먼트를 셀렉트합니다.
  • .class - 공백으로 구분된 값과 class가 같은 class속성을 가진 모든 엘리먼트를 셀렉트합니다.
  • attr_name=attr-value - 주어진 속성과 주어진 값이 같은 모든 엘리먼트를 셀렉트합니다.
  • element_name - 이름과 매칭되는 모든 엘리먼트를 셀렉트합니다.
  • * - 모든 엘리먼트를 셀렉트합니다.
  • @name - 지정한 name을 가진 모든 엘리먼트를 셀렉트합니다.
  • :button - type=”button”인 모든 엘리먼트를 셀렉트합니다.
  • :checkbox - type=”checkbox”인 모든 엘리먼트를 셀렉트합니다.
  • :file - type=”file”인 모든 엘리먼트를 셀렉트합니다.
  • :password - type=”password”인 모든 엘리먼트를 셀렉트합니다.
  • :radio - type=”radio”인 모든 엘리먼트를 셀렉트합니다.
  • :reset - type=”reset”인 모든 엘리먼트를 셀렉트합니다.
  • :submit - type=”submit”인 모든 엘리먼트를 셀렉트합니다.
  • :text - type=”text”인 모든 엘리먼트를 셀렉트합니다.
셀렉터뒤에 치환(replacement) 규칙을 둘 수 있습니다.

  • 아무것도 없으면(예를들어 “#id”) 값과 매칭되는 모든 엘리먼트를 치환합니다.
    
    "#name" #> "David" // <span><span id="name"/></span> -> <span>David</span>
    

  • * 은(예를 들면 “#id *”) 값과 매칭되는 엘리먼트의 자식컨텐츠를 치환합니다.
    
    "#name *" #> "David" // <span><span id="name"/></span> -> <span><span id="name>David</span></span>
    

  • *+ 또는 *< 는(예를 들면 “#id *+”) 컨텐츠 자식 노드들에 값을 추가합니다.
    
    "#name *+" #> "David" // <span><span id="name"/>Name: </span> -> <span><span id="name>Name: David</span></span>
    

  • -* 또는 >* 는(예를 들면 “#id -*”) 컨텐츠 자식 노드들 앞에 값을 추가합니다.
    
    "#name -*" #> "David" // <span><span id="name"/> Pollak</span> -> <span><span id="name>David Pollak</span></span>
    

  • [attr] 은(예를 들면 “#id [href]”) 값과 매칭되는 속성의 값을 치환합니다.
    
    "#link [href]" #> "http://dogscape.com" // <a href="#" id="link">Dogscape</a> -> <a href="http://dogscape.com" id="link">Dogscape</a>
    

  • [attr+] 은(예를 들면 “#id [class+]”)는 존재하는 속성에 값을 추가합니다.
    
    "span [class+]" #> "error" // <span class"foo">Dogscape</span> -> <span class"foo error">Dogscape</span>
    

  • ^^ - 선택된 엘리먼트를 템플릿에서 한 엘리먼트를 선택하는 것이 가능하도록  한것이 리턴된 엘리먼트의 루트로 올립니다.(lift)
  • ^* - 선택된 엘리먼트의 자식들을 템플릿에서 한 엘리먼트의 자식들을 선택하는 것이 가능하도록 한 것이 리턴된 엘리먼트의 루트로 올립니다.(lift)
CSS Selector Transform의 우변(right hand side)은 다음 중 하나가 될 수 있습니다.

  • 문자열 - 문자열 상수, 예를 들면:
    
    "#name *" #> "David" // <span id="name"/> -> <span id="name">David</span>
    "#name *" #> getUserNameAsString
    

  • NodeSeq - NodeSeq 상수, 예를들면:
    
    "#name *" #> <i>David</i> // <span id="name"/> -> <span id="name"><i>David</i></span>
    "#name *" #> getUserNameAsHtml
    
  • NodeSeq => NodeSeq - 노드를 변환하는 함수(CssBindFunc가 될수 있습니다.):
    
    "#name" #> ((n: NodeSeq) => n % ("class" -> "dog")) // <span id="name"/> -> <span id="name" class="dog"/>
    
  • Bindable - Bindable trait의 구현체 (예를들면 MappedField와 Record.Field)
  • StringPromotable - String이 될 수 있는 상수(Int, Symbol, Long, Boolean). Int, Symbol, Long, Boolean에서 StringPromotable으로 변환되는 자동 (implicit) conversion이 있습니다.
    
    "#id_like_cats" #> true & "#number_of_cats" #> 2
    
  • IterableConst - Box, Seq, NodeSeq => NodeSeq의 Option, String, NodeSeq, Bindable. Implicit conversion은 자동적으로 Box[String], List[String], List[NodeSeq]등을 IterableConst으로 바꿉니다.
    
    "#id" #> (Empty: Box[String]) // <span><span id="id">Hi</span></span> -> <span/>
    "#id" #> List("a", "b", "c") // <span><span id="id"/></span> -> <span>abc</span>
    "#id [href]" #> (None: Option[String]) <a id="id" href="dog"/> -> <a id="id"/>
    
선택된 엘리먼트의 자식들에 바인딩되면 엘리먼트의 여러 복사본들은 IterableConst로 바인딩됩니다. (엘리먼트가 id속성을 가지고 있으면 id 속성은 첫 엘리먼트 후에는 없어질 것입니다.)

"#line *" #> List("a", "b", "c") // <li id="line>sample</li> -> <li id="line">a</li><li>b</li><li>c</li>
"#age *" #> (None: Option[NodeSeq]) // <span><span id="age">Dunno</span></span> -> <span/>

위의 유즈케이스들은 약간 이상해 보입니다.(완전히 다르지는 않습니다.)하지만 이것들은 Lift에서 일반적인 유즈케이스입니다. * IterableFunc - NodeSeq => String, NodeSeq, Seq[String], Seq[NodeSeq], Box[String], Box[NodeSeq], Option[String], Option[NodeSeq]로 변환하는 함수의 Box, Seq, Option. IterableConst에서 여러가지 값들을 다루기 위한 같은 규칙들은 IterableFunc에 적용됩니다. Implicit conversion은 자동적으로 적절한 시그니처를 가진 함수들을 IterableFunc로 바꿉니다.

& 메서드와 CSS Selector Transform를 묶을 수 있습니다.
"#id" #> "33" & "#name" #> "David" & "#chat_line" #> List("a", "b", "c") & ClearClearable

CSS Selector Transform은 Lift의 전통적인 바인딩에 대안을 제공합니다.(Helpers.bind()를 보세요.)




7.11 서버사이트 함수를 호출하는 클라이언트사이드 동작(behavior)

7.12 Ajax

7.13 Comet

7.14 LiftActor

7.15 패턴매칭 (Pattern Matching)

7.16 타입 안정성(Type safety)

7.17 페이지 재작성(Page rewriting)

7.18 보안(Security)
2011/02/19 03:03 2011/02/19 03:03