Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


3장

배열

  • 파라미터화(parameterization) : new를 사용해 객체를 인스턴스화 할때에 값과 타입을 파라미터로 전달하는 것
val greetStrings: Array[String] = new Array[String](3)
  • 인스턴스 타입은 타입 파라미터 까지만 포함됨 : (3) 은 제외
val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "World!\n"
for (i <- 0 to 2)
  print(greetStrings(i))
  • 메소드가 파라미터를 하나만 요구하는 경우, 그 메소드를 점(.)과 괄호 없이 호출할 수 있다. 단, 호출 대상 객체가 명시적으로 지정되어 있는 경우에 가능 0 to 2 === (0).to(2)
  • 스칼라는 전통적인 의미의 연산자가 없으므로 연산자 오버로드를 제공하지 않으며, 대신 +, -, *, / 등의 문자를 메소드 이름으로 사용 가능 1 + 2 === (1).+(2)
  • 배열도 평범한 인스턴스 : 변수 뒤에 하나 이상의 값을 괄호로 둘러싸서 호출하면, 스칼라는 변수에 대해 apply 메소드 호출 greetStrings(i) === greetStrings.apply(i)
  • 변수 뒤에 괄호로 둘러싼 인자들이 있는 표현식에 할당을 하면, 스칼라는 update 메소드를 호출 greeStrings(0) = "Hello" === greetStrings.update(0, "Hello")
val numNames = Array("zero", "one", "two")
  • Array의 동반 객체에 정의된 apply라는 팩토리 메소드 호출

리스트

같은 타입의 변경 불가능한 시퀀스

val oneTwo = List(1,2)
val threeFour = List(3,4)
val oneTwoThreeFour = oneTwo ::: threeFour
  • 리스트 자체를 변환하지 않고 새 값을 갖는 리스트를 만든다.
val twoThree = List(2,3)
val oneTwoThree = 1 :: twoThree
  • : 로 이름이 끝나는 메소드는 오른쪽 피연산자에 대해 호출한다.
val oneTwoThree = 1 :: 2 :: 3 :: Nil
  • List() === Nil

리스트 메소드

  • List() / Nill : 빈 리스트
  • List(1,2,3) / 1 :: 2 :: 3 :: Nill : 원소들로 구성된 리스트
  • List(1, 2) ::: List(3, 4) : 두 리스트를 연결한 리스트
  • thrill(2) : thrill 리스트의 세번째 원소
  • thrill.count(s => s.length == 4) : 길이가 4인 것의 갯수
  • thrill.drop(2) : 처음 두 원소를 뺀 리스트
  • thrill.dropRight(2) : 마지막 두 원소를 뺀 리스트
  • thrill.exists(s => s == "until") : "until"이 리스트에 있는지 여부
  • thrill.filter(s => s.length == 4) : 길이가 4인 것들의 리스트
  • thrill.forall(s => s.endsWith("l")) : 모든 원소가 끝이 "l"로 끝나는지 여부
  • thrill.foreach(s => print(s)) : 리스트의 모든 원소에 대해서 print 실행
  • thrill.foreach(print) : 상동
  • thrill.head : 첫번째 원소
  • thrill.init : 마지막 원소 제외한 나머지
  • thrill.isEmpty : 리스트가 비어있는지 여부
  • thrill.last : 마지막 원소
  • thrill.length : 리스트의 길이
  • thrill.map(s => s + "y") : 각 원소 뒤에 "y"를 추가한 리스트
  • thrill.mkString(", ") : 원소들을 ", "로 연결한 문자열
  • thrill.remove(s => s.length == 4) : 길이가 4인 것을 제외한 리스트
  • thrill.reverse : 원소를 역순으로 한 리스트
  • thrill.sort((s, t) => s.charAt(0).toLower < t.charAt(0).toLower) : 알파벳 순서 정렬한 리스트
  • thrill.tail : 첫번째 원소를 제외한 리스트

튜플

다른 타입의 원소들을 갖는 변경 불가능한 시퀀스

val pair = (99, "Luftballons")
println(pair._1) // 99
println(pair._2) // Luftballons
  • apply 메소드는 항상 동일한 타입 객체를 반환하기 때문에 pair(0)과 같이 할 수 없음.
  • 하스켈, ML 등의 영향으로 인덱스가 1부터 시작

집합과 맵

