프로젝트 관리 추천 도서

올초에 게임 개발 그룹으로 이동하면서 내가 게임 팀장님들과 시니어 개발자분들에게 선물한 책이 2권 있는데, 한 권은 스콧 버쿤의 《The Art of Project Management- 마음을 움직이는 프로젝트 관리》이고 다른 한 권은 톰 드마르코, 티모시 리스터의 《피플웨어》이다. 특히, 실제 프로젝트 관리에서 발생하는 다양한 문제들에 대한 통찰과 상세한 지침이 들어있는 스콧 버쿤의 책을 가장 추천한다. 참고로 국내엔 아직 번역되지 않았지만, 이 책의 2판이 《Making Things Happen: Mastering Project Management》이란 제목으로 출간되었다.

5-6개월 남짓한 기간 동안 개발 프로세스나 문화는 어느 정도 정착이 되었으나, 초반부터 있었던 여러 문제가 아직도 반복되고 있어서 어느 정도 한계를 느끼는 시기이기도 하다. 주변의 경험 많은 PM분들에게 조언을 듣는 것도 방법이지만, 항상 그렇듯이 그런 좋은 분들이 주변에 드물기 때문에 당분간은 다시 프로젝트 관리와 관련된 도서들을 읽어보려고 한다. 같은 책이라도 내가 처한 상황에 따라 마음에 와닿는 게 다르기 때문에 이전에 읽었던 책이라도 다시 읽으면 또 새로운 지식을 발견하게 된다.

이런 맥락에서 오늘 구매한 책은 역시 스콧 버쿤의 《The Year Without Pants: WordPress.com and the Future of Work》이다. 프로젝트 관리와 관련된 4권의 책을 출판하여 세계적인 베스트셀러 작가가 된 스캇 버쿤이 다시 일선으로 돌아가서 프로젝트 관리를 경험하고 출간한 책이다. 사무실이 없고 이메일도 거의 쓰지 않는 것으로 유명한 미래지향적 회사인 WordPress.com에서 스콧 버쿤이 직접 매니저로 일하며 겪은 경험담을 일종의 참여 저널리즘 형태로 풀어난 책이라 흥미롭다. 회사에 대한 책을 출간하는 게 입사 조건이었다고 하니 말이다!

이 책을 통해 뭔가 변화의 계기가 될 수 있는 통찰력을 얻을 수 있었으면 좋겠다.

Scala Option.fold vs Option.map/getOrElse

Scala Option offers two different ways to handle an Option value.

Option.map/getOrElse

val name: Option[String] = request getParameter "name"
name map { _.toUpperCase } getOrElse ""

Option.fold

val name: Option[String] = request getParameter "name"
name.fold("") { _.toUpperCase }

On the spark-dev mailing list, there was a discussion on using Option.fold instead of Option.map/getOrElse combination.

Two idioms look almost the same, but people seem to prefer one over the other for readability reasons. Here is the summary of the discussion:

  • Option.getOrElse/map is a more idiomatic Scala code because Option.fold was only introduced in Scala 2.10.
  • Fold on Option is not obvious to most developers.
  • Option.fold is not readable.
    • Reverses the order of Some vs None.
    • Putting the default condition first there makes it not as intuitive.
    • When code gets long, the lack of an obvious boundary with two closures is confusing. (“} {” compared to getOrElse)
  • Fold is a more functional idiom in general.

It seems people are getting used to functional idioms such as map and filter, but still are reluctant to accept more functional idioms such as Option.fold.

I prefer Option.getOrElse/map because I think putting the default value first is not intuitive and much of Scala code is already written with Option.getOrElse/map. However, both options are fine as long as only one style is used through the project. Consistency is more important than taste!

A Hindley-Milner type inference implementation in OCaml

Hindley–Milner (HM) is a classical type system for the lambda calculus with parametric polymorphism. If you want to know what HM is and why it is cool, please read Daniel Spiewak’s article “What is Hindley-Milner? (and why is it cool?)

There are multiple implementations of HM in many different languages.

I reimplemented the algorithm in OCaml guided by Scala and Python implementations. All these implementations are based on the Modula-2 implementation of the Cardelli 1987 paper. Because the algorithm described in the paper depends on mutable states for optimization, it is not easy to implement it directly in a purely functional programming language like Haskell.

The program implements a small functional programming language with no concrete syntax. The language consists of identifier, lambda, application, let and let rec. The type inference algorithm reconstructs the type of the given example expressions in the context of some predefined types. The program produces the following results when executed:

letrec factorial = fn n => cond zero n 1 times n factorial pred n in factorial 5 : int
fn x => pair x 3 x true : Type mismatch bool != int
pair f 4 f true : Undefined symbol f
let f = fn x => x in pair f 4 f true : (int * bool)
fn f => f f : Recursive unification
let g = fn f => 5 in g g : int
fn g => let f = fn x => g in pair f 3 f true : (a -> (a * a))
fn f => fn g => fn arg => g f arg : ((c -> d) -> ((d -> b) -> (c -> b)))

Installing OCaml+OPAM+utop+Core Library on Ubuntu Saucy

Install OCaml+OPAM

There are PPAs available that are pinned to specific revisions of OCaml and OPAM.