변경가능 / 불가능 모두 제공

변경 불가능 Set

var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"

변경 가능 Set

import scala.collection.mutable.Set
val movieSet = Set("Hitch", "Poltergeist")
movieSet += "Shrek"

변경 불가능 Map

val romanNumeral = Map(
    1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
)
  • -> 메소드를 호출하면 해당 객체를 키로하고 인자를 값으로 하는 원소가 2개인 튜플을 생성

변경 가능 Map

import scala.collection.mutable.Map
val treasureMap = Map[Int, String]()
treasureMap += (1 -> "Go to Island.")

함수형 스타일

  • 코드에 var가 있으면 명령형 스타일, 아니면 함수형 스타일
  • val, 불변객체, 부수 효과 없는 메소드를 먼저 사용해보고, 꼭 필요한 경우에만 var, 가변객체, 부수 효과 있는 메소드를 사용하라

파일

import scala.io.Source
if (args.length > 0) {
    for (line <- Source.fromFile(args(0)).getLines())
        println(line.length + " " + line)
}
else
    Console.err.println("Please enter filename")


Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.

2장

인터프리터 사용법

  • 대화형 셸 $ scala
  • res번호 식별자는 변수 처럼 사용 가능 ex> res0 * 3
  • println("Hello, world!")

변수 정의

  • val : 재할당 불가능
  • var : 재할당 가능
  • 타입 추론으로 타입을 지정하지 않아도 됨
val msg = "Hello"
val msg2: java.lang.String = "Hello"
val msg3: String = "Hello"

var msg4 = "Hello"
msg4 = "World"

함수 정의

def max(x: Int, y: Int): Int = {
    if (x < y) x
    else y
}
  • def : 함수 정의 시작
  • max : 함수 이름
  • (x: Int, y: Int) : 인자 항목
  • Int : 함수 결과 타입
  • = : 등호
  • { } : 함수 본문

Unit 결과 타입은 함수가 의미있는 결과를 반환하지 않는다는 의미

스칼라 스크립트

  • 실행 방법 예 $ scala hello.scala
  • 명령행 인자 접근 : args args(0), args(1), ...

반복 / 분기

  • while while (i < args.length)
  • if if (i != 0) print(" ")
  • foreach args.foreach(arg => println(arg)) args.foreach(println)
  • for for (arg <- args) println(arg)

함수 리터럴

(x: Int, y: Int) => x + y
  • (x: Int, y: Int) : 인자 항목
  • => : 오른쪽 화살표
  • x + y : 함수 본문

함수 리터럴이 인자를 하나만 받는 문장인 경우에는 해당 인자에 이름을 붙일 필요가 없다 (8.6절)


Programming in Sala 책으로 스칼라 스터디하면서 정리했던 내용이다. 지금은 3판 번역본도 나왔지만, 약간 앞서서 스터디를 시작해서 2판으로 진행하고 있다.


1장

스칼라

  • 확장 가능 언어
  • 객체지향과 함수형 프로그래밍 개념을 정적 타입 언어에 합쳐 놓은 언어

확장성

타입

  • 언어가 기본 지원하는 것처럼 느껴지는 스칼라 표준 라이브러리로 타입을 제공
  • 라이브러리로 새로운 타입을 만들어 내장 타입처럼 편하게 사용할 수 있음

제어구조

  • 액터 모델을 구현하는 라이브러리를 제공하여 스칼라 내장 구성요소처럼 사용함
  • 새로운 분야에 대한 추상화를 설계/구현하고 언어가 원래 지원하는 것처럼 사용 가능

확장성의 이유

객체지향적

  • 스칼라는 순수한 형태의 객체지향 언어 : 모든 값은 객체, 모든 연산은 메소드
  • Trait : 끼워넣기 좋은 구성 요소

함수형

  • 함수는 1급 계층 값 : 인자로 넘길 수 있고, 변수에 저장 가능하고, 함수에서 반환 가능
  • 메소드에는 부수 효과가 없어야 함

스칼라 장점

  • 호환성 : 자바 메소드 호출, 필드 접근, 클래스 상속, 인터페이스 구현 가능
  • 간결함 : 적은 얼개 코드 사용, 타입 추론, 라이브러리 정의 도구
  • 고수준 : 높은 수준 추상화 가능
  • 정적타입
    • 타입 추론으로 장황함 회피, 패턴 매치와 타입을 쓰고 합성하는 방법으로 유연성 확보
    • 프로퍼티 검증, 안전한 리펙토링, 문서화

스칼라의 뿌리

  • 문법 : 자바, C#
  • 일관성 있는 객체 모델 : 스몰토크, 루비
  • 보편적인 내포 : 알골, 시뮬라, Beta, gbeta
  • 메소드 호출, 필드 선택을 통일하게 처리하는 단일 접근 원칙 : Eiffel
  • 함수형 프로그래밍 접근 방식 : SML, OCaml, F#
  • 스칼라 표준 라이브러리의 고차 함수 중 다수 : ML, 하스켈
  • 암시적 파라미터 : 하스켈의 타입 클래스
  • 액터 기반 동시성 라이브러리 : 얼랑


PHP Best Practices를 읽다가 내친 김에 번역을 했는데, 이걸로 모던PUG에서 "UTF-8 in PHP"라는 주제로 발표까지 하게 되었다. 별 내용은 아니라서 발표할 것까지는 없다고 생각했는데, 발표 준비를 한 덕분에 PHP의 UTF-8 지원에 대해서 정리해볼 수 있었다.

PHP7에서 기본 문자열이 유니코드(UTF-8)로 되는 것으로 알고 있었기때문에 곧 필요없어질 내용이라고 생각했는데, 조사하다보니 PHP7에서도 유니코드 지원 상황은 변함이 없는 것으로 확인되었다. RFC에 UString 클래스 제안이 있던 정도였지만, 이마저도 통과되지 않았다.

가장 흥미로웠던 것은 MySQL의 "utf8mb4" 인코딩이었다. UTF-8에서 4바이트를 사용하는 문자가 있다는 것은 어렴풋이 알고 있었지만, 보통 사용하는 "utf8" 인코딩은 4바이트 문자를 저장하지 못한다는 사실은 모르고 있었다. 이것 때문에 UTF-8의 "Plane"이라는 것에 대해서도 알아보게 되었다.

별 내용은 없지만, 아래는 발표때 사용한 슬라이드이다.


월의 몇 주차를 어떻게 계산해야할지 찾아보았지만, 마땅한 표준은 존재하지 않았다. 하지만, 해당 년도의 첫 주 계산은 ISO 8601 표준에 정해져있다는 것을 알게되었다. ISO 8601 표준의 위키피디아 해당 항목을 보면 매 해의 첫주의 정의는 아래와 같이 설명될 수 있다고 한다,

  • the week with the year's first Thursday in it (the formal ISO definition),
  • the week with 4 January in it,
  • the first week with the majority (four or more) of its days in the starting year, and
  • the week starting with the Monday in the period 29 December – 4 January.

가장 간단한 설명은 1월 4일이 포함된 주가 첫주라고 보면 된다는 것이다. 년에 관한 것만 나와있어서 그다지 도움은 안됐지만, 한가지 또 알게 된 사실은 ISO 8601 표준에서 "주"는 월요일에서 시작해서 일요일로 끝나는 것으로 본다.

하지만, 달력에는 주로 일요일이 시작인 것처럼 나와있는 경우가 많아서 한주의 시작요일을 변경할 수 있도록 해서 한번 만들어봤다.


'Developing' 카테고리의 다른 글

PHP에서의 UTF-8 지원  (0) 2015.06.08
PHP 에러(Error)를 예외(Exception)로 변환하기  (0) 2015.01.30
PHP 에서 IE 구분  (0) 2015.01.19

PHP에서 논란이 많은 것 중에 하나가 Exception 이외에 핸들링하기 어려운 Error가 또 있다는 것이다. 작은 규모의 웹 애플리케이션에서는 별 문제가 되지않고, 오히려 장점도 있을 수 있겠지만, 규모가 조금 큰 웹 애플리케이션에서는 문제가 될 수 있고, 특히 서버 콘솔 프로그램을 만들었다면 치명적일 수 있는 문제이다.

물론 Error도 set_error_handler()를 사용해서 핸들링할 수 있다고는 하지만, Exception 처리와는 별개로 처리하다보면 코드 흐름이 엉망이 되기가 쉽기도 하고 해서 Exception으로 변환해서 처리를 하는게 낫다고 생각이 들때가 있는데, 그럴때는 아래와 같은 코드를 사용하면 된다.