add-apt-repository ppa:avsm/ppa
apt-get update
apt-get install ocaml opam

Or you can install pre-compiled versions using Binary installer.

wget https://raw.github.com/ocaml/opam/master/shell/opam_installer.sh
sh ./opam_installer.sh /usr/local/bin

Install utop

utop is an improved toplevel for OCaml. You can install utop using opam installl.

opam install utop

Install Core library

Core library is Jane Street’s alternative to the standard library. You can install Core library using opam install

opam install core
opam install async
opam install core_extended

Put the following in your .ocamlinit file to use Core and its associated libraries and syntax extensions in the toplevel:

#use “topfind”
#thread
#require “dynlink”
#camlp4o
#require “bin_prot.syntax”
#require “sexplib.syntax”
#require “variantslib.syntax”
#require “fieldslib.syntax”
#require “comparelib.syntax”
#require “core”
#require “async”
#require “core_extended”
#require “core.top”
open Core.Std

A Critique of Homoiconicity

Homoiconicity is a property of Lisp in which program and data have the same representation, namely S-expressions. This gives Lisp uniquely strong power to write programs that manipulate programs, such as interpreters and compilers. But it also makes it easy for a novice programmer to be confused between program and data.

Philip Wadler described several such confusions in his paper, “A critique of Abelson and Sussman – or – Why calculating is better than schemin“. The original paper compared Scheme and Miranda, but I changed them to more modern languages Clojure and Haskell respectively.

Clojure lists are not self-quoting

In Clojure, lists are not self-quoting while numbers as data are self-quoting. For example, 3 evaluates to 3 itself, but (1 2 3) evaluates as a function call, not as a list. To include list as a datum, one must quote it as in (quote (1 2 3)) or ‘(1 2 3).

user=> 3
3
user=> (1 2 3)
ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn user/eval5 (NO_SOURCE_FILE:4)
user=> (quote 1 2 3)
1
user=> ‘(1 2 3)
(1 2 3)

When combined with the substitution model to explain the evaluation of (list (list 1 2) nil), this is quite confusing because the intermediate steps are no longer legal Lisp expressions.

(list (list 1 2) nil)
-> (list (1 2) nil)
-> (list (1 2) ())
-> ((1 2) ())

In Haskell, one just writes [[1,2], []]. It is already a value, so there is no evaluation to explain.

Further confusion with quote

Surprisingly, (peek ”abracadabra) evaluates to quote.

user=> (peek ”abracadabra)
quote

because (peek ”abracadabra) expands to (peek (quote (quote abracadabra))) which evaluates as in

(peek (quote (quote abracadabra)))
-> (peek (quote abracadabra))
-> quote

Evaluating too little or too much

As Clojure represents program and data in the same form, one often mistakenly evaluates either too little or too much.

(peek (quote (a b)))

The result of evaluating the expression above is a. However, many programmers give the answer quote (too little) or the value of the variable a (too much).

In Haskell, no such problem arises because there is no quote.

Other confusions with lists

A list containing just one element x is (list x) or (x) in Clojure, while it is [x] in Haskell. As people tend to drop parenthesis, (x) often mistakenly read as x.

People also get confused between cons and list because (cons x y) and (list x y) in Clojure look similar while x:y and [x, y] in Haskell look distinct.

These are just notational differences, but some notations certainly make people more confused than others.

Syntax

Programming language research does not talk much about syntax because the preference of one syntax over the others depends much on the taste of a programmer. But the famous S-expression certainly hinders reasoning with algebraic properties, such as associativity. Also most people who are accustomed to infix notations taught at math classes find it hard read S-expressions at first.

Conclusion

A Lisp family language such as Clojure traditionally has been regarded as a good introductory language to students or novice programmers thanks to its simple syntax (or lack of) and semantics. But one of its powerful language feature, homoiconicity is a mixed blessing for beginners as it makes it hard to reason about programs. Maybe this is the reason why other functional programming languages such as OCaml, F#, Scala and Haskell end up not including homoiconicity in the language.

Functions are Objects in Scala

Scala is a multi-paradigm language which supports both object-oriented programming and functional programming. So Scala has both functions and objects. But in the implementation level, function values are treated as objects. The function type A => B is just an abbreviation for the class scala.Function1[A, B],

package scala
trait Function1[A, B] {
    def apply(x: A): B
}

There are traits Function2, …, Function22 for functions which take more arguments.

An anonymous function such as (x: Int) => x * x is expanded to

new Function1[Int, Int] {
    def apply(x: Int) = x * x
}

A function call such as f(a, b) is expanded to f.apply(a, b).

So the translation of

val f = (x: Int) => x * x
f(7)

would be

val f = new Function1[Int, Int] {
    def apply(x: Int) = x * x
}
f.apply(7)

This trick is necessary because JVM does not allow passing or returning functions as values. So to overcome the limitation of JVM (lack of higher order functions), Scala compiler wraps function values in objects.

The following method f is not itself a function value.

def f(x: Int): Boolean = ...

But when f is used in a place where a Function type is expected, it is converted automatically to the function value

(x: Int) => f(x)

This is an example of eta expansion.

The code examples in this article are taken from Martin Odersky’s Functional Programming Principles in Scala lecture.