set_error_handler(function ($errno, $errstr, $errfile, $errline) {

    throw new ErrorException($errstr, $errno, $errno, $errfile, $errline);

});

참고 : http://www.sitepoint.com/forums/showthread.php?632913-Converting-native-PHP-errors-to-Exceptions

"개발자 좀 살려주세요!", "굿바이 IE6" 등의 캠페인 등으로 브라우저 업그레이드를 해왔지만, 그때뿐이고 Active-X로 점철된 금융권 사이트와 정부 사이트로 인해서 우리나라에서 IE는 쉽게 버전 업그레이드 할 수 없다. 때문에 최신 버전이 이미 10대를 넘었지만, 아직도 7을 써야만 하는 사이트가 있는 우리나라 현실에서는 아직도 IE 버전 체크가 필요한 경우가 자주 있다.

버전을 구분하는 방법은 여러가지가 있지만, 다음은 간단히 PHP 코드를 사용해서 구분하는 예제 코드이다.

if (preg_match('/(?i)msie [2-7]/',$_SERVER['HTTP_USER_AGENT']))
{
// IE 7 이하
}

elseif (preg_match('/(?i)msie [8-9]/',$_SERVER['HTTP_USER_AGENT']))
{
// IE 8~9
}
else
{
// IE 10 이상
}


Yeoman을 사용하여 프론트엔드 개발을 하면서 한가지 아쉬운 점이 있었다. 단일 페이지로 구성된 웹 애플리케이션이라면 문제가 없겠지만, 페이지가 많아지면서 중복 코드가 많아진다는 점이었다. HTML에도 include같은 기능이 있으면 좋겠지만, 그것은 불가능하니 자바스크립트의 힘을 빌리거나 조립해서 하나의 화면을 만들어줄 서버 프로그램이 필요하다. 하지만, 프론트엔드 개발 진행중에 서버 코드와 별개로 HTML/CSS로만 작업을 하려다보니 발생한 문제인데, Yeoman의 구성인 Grunt에서 사용할 수 있는 grunt-bake를 사용하면 해결할 수 있었다.

간단히 다음 명령으로 설치할 수 있다.

$ npm install grunt-bake --save-dev

이제 Gruntfile.js 파일을 수정해야 한다.

grunt.initConfig( { bake: { your_target: { options: { // Task 옵션을 추가한다. }, files: { // 파일을 아래와 같은 형식으로 추가한다.
// "결과 파일": "작업 파일" "dist/index.html": "app/index.html" // etc ... } }, }, } )

위와 같이 설정을 해주면, app/index.html 내용은 아래와 같은 형식으로 작성하게 된다.

<html> <head></head> <body> <!--(bake includes/header.html)-->
<div class="content"> ... </div> <!--(bake includes/footer.html)--> </body> </html>

물론, includes/header.html 파일과 includes/footer.html 파일도 각 부분에 들어갈 내용으로 작성해준다.

이제 빌드를 위한 Task 설정을 수정할 차례이다. server Task와 build Task에 아래와 같이 "bake:build"를 추가해준다.

grunt.registerTask('server', function (target) {
    if (target === 'dist') {
        return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
    }
    grunt.task.run([
        'clean:server',
        'bake:build',
        'concurrent:server',
        'connect:livereload',
        'open',
        'watch'
    ]);
});
grunt.registerTask('build', [
    'clean:dist',
    'bake:build',
    'useminPrepare',
    'concurrent:dist',
    'concat',
    'cssmin',
    'uglify',
    'copy:dist',
    'rev',
    'usemin'
]);

그리고 livereload를 위해서 watch 설정에 페이지 부분 내용이 들어갈 폴더를 추가해준다.

watch: {
    compass: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
        tasks: ['compass:server']
    },
    styles: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
        tasks: ['copy:styles']
    },
    bake: {
        files: [ "<%= yeoman.app %>/includes/**" ],
        tasks: "bake:build"
    },
    livereload: {
        options: {
            livereload: LIVERELOAD_PORT
        },
        files: [
            '<%= yeoman.app %>/*.html',
            '.tmp/styles/{,*/}*.css',
            '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
            '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
    }
}

이제 모든 설정이 끝났다. 페이지에서 중복된 코드는 반복하지 말고, 모두 부분 HTML로 작성하면 된다.

긴 웹페이지에서 A태그를 사용해서 섹션별로 이동할 수 있도록 만들었는데, fixed로 고정되어있는 헤더가 이동한 섹션 일부를 가리는 문제가 발생했다. 다음은 이런 문제가 생기는 예제 코드이다. (문서 내용은 한글 Lorem Ipsum을 사용했다.)

위의 코드에서 헤더에 있는 각 링크를 클릭하면 동일한 페이지의 해당하는 섹션으로 이동하는데, 상단의 헤더가 고정되어 가리게 된다. 이 문제를 해결하기 위해서 스크립트를 사용하지 않고 CSS 스타일만으로 해결되는 간단한 방법이 없을지 여러 방법들을 테스트하다가 아래와 같이 코드를 작성하였다.

이 해결 방법의 핵심은 이동할 섹션 바로 앞쪽에 보이지 않는 요소를 헤더와 동일한 높이로 만들어서 섹션 id를 지정한다는 것이다. 바로 아래와 같은 코드이다.

/* CSS */
.anchor{
display: block;
height: 100px; /* 헤더의 height와 동일한 값 */
margin-top: -100px; /* 헤더의 height와 동일한 값 */
visibility: hidden;
}
<!-- HTML -->
...
<span class="anchor" id="section1"></span>
<div class="section"> ...

참고 : http://pixelflips.com/blog/anchor-links-with-a-fixed-header

"평생 개발자로 먹고 살 수 있다"는 부제를 가진 이 책은 정말 제목대로 유지보수하기 어렵게 코드를 작성하는 방법이 나와있다. 그렇다고 부제처럼 자기만 알아볼 수 있는 코드를 작성해서 평생 짤리지 않도록 하는 방법을 다뤘다기 보다는, 나쁜 예를 보여서 이렇게는 하지 말자고 반성하게 하는 거울과 같은 역할을 하는 책이다.

읽다보니 정말 별의별 상황이 다 있는데, 어찌보면 내가 겪어본 것도 있고, (의도적이 아니었더라도) 내가 하고 있어서 반성하게 하는 부분도 있었다. 몇가지를 옮겨보면 아래와 같다.

"언어의 규칙이 허용하는 범위 내에서 클래스, 생성자, 메소드, 멤버 변수, 파라미터, 지역 변수에 같은 이름을 사용하자. 이에 안주하지 말고 더 나아가서 {} 블록 내에서 이미 사용되고 있는 지역 변수명을 재사용할 수 있는지 고민하자."

"이유는 빼고 어떻게에 대해서만 문서화하라"

"피트, 미터, 통과 같은 측정 단위를 변수, 입력, 출력, 매개변수에 문서화는 절대 하지 않는다."

"에러나, 기기 크래쉬, OS 결함을 처리하는 코드는 절대 테스트 하지 않는다. OS가 반환하는 코드도 검사하지 않는다. OS가 반환하는 코드는 실행에 아무 도움이 되지 않으며 우리 테스트 시간만 오래 걸리게 한다. 게다가 우리 코드가 디스크 에러, 파일 읽기 에러, OS 크래쉬와 같은 모든 경우를 적절하게 처리하는지 어떻게 일일이 테스트 할 수 있겠는가? 도대체 왜 컴퓨터 시스템을 신뢰할 수 없는 것처럼 생각하고 교수대 같은 것이 제대로 동작하지 않는지 테스트해야 하는지 이해할 수가 없다. 최신 하드웨어에서는 에러가 발생하지 않는다."

"프로그램이 좀 느리다고? 고객에게 더 빠른 컴퓨터를 사라고 말하자. 성능 테스트를 수행했다면, 문제가 일어나는 지점을 찾았을 것이다. 아마 문제를 해결하려면 알고리즘을 변경해야 할 것이고 제품 전체를 완전히 다시 설계해야 하는 경우도 생길 수 있다."

"개발 도구에 포함된 라이브러리를 모른척해야 한다. 비주얼 C++을 사용한다면 MFC나 STL의 존재를 무시하고 문자열이나 배열을 손수 작정할 수 있다. 이렇게 하면서 자신도 모르게 포인터 기술이 좋아지고 동시에 코드를 확장하려는 시도를 좌절시킬 수 있다."

이 책은 무료 e-book으로 공개되어 다운로드도 가능하고, 웹사이트에서 기사 형식으로도 공개되어있다.

e-Book


+ Recent posts