<?xml version="1.0" encoding="UTF-8" ?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
	<channel>
		<title>비공정</title>
		<link>https://manalith.org/zbxe/</link>
		<description></description>
		<atom:link href="https://manalith.org/zbxe/rss" rel="self" type="application/rss+xml" />
		<language>ko</language>
		<pubDate>Thu, 04 Jun 2026 17:48:55 +0000</pubDate>
		<generator>Rhymix</generator>
			<item>
			<title>간단한 디데이 계산기</title>
			<link>https://manalith.org/zbxe/blog/1205784</link>
				<description>&lt;p&gt;디데이 계산기 사이트를 자주 활용하는 편인데, 매번 성인 만화 광고 같은게 가득 뜨는게 싫어서 간단한 계산기 사이트를 하나 만들었다. 검색 결과는 URL에 반영되므로 즐겨찾기하면 된다. 부가기능을 하나씩 붙여볼 예정. iOS 시계 앱의 알람 탭처럼 개인화된 홈을 만드는 것을 고려하고 있다.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://kidarim.day/&quot; target=&quot;_blank&quot;&gt;https://kidarim.day/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;참고로 잔여 생존일 기능은 최근에 세상을 떠난 친구 때문에 만든 것이다. 자세한 이야기는 다음에 &lt;/p&gt;</description>
		<category>비공정</category>	<category>개인</category>	<category>기다림</category><category>디데이</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1205784</guid>
	<comments>https://manalith.org/zbxe/blog/1205784#comment</comments>			<pubDate>Sat, 26 Mar 2022 12:14:29 +0000</pubDate>
		</item><item>
			<title>가벼운 베이스기타</title>
			<link>https://manalith.org/zbxe/blog/1205656</link>
				<description>&lt;p&gt;스윙 G1 액티브 모델을 쓰면서 꽤 오랫동안 가벼운 베이스기타를 찾다가, 최근에야 샌드버그 TT4 슈퍼라이트 모델에 정착했다.&lt;br /&gt;나처럼 무게 위주로 악기를 찾는 사람들이 있는 것 같아서 그간 배운 지식들을 적어본다.&lt;br /&gt;&lt;/p&gt;&lt;h5 id=&quot;h1647770315364&quot;&gt;필요 지식&lt;/h5&gt;&lt;p&gt;첫째로, 같은 베이스 모델이라고 다 무게가 같지 않다. 휴대폰이나 노트북 같은 IT기기와 다른 점인데, 지판이나 바디 등 각 파트에 사용된 목재나 부속이 완전히 같은 제품이라도 나무 자체의 특성으로 인해 완성품마다 무게가 제각각이다. 이게 악기 스펙에 무게가 잘 없는 이유다. 버즈비 리뷰 같은 제품 리뷰에서 보여주는 무게도 해당 모델이 아니라 리뷰에 사용된 제품의 무게라고 보아야한다. 정확한 수치 혹은 범위는 제조사나 판매처에 문의하거나 구글링해야한다. 구글에서 모델명에 weight 같은 키워드를 붙여 검색해보면 talkbass 같은 싸이트에서 각자 가진 제품의 무게를 이야기해보는 글타래들이 많이 있고, 해외 판매숍에서 판매중인 제품의 정확한 수치들을 적어놓는 경우도 있으니 짐작은 할 수 있다. 다만 범위의 경우 7~800g까지 차이나는 경우도 있다고 한다. &lt;a href=&quot;https://www.mule.co.kr/bbs/info/base?v=v&amp;amp;idx=55042836&quot; target=&quot;_blank&quot;&gt;(댓글 참고)&lt;/a&gt; 국내의 경우 소량 주문제작된 제품 위주로 취급하는 라이딩베이스 같은 곳은 해당 제품 스펙에 무게를 기재하고 있다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;그리고 가벼운 베이스 찾다 보면 자주 보게 되는 키워드들이 있는데 다음과 같다.&lt;br /&gt;- 현의 갯수. 보통 같은 모델이 4현과 5현으로 나오는 경우가 있는데 이 경우 전자가 항상 가볍다.&lt;br /&gt;- 헤드리스. 말 그대로 헤드가 없는 악기인데 처음 보면 모양이 다소 특이하며, 전반적인 무게 뿐 아니라 넥다이브 방지에도 잇점이 있다고 한다.&lt;br /&gt;- 숏스케일. 일반적으로 34인치 베이스를 많이 쓰지만 숏스케일 베이스는 32인치나 30인치로 짧다. 대표적인 악기로 펜더 머스탱 베이스가 있다.&lt;br /&gt;- 챔버드, 할로우, 세미할로우. 솔리드 바디처럼 속이 꽉 차있지 않고 속을 파낸 악기들을 말하는데, 뜻은 제각기 조금씩 다르다. 예로 호프너의 바이올린 베이스가 있다. 어쿠스틱 베이스도 마찬가지로 상대적으로 가볍다.&lt;br /&gt;- 바디 재질. 베이스우드가 가볍다는 이야기가 많이 보인다. 최근에는 비전통적인 재료들도 많이 보이는데 최근 나온 펜더 프로페셔널 2 제품군에는 Roasted pine이 새로 추가되어 가볍다고 홍보하고 있고, 내가 쓰는 TT4 SL의 경우 Paulownia(오동나무) 바디에 Norway Maple(일반 메이플보다 가볍다고 함)이다.&lt;br /&gt;&lt;br /&gt;참고로, &#039;가볍다&#039;의 기준이 천차만별이기 때문에 너무 큰 기대를 하면 안된다. 그리고 위에 언급한 특성들을 가진다고 반드시 가벼운 것도 아니다. 예를 들어 숏스케일이거나 세미할로우 바디이면서 3kg대 후반일 수 있다. 3kg대 후반의 무게는 누군가에겐 가볍게 보일 수도 있지만 이 글을 보는 사람에게는 충분치 않을 수 있다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;그리고 위에 열거한 특성들은 소리와 편의성에 일부 영향을 미친다. 숏스케일이나 가벼운 바디는 음의 울림 길이에 영향을 미칠 수 있는데, 이는 라이딩베이스 유튜브 채널의 Lionel(VS4의 숏스케일 모델)이나 Superlight 제품 리뷰에 설명이 잘 되어 있다. 헤드리스의 경우 일반적이지 않은 스트링을 요구하는 경우가 있다고 알고 있다.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;그밖에 고려사항들로, 패시브 악기의 경우 액티브에 비해 프리앰프가 없어 가볍다는 이야기가 있다. 그리고 헤드머신 같은 금속 부속의 경우에도 경량 제품들이 존재한다. 힙샷 울트라라이트 헤드머신 같은. 샌드버그 커스터마이징을 고려하면서 들은 말로는, 같은 모델의 경우 도장을 두껍게 입힌 글로시 피니시보다 매트 피니시가 제법 큰 차이로 가볍다고 한다. 그리고 바디만 가볍고 넥은 그렇지 않은 경우 넥다이브가 생기지 않는지도 확인해볼 필요가 있는 것 같다. 역시 가장 좋은 건 써보고 사는 것일 듯.&lt;br /&gt;&lt;/p&gt;&lt;h5 id=&quot;h1648293315646&quot;&gt;고려한 제품들&lt;/h5&gt;&lt;p&gt;내 경우 4현 베이스만 고려했기 때문에 다음 제품들을 고려했고, 국내에서 못 구하는 제품과 출시연도가 아주 오래된 제품들은 고려하지 않았다.&lt;br /&gt;&lt;br /&gt;- 스트랜드버그 Boden Bass Prog 4. 헤드리스에 스트랜드버그 특유의 넥. 리뷰제품 기준 2.77kg &lt;a href=&quot;https://www.youtube.com/watch?v=ehcfdj3b4zg&quot; target=&quot;_blank&quot;&gt;기어타임스 리뷰&lt;/a&gt; (무게는 5:20). 2022년 현재 버즈비 기준 신품가는 3백만원대 중반 정도..&lt;br /&gt;- 아이바네즈 EHB1005MS. 숏스케일, 헤드리스에 리뷰제품 기준 3.24kg &lt;a href=&quot;https://www.youtube.com/watch?v=_6tqRRUyWOM&quot; target=&quot;_blank&quot;&gt;기어타임스 리뷰&lt;/a&gt; (무게는 2:36) 네이버 쇼핑 기준 신품가 130만원대. 5현에 멀티스케일이지만 고려했던 이유는 가볍고 상대적으로 상대적으로 저렴했기 때문. 4현에 비 멀티스케일 모델로 EHB1000이 있는데 아쉽게도 국내 출시되지 않았다.&lt;br /&gt;- 트래블러기타 TB-4P. 32인치 스케일에 헤드리스. 70만원대. 스펙상 2.9kg. &lt;a href=&quot;https://smartstore.naver.com/travelerguitar/products/3756866285&quot; target=&quot;_blank&quot;&gt;네이버 스토어 제품 스펙&lt;/a&gt; 알아본 제품들 중에 가장 저렴. 헤드폰 앰프까지 내장되어 여행용으론 정말 좋을 듯 하다.&lt;br /&gt;- 샌드버그 TT4 Superlight. 내가 구매한 모델은 2.3kg대. &lt;a href=&quot;https://www.youtube.com/watch?v=FiUzDSP6Ajk&quot; target=&quot;_blank&quot;&gt;라이딩베이스 리뷰&lt;/a&gt;&amp;nbsp; 3백만원이 조금 못 되었다. 참고로 바디에 Paulownia를 쓰기 전 나온 예전 Superlight 제품들은 3kg 정도로 무게 차이가 좀 있다.&lt;br /&gt;&lt;br /&gt;참고로 기존에 쓰던 스윙 G1 액티브는 무게를 재어보니 4.2kg. 일반적인 베이스 무게가 어떤지 궁금하면 버즈비에서 올린 &lt;a target=&quot;_blank&quot; href=&quot;http://www.buzzbee.co.kr/shop/data/buzzbeetv/bass_weight_2015-2016.pdf&quot;&gt;기어타임스 2015-2016 리뷰 제품 무게 정리&lt;/a&gt;를 참고하면 된다. &lt;br /&gt;&lt;/p&gt;&lt;h5 id=&quot;h1648297338873&quot;&gt;기변 후기&lt;/h5&gt;&lt;p&gt;최종적으로 TT4 SL을 선택한 이유는, 기존에 쓰던 스윙 G1 액티브 재즈베이스의 완전한 상위호환으로 달리 고민할 요소가 없었기 때문이다. 가격은 확실히 적지 않은 부담이었지만, 목/어깨 건강이 늘 안좋으니 앞으로도 무거운 악기를 선택할 가능성이 없고 동일 무게 범위 안에서는 선택지가 많지도 않아서 앞으로도 기추면 모를까 기변할 일은 없을 거라 생각하니 결정은 어렵지 않았다. 바꾸고 나니 다른건 다 너무 만족스러운데, 단지 나뭇결이 그대로 드러나는 다소 칙칙한 외관과, 사용된 나무와 매트피니시로 인한 상대적으로 약한 내구성(오죽하면 리어픽업 위에 붙이라고 스티커를 준다)에 위로 올라올수록 프론트픽업에 바짝 달라붙는 캘리포니아2 모델 특유의 픽가드 디자인까지 더해 벌써 손톱자국이 두개나 나버린게 좀 가슴아프다.&lt;br /&gt;&lt;/p&gt;</description>
		<category>비공정</category>	<category>생각</category>	<category>베이스</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1205656</guid>
	<comments>https://manalith.org/zbxe/blog/1205656#comment</comments>			<pubDate>Sat, 26 Mar 2022 11:53:22 +0000</pubDate>
		</item><item>
			<title>퍼펫티어(혹은 헤드리스 크롬)에서 구글 폰트의 나눔 명조가 보이지 않는 문제</title>
			<link>https://manalith.org/zbxe/blog/1185564</link>
				<description>&lt;p&gt;퍼펫티어(Puppeteer)를 이용해 스크린샷을 찍는 프로그램을 짜는 도중, 특정 한글 글꼴들이 제대로 동작하지 않는다는 것을 발견했다. 코드는 다음과 같다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;xml&quot; class=&quot;code&quot;&gt;&amp;lt;!DOCTYPE&amp;nbsp;html&amp;gt;&lt;br /&gt; &amp;lt;html&amp;gt;&lt;br /&gt; &amp;lt;head&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;meta&amp;nbsp;charset=&quot;utf-8&quot;&amp;nbsp;/&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;style&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@import&amp;nbsp;url(https://fonts.googleapis.com/css?family=Signika);&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@import&amp;nbsp;url(https://fonts.googleapis.com/css?family=Open+Sans);&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@import&amp;nbsp;url(https://fonts.googleapis.com/css?family=Nanum+Myeongjo);&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;body&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-family:&amp;nbsp;&#039;Signika&#039;,&amp;nbsp;sans-serif;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;background-color:&amp;nbsp;white;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;h1&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-family:&amp;nbsp;&#039;Open&amp;nbsp;Sans&#039;,&amp;nbsp;sans-serif;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;h2&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;font-family:&amp;nbsp;&#039;Nanum&amp;nbsp;Myeongjo&#039;,&amp;nbsp;sans-serif;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/style&amp;gt;&lt;br /&gt; &amp;lt;/head&amp;gt;&lt;br /&gt; &amp;lt;body&amp;gt;&lt;br /&gt; Signika&amp;nbsp;Text&amp;nbsp;기본&amp;nbsp;한글&lt;br /&gt; &amp;lt;h1&amp;gt;Open&amp;nbsp;Sans&amp;lt;/h1&amp;gt;&lt;br /&gt; &amp;lt;h2&amp;gt;나눔&amp;nbsp;명조&amp;lt;/h2&amp;gt;&lt;br /&gt; &amp;lt;/body&amp;gt;&lt;br /&gt; &amp;lt;/html&amp;gt;&lt;/div&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;jscript&quot; class=&quot;code&quot; style=&quot;display: block;&quot;&gt;//&amp;nbsp;...&lt;br /&gt; &lt;br /&gt; const&amp;nbsp;browser&amp;nbsp;=&amp;nbsp;await&amp;nbsp;puppeteer.launch({&lt;br /&gt; &amp;nbsp;&amp;nbsp;headless:&amp;nbsp;true,&amp;nbsp;//&amp;nbsp;불필요하다.&lt;br /&gt; &amp;nbsp;&amp;nbsp;args:&amp;nbsp;[&#039;--no-sandbox&#039;,&amp;nbsp;&#039;--disable-dev-shm-usage&#039;]}&lt;br /&gt; );&lt;br /&gt; const&amp;nbsp;page&amp;nbsp;=&amp;nbsp;await&amp;nbsp;browser.newPage();&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;...&lt;br /&gt; &lt;br /&gt; await&amp;nbsp;page.goto(argv.source,&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;waitUntil&quot;:&amp;nbsp;[&quot;load&quot;,&amp;nbsp;&quot;networkidle0&quot;]&lt;br /&gt; });&lt;br /&gt; await&amp;nbsp;page.evaluateHandle(&#039;document.fonts.ready&#039;);&lt;br /&gt; await&amp;nbsp;page.screenshot(settings);&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;...&lt;/div&gt;&lt;p&gt;실행해 보면 Signika나 Open Sans의 경우 정상 표시가 되는데, 유독 나눔 명조 글꼴만 제대로 표시되지 않았다. 나눔 고딕도 마찬가지. 반면에 헤드리스(headless) 옵션을 바꾸어 화면에 보이도록 표시하면 정상 표시 되었다. page.tracing API를 사용해 통신 내역을 확인해보니 헤드리스와 일반 상태일 때 요청 내역이 TTF와 WOFF2 파일들로 서로 달랐다. 즉 헤드리스 여부에 따라 fonts.googleapis.com 에서 반환하는 CSS 파일의 내용이 달랐던 것이다.&lt;br /&gt;&lt;br /&gt;그런데 서버는 어떻게 헤드리스인 줄을 알았을까? 바로 User-Agent 헤더가 다르기 때문인데, 퍼펫티어 1.11.0 기준으로는 다음과 같다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;jscript&quot; class=&quot;code&quot;&gt;//&amp;nbsp;console.log(await&amp;nbsp;page.evaluate(()&amp;nbsp;=&amp;gt;&amp;nbsp;navigator.userAgent));&amp;nbsp;코드로&amp;nbsp;확인&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;headless&amp;nbsp;=&amp;nbsp;false&lt;br /&gt; Mozilla/5.0&amp;nbsp;(X11;&amp;nbsp;Linux&amp;nbsp;x86_64)&amp;nbsp;AppleWebKit/537.36&amp;nbsp;(KHTML,&amp;nbsp;like&amp;nbsp;Gecko)&amp;nbsp;Chrome/72.0.3617.0&amp;nbsp;Safari/537.36&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;headless&amp;nbsp;=&amp;nbsp;true&amp;nbsp;(default)&lt;br /&gt; Mozilla/5.0&amp;nbsp;(X11;&amp;nbsp;Linux&amp;nbsp;x86_64)&amp;nbsp;AppleWebKit/537.36&amp;nbsp;(KHTML,&amp;nbsp;like&amp;nbsp;Gecko)&amp;nbsp;HeadlessChrome/72.0.3617.0&amp;nbsp;Safari/537.36&lt;/div&gt;&lt;p&gt;사실 TTF라고 해서 딱히 렌더링이 안 될 이유는 없으니,&amp;nbsp; TTF 혹은 CSS 파일의 무언가가 잘못된 게 아닌가 싶지만 막연한 추측.&lt;br /&gt;아무튼 page.setUserAgent 메서드를 사용해 User-Agent 헤더를 headless=false일 때의 것으로 변경하여 당장 해결은 가능하며, 추후 업스트림 패치가 이뤄져야 할 것 같다.&lt;br /&gt;&lt;/p&gt;</description>
		<category>비공정</category>	<category>CSS</category>				<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1185564</guid>
	<comments>https://manalith.org/zbxe/blog/1185564#comment</comments>			<pubDate>Sun, 16 Dec 2018 18:03:43 +0000</pubDate>
		</item><item>
			<title>MS Sculpt Ergonomic Keyboard 자가수리 실패기</title>
			<link>https://manalith.org/zbxe/blog/1183917</link>
				<description>&lt;p&gt;최근에 잘 쓰던 키보드가 갑자기 고장이 났다. 정 중앙의 G키가 잘 안 눌리기 시작한 것. 이 키보드가 포함된 MS 스컬프트 인체공학(컴포트 말고!) 데스크톱 세트를 산 것이 출시 직후이니, 사용한 지는 5년 정도 된다. 무상수리 기간도 지났고 그냥 키를 뽑아서 좀 청소해보고 안 되면 아예 분해해서 청소하면 되겠지 싶어, 뜯어 보았다.&lt;br /&gt;&lt;br /&gt;일단 키 분해는 아래 링크를 참고했다. 쉽게 뜯으려면 키 뒷면을 참고해야 하는데 영상에서는 뒤집어서 보여주는 것에 주의.&lt;br /&gt;https://www.youtube.com/watch?v=vNNvP146a1s&lt;br /&gt;&lt;br /&gt;보면 알겠지만... 뜯어도 별 것이 없다. 러버돔은 아예 접착이 되어 있고, G키 경우 아주 약간의 틈새가 있긴 한데 뭘 어찌할 수 있는 것은 아니라서 아예 전체를 뜯을 생각을 했다.&lt;br /&gt;이 때는 아래를 참고했다.&lt;br /&gt;http://emmanuelcontreras.com/how-to/how-to-disassemble-microsoft-sculpt-ergonomic-keyboard-and-make-it-wired/&lt;br /&gt;&lt;br /&gt;링크의 6번째 사진(철판이 보이는)에 나온 것만큼 뜯기는 했는데, 문제는 저 상태가 한계다. 아마도 필름이 들어 있을 두 개의 철판이 일부 접착이 되어 있고 이 철판들을 키보드 전면 프라스틱이 사출 수준에서 고정하고 있어서, &lt;a href=&quot;https://manalith.org/zbxe/index.php?&amp;amp;vid=blog&amp;amp;mid=textyle&amp;amp;act=dispTextyle&amp;amp;search_target=title_content&amp;amp;search_keyword=%ED%94%84%EB%A6%AC%EC%8A%A4%ED%83%80%EC%9D%BC&amp;amp;document_srl=54291&quot; target=&quot;_self&quot;&gt;프리스타일 수리&lt;/a&gt; 때처럼 필름이라도 보려면 사실상 부수는 수 밖에 없어 보였다. 여기까지 뜯기도 더럽게 힘들어서 조립 뒤에 봤더니 키보드 전면 곳곳에 금이 가있던 것은 덤...&amp;nbsp; 아마도 이 정도로 밀봉하면 이물질로 고장날 일은 없을 거라 생각한 듯 한데, 그럼에도 난 고장이 나 버렸는데 어쩌나. (보증기간 안에 고장만 안 난다는 뜻이었다면 뭐...) 아무튼 아주 버릴 생각을 한 것은 아니라 대충 틈새마다 블로워 등으로 별 의미없는 청소를 하고 다시 조립했는데, 역시나 문제의 G 키는 여전히 되다가 말다가다.&lt;br /&gt;&lt;br /&gt;현재는 새로운 제품을 사는 것을 고려하고 있다. 스컬프트 키보드는 이렇게 청소하기 힘든 것 외에도 마우스, 키패드 세트로만 판매하는 점, 그나마도 로지텍처럼 리시버를 변경 가능한 것도 아니라서(새 스컬프트 세트를 사서 현재 남아 있는 마우스와 키패드를 쓰려면 리시버 두 개를 써야 한다) 고려하지 않는 것으로. 바로 전 세대의 MS 인체공학 키보드는 예전에 잘 썼었지만 다시 사자니 너무 커서 불편했던 게 떠올라 패스. 서피스 어고노믹도 커서... 일단 기계식 중에 인체공학 제품 위주로 알아보려고 하고, 마땅한 게 없으면 프리스타일 엣지로 갈 생각이다.&lt;br /&gt;&lt;/p&gt;</description>
		<category>비공정</category>	<category>IT 생각</category>				<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1183917</guid>
	<comments>https://manalith.org/zbxe/blog/1183917#comment</comments>			<pubDate>Thu, 04 Oct 2018 02:50:29 +0000</pubDate>
		</item><item>
			<title>우분투 17.10의 웨이랜드/그놈 셸로 마이그레이션</title>
			<link>https://manalith.org/zbxe/blog/1176485</link>
				<description>&lt;p&gt;우분투 17.10에서 기본 셸이 유니티(Unity)에서 그놈 셸로 변경되었다. 업그레이드 하면서 겪은 것들은 다음과 같다.&lt;br /&gt;&lt;/p&gt;&lt;h3 id=&quot;h1515648618501&quot;&gt;시스템 인디케이터 애플릿&lt;/h3&gt;&lt;p&gt;기존까지 잘 사용하던 인디케이터 애플릿(indicator-multiload)이 갑자기 매우 작은 애플릿으로 변경되어 쓸모가 없어졌다. 이 현상은 현재 런치패드에 &lt;a href=&quot;https://bugs.launchpad.net/ubuntu/+source/indicator-multiload/+bug/1714804&quot; target=&quot;_blank&quot;&gt;버그로 등록&lt;/a&gt;되어 있는데, 글 쓰는 현재 고쳐지지 않고 있는 중. 그런데 기존 그놈 셸 생태계에 대안이 있으므로, 이참에 애플릿을 변경했다. &lt;a href=&quot;https://extensions.gnome.org/extension/120/system-monitor/&quot; target=&quot;_blank&quot;&gt;system-monitor&lt;/a&gt; 라는 애플릿이다. 덤으로 훨씬 화려(?)하다.&lt;br /&gt;&lt;br /&gt;설치 방법은 아래 주소를 참고.&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/paradoxxxzero/gnome-shell-system-monitor-applet&quot; target=&quot;_blank&quot;&gt;https://github.com/paradoxxxzero/gnome-shell-system-monitor-applet&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;읽어보면 알겠지만, 갓 업그레이드한 상태라면 먼저 APT로 요구하는 패키지들을 설치하고, 브라우저 확장을 설치한 뒤 &lt;a href=&quot;https://extensions.gnome.org/extension/120/system-monitor/&quot; target=&quot;_blank&quot;&gt;애플릿 페이지&lt;/a&gt;에 들어가서 스위치를 눌러 설치 &amp;amp; 활성화시켜주면 된다. 파이어폭스의 경우 덤으로 chrome-gnome-shell 패키지까지 설치해야 사이트에서 인식하는데, 역시 주소에 나와 있다.&lt;br /&gt;&lt;/p&gt;&lt;h3 id=&quot;h1515648995310&quot;&gt;블루라이트 감소 프로그램&lt;/h3&gt;&lt;p&gt;화면 색조를 변경할 수 없는 노트북에서 낮은 색온도를 사용하기 위해 기존에 &lt;a href=&quot;https://github.com/jonls/redshift&quot; target=&quot;_blank&quot;&gt;레드시프트(redshift)&lt;/a&gt;를 편리하게 사용했는데, 업그레이드 이후로는 X 환경이 웨이랜드(wayland)로 변경됨에 따라 더이상 동작하지 않았다. 역시 &lt;a href=&quot;https://github.com/jonls/redshift/issues/543&quot; target=&quot;_blank&quot;&gt;이슈로 등록&lt;/a&gt;되어 있으나 해결되지 않고 있는 상황. 답은 그놈3의 자체 기능에 있다. 설정의 디스플레이 항목에 보면 &#039;야간 조명&#039;이라는 항목이 있는데 이것을 켜면 된다. 끌 때는 초승달 모양으로 변한 인디케이터에서 찾아서 끄면 된다. 하지만 레드시프트처럼 인디케이터를 통해 바로 켤 수 있는 방법이 없고, 색온도를 얼마만큼 낮출지 지정할 수 없는 것이 단점이다.&lt;br /&gt;&lt;/p&gt;&lt;h3 id=&quot;h1515649351851&quot;&gt;윈도우 스냅 단축키&lt;/h3&gt;&lt;p&gt;단축키를 통해 현재 열려 있는 윈도우를 전체화하거나 좌/우로 배치하는 단축키가 조금 변경되었다. Super+Left/Right로 좌우 정렬, Ctrl+Super+Up/Down으로 최대화/복원이다. 기본 상태로는 Ctrl+Super+Left/Right는 동작하지 않는다. 덤으로 좌우로 스냅된 두 창의 경계를 움직여 양쪽 창의 크기를 동시에 변경하는 기능도 지원된다. 하지만 가끔 윈도우가 화면을 약간 벗어나는 것으로 보아 버그가 있는 것 같다.&lt;br /&gt;&lt;/p&gt;</description>
		<category>비공정</category>	<category>리눅스</category>				<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1176485</guid>
	<comments>https://manalith.org/zbxe/blog/1176485#comment</comments>			<pubDate>Thu, 11 Jan 2018 05:45:57 +0000</pubDate>
		</item><item>
			<title>자급제 혹은 타 통신사용 단말기의 LG U+ MMS 호환성</title>
			<link>https://manalith.org/zbxe/blog/1173800</link>
				<description>&lt;p&gt;자급제나 타 통신사용 단말기를 개통시 가장 흔한 문제는 MMS 이다. 그리고 일반적인 해결책은 APN 설정을 해 주는 것이다. APN 설정 방법과 값이야 인터넷에 널려있으므로 생략하자. 일부 기기의 경우(안드로이드 7.0 누가 이후의 OS에 기본 탑재된 건지 모르겠으나) APN을 포함한 네트워크 설정을 다운받을 수 있는 메뉴가 존재하므로 굳이 주소와 포트 등을 한자한자 입력할 필요도 없다.&lt;br /&gt;&lt;br /&gt;문제는 그래도 안 되는 경우가 있더라는 것이다. 필자의 지인이 최근에 KT용 V10을 LG U+ 망을 사용하는 우체국 인스코비 요금제로 개통했는데, 문자메시지 앱에서 사진 발송이 계속 실패했다. APN 설정을 아무리 바꿔봐도 소용없었다. 테스트 중에 LG U+ 망을 사용하는 필자의 엑스페리아 XZ 프리미엄도 같은 문제가 발생하는 것을 발견했는데, APN 설정은 메뉴에서 받은 것으로 고칠 것도 없었지만 인터넷에서 찾은 것들로 고쳐봐도 오히려 뭔가가 더 안되기만 했다.&lt;br /&gt;&lt;br type=&quot;_moz&quot; /&gt;해결책은 소프트웨어에 있었다. KT용 V10의 경우 PC에 연결하여 7.0 누가로 업그레이드하고 나니 문제가 해결되었다. 엑스페리아 XZ 프리미엄의 경우 일부러 설치한 QKSMS 앱(현재 2.7.3)을 제거하고 기본 메시지 앱을 사용하니 문제가 해결되었다. APN 설정을 하고서도 안 되는 경우 펌웨어나 메시지 앱을 의심해볼 필요가 있는 것 같다. 별것 아닌 팁이지만 APN 문제인 줄만 알고 한참을 고생하여 굳이 남겨둔다.&lt;/p&gt;</description>
		<category>비공정</category>	<category>생각</category>				<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1173800</guid>
	<comments>https://manalith.org/zbxe/blog/1173800#comment</comments>			<pubDate>Sun, 08 Oct 2017 15:44:55 +0000</pubDate>
		</item><item>
			<title>파나소닉 DMC-GM1 서비스 모드에서 빠져나오기</title>
			<link>https://manalith.org/zbxe/blog/1173670</link>
				<description>&lt;h3 id=&quot;h1507086056592&quot;&gt;서비스 모드에 진입해 셔터 카운트 확인하기&lt;/h3&gt;&lt;p&gt;1. 메모리 카드를 삽입한다.&lt;br /&gt;2. 카메라를 켜고 사진을 1장 찍는다.&lt;br /&gt;3. 카메라를 끈다.&lt;br /&gt;4. 재생, 휴지통과 오른쪽 버튼을 누른 상태에서 카메라를 켠다.&lt;br /&gt;5. 재생 버튼을 누른 상태에서 Menu/Set 버튼과 왼쪽 버튼을 누른다. 서비스 메뉴가 표시된다.&lt;br /&gt;6. 위의 동작을 한번 더 반복하면 PWRCNT(전원 켠 횟수), SHTCNT(셔터 활성화 횟수), STBCNT(플래시 발광 횟수)가 표시된 메뉴가 나온다.&lt;br /&gt;&lt;br /&gt;일반적으로는, 이대로 전원을 끄면 노멀 모드로 복귀한다.&lt;/p&gt;&lt;p class=&quot;link&quot;&gt;&lt;strong&gt;출처 : Panasonic G shutter count&lt;/strong&gt;&lt;br /&gt;&lt;a href=&quot;https://www.apotelyt.com/photo-camera/panasonic-g-shutter-count&quot;&gt;https://www.apotelyt.com/photo-camera/panasonic-g-shutter-count&lt;/a&gt;&lt;/p&gt;&lt;h3 id=&quot;h1507086274700&quot;&gt;서비스 모드에서 빠져나와 리셋하기&lt;/h3&gt;&lt;p&gt;위 방법을 따라했을 때 서비스 모드를 빠져나오지 못하는 경우가 있다. 아마 5에서 뭔가를 잘못 눌러서 생기는 일인 것 같은데, 증상은 전원을 켜면 설정 메뉴에 ROM BACKUP 항목이 추가되며 끌 때는 항상 루믹스 로고 대신 노란색 느낌표가 뜬다. 해결책은 다음과 같지만 &lt;b&gt;카메라 설정 리셋을 감수해야 한다&lt;/b&gt;. (아래 방법 외에 설정 - 리셋 메뉴에서 초기화를 하거나 메모리카드 포맷도 해 보았지만 문제가 해결되지 않았다.&lt;br /&gt;&lt;br /&gt;1. 다이얼을 P 모드로 맞추고 Menu/Set 버튼과 오른쪽 버튼을 누른 상태로 전원을 켠다.&lt;br /&gt;2. Menu/Set 버튼과 오른쪽 버튼을 누른다.&lt;br /&gt;&lt;br /&gt;이렇게 하면 완전히 초기화되었다는 신호로 시간 설정 화면이 나오며 일반 모드로 돌아온다.&lt;/p&gt;&lt;p class=&quot;link&quot;&gt;&lt;strong&gt;출처 : Re: Stuck in service mode - Dpreview.com&lt;/strong&gt;&lt;br /&gt;&lt;a href=&quot;https://www.dpreview.com/forums/post/54451777&quot;&gt;https://www.dpreview.com/forums/post/54451777&lt;/a&gt;&lt;/p&gt;</description>
		<category>비공정</category>	<category>내가 쓰는 물건</category>				<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1173670</guid>
	<comments>https://manalith.org/zbxe/blog/1173670#comment</comments>			<pubDate>Wed, 04 Oct 2017 03:12:59 +0000</pubDate>
		</item><item>
			<title>java.io.File에서 java.nio.file로의 이행 (feat. Play, Scala)</title>
			<link>https://manalith.org/zbxe/blog/1172364</link>
				<description>&lt;p&gt;Java SE 7에서는 기존의 java.io.File 기반의 File I/O 대신에 java.nio.file.Path 기반의 새로운 API를 사용할 수 있게 되었다. 새 API들은 기존에 비해 좀더 상세한 에러, 강화된 크로스 플랫폼 지원, 심볼릭 링크 지원, 더 나은 메타데이터 지원 등 여러 장점을 가진다. (&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/essential/io/legacy.html&quot; target=&quot;_blank&quot;&gt;참고&lt;/a&gt;) 하지만 API 구조가 다르기에 이행 작업이 필요하다. 내 경우 플레이 프레임워크 2.6(스칼라 기반)에서 TemporaryFile의 API가 File에서 Path 기반으로 변경되었기에 기존 코드들을 수정할 필요성이 생겨 정리해보았다.&lt;/p&gt;&lt;h3 id=&quot;h1504027449741&quot;&gt;File에서 Path로&lt;/h3&gt;&lt;p&gt;기본이 되는 작업은 java.io.File 대신 java.nio.file.Path를 사용하는 것이다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; import&amp;nbsp;java.io.File&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; import&amp;nbsp;java.nio.file.Path&lt;/div&gt;&lt;p&gt;File은 클래스지만, Path는 인터페이스이다. 따라서 직접 생성하는 대신 Paths 등의 도구를 사용해야 한다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; val&amp;nbsp;file&amp;nbsp;=&amp;nbsp;new&amp;nbsp;File(fullPath)&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; val&amp;nbsp;path&amp;nbsp;=&amp;nbsp;Paths.get(fullPath)&lt;/div&gt;&lt;p&gt;파일을 가리킬 뿐 아니라 파일에 대한 다양한 동작을 가지고 있던 File 클래스와 달리, Path 인터페이스는 경로를 가리키는 역할만 수행한다. 기존 File 클래스의 다양한 기능들은 Files 유틸리티 등에 나뉘어져 있다. 이를테면, 경로의 파일 크기를 알아내는 코드가 그렇다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; val&amp;nbsp;file:&amp;nbsp;File&amp;nbsp;=&amp;nbsp;...&lt;br /&gt; if&amp;nbsp;(file.length()&amp;nbsp;&amp;gt;&amp;nbsp;maxUploadSize)&amp;nbsp;{&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; val&amp;nbsp;path:&amp;nbsp;Path&amp;nbsp;=&amp;nbsp;...&lt;br /&gt; if&amp;nbsp;(Files.size(path)&amp;nbsp;&amp;gt;&amp;nbsp;maxUploadSize)&amp;nbsp;{&lt;/div&gt;&lt;p&gt;다음 문서에는 위처럼 기존 File 클래스의 특정 기능에 해당되는 java.nio.file 패키지의 요소들이 나열되어 있다.&lt;/p&gt;&lt;p class=&quot;link&quot;&gt;&lt;strong&gt;Mapping java.io.File Functionality to java.nio.file&lt;/strong&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.oracle.com/javase/tutorial/essential/io/legacy.html&quot;&gt;https://docs.oracle.com/javase/tutorial/essential/io/legacy.html&lt;/a&gt;&lt;/p&gt;&lt;p&gt;어떤 경로의 파일을 가리키는 Path 객체로부터 파일명만을 가져오기 위해서는, 오직 파일명만을 가리키는 Path 객체를 생성 후 문자열로 변환하면 된다. 상대경로를 절대경로로 바꿀 때도 비슷하게 새로운 Path 객체를 만들어서 한다. 다음은 기존과의 차이점을 살펴보기 위해 REPL에서 실행한 코드이다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;val&amp;nbsp;file&amp;nbsp;=&amp;nbsp;new&amp;nbsp;File(&quot;/home/setzer/myfile&quot;)&lt;br /&gt; file:&amp;nbsp;java.io.File&amp;nbsp;=&amp;nbsp;/home/setzer/myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;file.getName&lt;br /&gt; res0:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;val&amp;nbsp;path&amp;nbsp;=&amp;nbsp;Paths.get(&quot;/home/setzer/myfile&quot;)&lt;br /&gt; path:&amp;nbsp;java.nio.file.Path&amp;nbsp;=&amp;nbsp;/home/setzer/myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;path.toString&lt;br /&gt; res1:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;/home/setzer/myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;path.getFileName&lt;br /&gt; res2:&amp;nbsp;java.nio.file.Path&amp;nbsp;=&amp;nbsp;myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;path.getFileName.toString&lt;br /&gt; res3:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;val&amp;nbsp;file&amp;nbsp;=&amp;nbsp;new&amp;nbsp;File(&quot;./myfile&quot;)&lt;br /&gt; file:&amp;nbsp;java.io.File&amp;nbsp;=&amp;nbsp;./myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;file.getAbsolutePath&lt;br /&gt; res4:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;/home/setzer/./myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;val&amp;nbsp;path&amp;nbsp;=&amp;nbsp;Paths.get(&quot;./myfile&quot;)&lt;br /&gt; path:&amp;nbsp;java.nio.file.Path&amp;nbsp;=&amp;nbsp;./myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;path.toString&lt;br /&gt; res5:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;./myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;path.getFileName.toString&lt;br /&gt; res6:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;path.toAbsolutePath&lt;br /&gt; res7:&amp;nbsp;java.nio.file.Path&amp;nbsp;=&amp;nbsp;/home/setzer/./myfile&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;path.toAbsolutePath.toString&lt;br /&gt; res8:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;/home/setzer/./myfile&lt;/div&gt;&lt;p&gt;호환성 등 여러 이유로 java.io.File이 필요해지면 Path.toFile을 활용하면 된다. 역으로 File.toPath 메서드도 존재한다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; FileUtils.deleteQuietly(file)&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; FileUtils.deleteQuietly(path.toFile)&lt;/div&gt;&lt;p&gt;파일의 경로에서 구분자(예컨대 &#039;/home/setzer/myfile&#039; 의 &#039;/&#039;)를 가져오고자 한다면 FileSystems 유틸 클래스를 통해 직접 구하면 된다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전.&amp;nbsp;OpenJDK&amp;nbsp;기준으론&amp;nbsp;원래도&amp;nbsp;내부적으로&amp;nbsp;DefaultFileSystem.getFileSystem.getPathSeparator&amp;nbsp;이긴&amp;nbsp;했다.&lt;br /&gt; val&amp;nbsp;separator&amp;nbsp;=&amp;nbsp;File.separator&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; val&amp;nbsp;separator&amp;nbsp;=&amp;nbsp;FileSystems.getDefault.getSeparator&lt;/div&gt;&lt;p&gt;임시 파일을 생성하는 메서드는 File에서 Files로 옮겨졌다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; File.createTempFile(...)&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; Files.createTempFiles(...)&lt;/div&gt;&lt;p&gt;위의 기능 매핑 문서에도 나와 있는 내용이긴 하지만 굳이 여기 소개한 이유는, 반환되는 값이 File이 아닌 Path이기 때문에 File.deleteOnExit()로 마킹할 수 없었기 때문이다. 문서에는 이에 대응되는 기능이 파일 생성 때 DELETE_ON_CLOSE 옵션을 주라는 것인데, 위의 방식으로 하면 옵션을 줄 수가 없다.&lt;br /&gt;&lt;br /&gt;몇 가지 방법이 있는데 (&lt;a href=&quot;https://softwarecave.org/2014/02/05/create-temporary-files-and-directories-using-java-nio2/&quot; target=&quot;_blank&quot;&gt;문서 참고&lt;/a&gt;) 아래 방법이 가장 간단하다. 참고로 해당 문서에는 종료시 파일 삭제에 대한 문제점도 다루고 있으니 살펴보길 바란다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; file.deleteOnExit()&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; path.toFile.deleteOnExit()&lt;/div&gt;&lt;h3 id=&quot;h1504066106832&quot;&gt;Commons-IO&lt;/h3&gt;&lt;p&gt;편의상 Commons-IO 등 별도 오픈소스 라이브러리에 있던 것들이 구현된 경우도 있다. 그 중 하나는 &lt;a href=&quot;https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/FileUtils.html#forceMkdir(java.io.File)&quot; target=&quot;_blank&quot;&gt;FileUtils.forceMkdir()&lt;/a&gt; 인데, 이 메서드는 경로 문자열로 디렉터리 생성시 상위 디렉터리가 없으면 만드는 것으로, 배시 셸에서의 mkdir -p 명령과 같다. 새로운 API에서는, &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#createDirectories-java.nio.file.Path-java.nio.file.attribute.FileAttribute...-&quot; target=&quot;_blank&quot;&gt;Files.createDirectories()&lt;/a&gt; 로 구현되었다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; FileUtils.forceMkDir(file)&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; Files.createDirectories(path)&lt;/div&gt;&lt;p&gt;&lt;a href=&quot;https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/FileUtils.html#deleteDirectory(java.io.File)&quot; target=&quot;_blank&quot;&gt;FileUtils.deleteDirectory()&lt;/a&gt; 는 rm -r 명령처럼 디렉터리의 내용까지 재귀적으로 삭제한다. Files에는 이에 대응하는 메서드가 없지만 별도 라이브러리인 Guava에 같은 기능을 하는 &lt;a href=&quot;https://google.github.io/guava/releases/22.0/api/docs/com/google/common/io/MoreFiles.html#deleteRecursively-java.nio.file.Path-com.google.common.io.RecursiveDeleteOption...-&quot; target=&quot;_blank&quot;&gt;MoreFiles.deleteRecursively()&lt;/a&gt; 메서드가 있다. MoreFiles는 Path를 위한 유틸리티 메서드의 모음이다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; FileUtils.deleteDirectory(file)&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; MoreFiles.deleteRecursively(path)&lt;/div&gt;&lt;p&gt;FileUtils.readFileToString() 메서드는 주어진 파일을 특정한 인코딩으로 읽어들여 문자열 변수에 담은 다음, 파일을 닫는 작업까지 수행한다. FileUtils.readLines() 메서드는 파일을 읽어들여 각 행을 담은 컬렉션으로 반환한다. Files를 활용하면 다음처럼 대응이 가능하다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; val&amp;nbsp;content&amp;nbsp;=&amp;nbsp;FileUtils.readFileToString(file,&amp;nbsp;&quot;UTF-8&quot;)&lt;br /&gt; val&amp;nbsp;lines&amp;nbsp;=&amp;nbsp;FileUtils.readLines(file,&amp;nbsp;&quot;UTF-8&quot;)&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; val&amp;nbsp;content&amp;nbsp;=&amp;nbsp;new&amp;nbsp;String(Files.readAllBytes(path,&amp;nbsp;Charset.forName(&quot;UTF-8&quot;))&lt;br /&gt; val&amp;nbsp;lines&amp;nbsp;=&amp;nbsp;Files.readAllBytes(path,&amp;nbsp;Charset.forName(&quot;UTF-8&quot;))&lt;/div&gt;&lt;h3 id=&quot;h1504070105803&quot;&gt;보다 스칼라스럽게&lt;/h3&gt;&lt;p&gt;여느 자바 API가 그러하듯, NIO API에도 스칼라 래퍼들이 존재한다. 그중 &lt;a href=&quot;https://github.com/pathikrit/better-files&quot; target=&quot;_blank&quot;&gt;better-files&lt;/a&gt; 라이브러리를 활용하면 DSL이나 암묵 변환 등 스칼라의 기능을 활용해 더 편리하게 API를 조작할 수 있다. 이를테면, 위의 FileUtils.readFileToString() 메서드는 다음처럼 이행될 수 있다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;import&amp;nbsp;better.files._&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;암묵&amp;nbsp;변환으로&amp;nbsp;Charset.forName(&quot;UTF-8&quot;)과&amp;nbsp;같다.&lt;br /&gt; implicit&amp;nbsp;val&amp;nbsp;charset:&amp;nbsp;Charset&amp;nbsp;=&amp;nbsp;&quot;UTF-8&quot;&amp;nbsp;&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;위에서&amp;nbsp;선언한&amp;nbsp;암묵&amp;nbsp;인자&amp;nbsp;변수&amp;nbsp;charset이&amp;nbsp;사용된다.&lt;br /&gt; val&amp;nbsp;content&amp;nbsp;=&amp;nbsp;File(path).contentAsString&lt;/div&gt;&lt;p&gt;이때 path 변수는 java.nio.file.Path 일 수도 있고 문자열일 수도 있으며, 어느 쪽이든 암묵 변환으로 path.contentAsString 처럼 쓸 수도 있다. 자세한 것은 페이지 참조. 이 메서드는 내부적으로 위에서 java.nio.file.Files를 사용한 예시와 &lt;a href=&quot;https://github.com/pathikrit/better-files/blob/35184a642245db3d1e41fc02c7bfbec0b19a43bb/core/src/main/scala/better/files/File.scala#L241&quot; target=&quot;_blank&quot;&gt;동일하다&lt;/a&gt;. 이를 이용하면 쓰기도 간단하다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; FileUtils.writeStringToFile(file,&amp;nbsp;source,&amp;nbsp;&quot;UTF-8&quot;)&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; File(file).write(source)(charset&amp;nbsp;=&amp;nbsp;&quot;UTF-8&quot;)&lt;/div&gt;&lt;p&gt;파일 혹은 경로 삭제시 IOException을 무시하는 FileUtils.deleteQuietly() 유틸 메서드 또한 인자로 대응해 이행할 수 있다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;변경&amp;nbsp;전&lt;br /&gt; FileUtils.deleteQuietly(file)&lt;br /&gt; &lt;br /&gt; //&amp;nbsp;변경&amp;nbsp;후&lt;br /&gt; File(file).delete(true)&amp;nbsp;//swallowIOExceptions를&amp;nbsp;true로&amp;nbsp;설정한다.&amp;nbsp;단,&amp;nbsp;deleteQuietly와는&amp;nbsp;달리&amp;nbsp;IOException에만&amp;nbsp;대응한다.&lt;/div&gt;&lt;p&gt;better.files.File 객체는 java.io.File, java.nio.file.Path 및 문자열 뿐 아니라 DSL로도 초기화 가능하며, 메서드 체이닝도 된다. 이를테면 다음과 같은 코드가 가능하다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;import&amp;nbsp;better.files._&lt;br /&gt; val&amp;nbsp;path:&amp;nbsp;Path&amp;nbsp;=&amp;nbsp;(&quot;home&quot;&amp;nbsp;/&amp;nbsp;&quot;setzer&quot;&amp;nbsp;/&amp;nbsp;&quot;myFile&quot;).write(source)(charset&amp;nbsp;=&amp;nbsp;&quot;UTF-8&quot;).deleteOnExit().path&lt;/div&gt;&lt;p&gt;경로 지정 DSL은 다양한 방식으로 작성 가능한데 자세한 내용은 &lt;a href=&quot;https://github.com/pathikrit/better-files&quot; target=&quot;_blank&quot;&gt;better-files 홈페이지&lt;/a&gt;에서 확인하길 바란다.&lt;/p&gt;</description>
		<category>비공정</category>	<category>Scala</category>	<category>스칼라</category><category>Scala</category><category>better-files</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1172364</guid>
	<comments>https://manalith.org/zbxe/blog/1172364#comment</comments>			<pubDate>Tue, 29 Aug 2017 18:45:23 +0000</pubDate>
		</item><item>
			<title>Play 2.6과 AngularJS에서 CSRF 설정</title>
			<link>https://manalith.org/zbxe/blog/1170872</link>
				<description>&lt;p&gt;플레이 프레임워크(Play Framework) 2.6 부터는&amp;nbsp;&lt;a href=&quot;https://www.playframework.com/documentation/2.6.x/Highlights26#Filters-Enhancements&quot; target=&quot;_blank&quot;&gt;CSRF 필터가 기본으로 활성화&lt;/a&gt;되어 있다. 다시 말해 서버에서 CSRF 토큰을 감지하지 못할 경우 POST/PUT 등의 요청이 403 Forbidden 오류로 거부될 수 있다는 이야기이다. 서버 요청에 플레이에서 제공하는 헬퍼를 템플릿에 사용해왔다면 필드 하나를 추가해주면 그만이지만, 필자처럼 앵귤러(Angular) JS로 XHR을 이용한 통신을 주로 사용했을 경우 이 필터를 통과하거나 우회할 방법을 찾아야 한다.&lt;/p&gt;&lt;h3 id=&quot;h1499235467009&quot;&gt;방법 1. XHR 요청에 대해 CSRF 토큰 검사하지 않기&lt;/h3&gt;&lt;p&gt;X-Requested-With 헤더는 표준은 아니지만 JQuery 등의 XHR 요청에 널리 사용되고 있다. CORS 설정을 통해 타 도메인의 스크립트로 이런 요청을 보내지 않는다는 것을 전제로, X-Requested-With 헤더가 있는 요청을 신뢰하도록 플레이를 설정할 수 있다. 다음은 &lt;a href=&quot;https://www.playframework.com/documentation/2.6.x/ScalaCsrf#Plays-CSRF-protection&quot; target=&quot;_blank&quot;&gt;플레이 문서에 나온 예시&lt;/a&gt;이다. 이와 더불어 3번째 행은 Csrf-Token 헤더가 nocheck일 경우 또한 신뢰하도록 설정하고 있다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;as3&quot; class=&quot;code&quot;&gt;play.filters.csrf.header.bypassHeaders&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;X-Requested-With&amp;nbsp;=&amp;nbsp;&quot;*&quot;&lt;br /&gt; &amp;nbsp;&amp;nbsp;Csrf-Token&amp;nbsp;=&amp;nbsp;&quot;nocheck&quot;&lt;br /&gt; }&lt;/div&gt;&lt;p&gt;이렇게 헤더 단위가 아니라 특정 route에 대해서만 CSRF 필터를 적용하지 않는 것도 가능하다. 자세한 것은 &lt;a href=&quot;https://www.playframework.com/documentation/2.6.x/ScalaCsrf&quot; target=&quot;_blank&quot;&gt;문서&lt;/a&gt;를 참고하라.&lt;/p&gt;&lt;p class=&quot;link&quot;&gt;&lt;strong&gt;참고 : What&#039;s the point of the X-Requested-With header? - StackOverflow&lt;/strong&gt;&lt;br /&gt;&lt;a href=&quot;https://stackoverflow.com/questions/17478731/whats-the-point-of-the-x-requested-with-header/22533680&quot;&gt;https://stackoverflow.com/questions/17478731/whats-the-point-of-the-x-requested-with-header/22533680&lt;/a&gt;&lt;/p&gt;&lt;h3 id=&quot;h1499237984684&quot;&gt;방법2. AngularJS $http 모듈의 요청에 CSRF 설정하기&lt;/h3&gt;&lt;h4 id=&quot;h1499238038317&quot;&gt;쿠키를 통해 헤더 설정하기&lt;/h4&gt;&lt;p&gt;첫 번째 방법은 쿠키를 이용해 CSRF 토큰을 설정하는 것이다. 서버에서 지정된 이름으로 쿠키를 발행하면, $http 모듈이 자동으로 CSRF 토큰으로 인식해 헤더를 설정한다. 다만, 이 방법을 쓰려면 일단 플레이 프레임워크에서 CSRF 토큰을 세션이 아닌 쿠키에 저장하도록 설정 파일을 수정해야 한다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;#&amp;nbsp;AngularJS가&amp;nbsp;인식하는&amp;nbsp;기본&amp;nbsp;쿠키명&lt;br /&gt; play.filters.csrf.cookie.name&amp;nbsp;=&amp;nbsp;&quot;XSRF-TOKEN&quot;&lt;/div&gt;&lt;p&gt;다음으로 CSRF 쿠키는 성능상의 이유로 항상 발행되지 않기 때문에, 발행할 라우트 메서드에서 CSRF.GetToken을 한번 호출해주어야한다. 다음 코드는&amp;nbsp;&lt;a href=&quot;https://www.playframework.com/documentation/2.6.x/ScalaCsrf#Defining-an-implicit-Request-in-Actions&quot; target=&quot;_blank&quot;&gt;메뉴얼에도 나온 내용&lt;/a&gt;을 응용한 것이지만, 다소 이상해보이기는 한다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;def&amp;nbsp;home&amp;nbsp;=&amp;nbsp;Action&amp;nbsp;{&amp;nbsp;implicit&amp;nbsp;request&amp;nbsp;=&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;accessToken&amp;nbsp;//&amp;nbsp;request&amp;nbsp;is&amp;nbsp;passed&amp;nbsp;implicitly&amp;nbsp;to&amp;nbsp;accessToken&lt;br /&gt; &amp;nbsp;&amp;nbsp;Ok(html.index())&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; def&amp;nbsp;accessToken(implicit&amp;nbsp;request:&amp;nbsp;Request[_])&amp;nbsp;=&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;val&amp;nbsp;token&amp;nbsp;=&amp;nbsp;CSRF.getToken&amp;nbsp;//&amp;nbsp;request&amp;nbsp;is&amp;nbsp;passed&amp;nbsp;implicitly&amp;nbsp;to&amp;nbsp;CSRF.getToken&lt;br /&gt; }&lt;/div&gt;&lt;p&gt;이제 브라우저에서 home 액션에 접근하면 개발 도구 등을 통해 Set-Cookie 헤더로 XSRF-TOKEN이 쿠키에 설정되는 것을 확인할 수 있다. 앵귤러JS의 $http 모듈은 이 쿠키를 읽어들인 후 서버의 헤더로 전달하는데, 이 헤더명의 기본값은&amp;nbsp;X-XSRF-TOKEN으로 플레이에서 인식할 수 없기 때문에 바꾸어주어야 한다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;$httpProvider.defaults.xsrfHeaderName&amp;nbsp;=&amp;nbsp;&quot;Csrf-Token&quot;&lt;/div&gt;&lt;p class=&quot;link&quot;&gt;&lt;strong&gt;참고 : Cross Site Request Forgery (XSRF) Protection - AngularJS: API: $http&lt;/strong&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.angularjs.org/api/ng/service/$http&quot;&gt;https://docs.angularjs.org/api/ng/service/$http&lt;/a&gt;&lt;/p&gt;&lt;h4 id=&quot;h1499238025356&quot;&gt;템플릿을 통해 설정하기&lt;/h4&gt;&lt;p&gt;두 번째 방법은 HTML을 통해 CSRF 토큰을 자바스크립트로 전달하는 것이다. 이 방법은 다른 서버측 설정이 필요없다는 점에서 좀더 플레이 프레임워크 친화적(?)으로 보이지만, 클라이언트 작업이 좀더 번잡하다. 우선 템플릿 상단에 아래와 같이 선언한다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;xml&quot; class=&quot;code&quot;&gt;@import&amp;nbsp;play.filters.csrf._&lt;br /&gt; @()(implicit&amp;nbsp;request:&amp;nbsp;RequestHeader)&lt;br /&gt; ...&lt;/div&gt;&lt;p&gt;암묵적인 RequestHeader는 CSRF 값을 얻기 위해서 필요하다. 다음으로 앵귤러JS의 $httpProvider를 다음과 같이 설정한다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;jscript&quot; class=&quot;code&quot;&gt;$httpProvider.defaults.headers.common[&#039;Csrf-Token&#039;]&amp;nbsp;=&amp;nbsp;&quot;@CSRF.getToken.get.value&quot;;&lt;/div&gt;&lt;p&gt;이렇게 하면 해당 페이지의 $http를 이용한 모든 요청에 Csrf-Token이라는 헤더가 삽입된다. 이 헤더가 플레이 프레임워크에서 기본으로 인식하는 헤더이기 때문에 다른 설정은 필요없다.&lt;br /&gt;만일 자바스크립트 파일이 Twirl 템플릿이 아니라면 위 값을 전역 변수나 다른 방법으로 앵귤러JS로 전달해야 할 것이다.&lt;br /&gt;스칼라JS 바인딩(Scalajs-angular)을 사용한다면 설정은 다음과 같을 것이다. (0.8-SNAPSHOT 기준)&lt;br /&gt;보면 알겠지만 headers 퍼서드는 더 개선될 여지가 있다. 참여를 기다린다 :-)&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;//&amp;nbsp;전제&amp;nbsp;:&amp;nbsp;csrfToken이라는&amp;nbsp;String&amp;nbsp;변수에&amp;nbsp;토큰이&amp;nbsp;할당되어&amp;nbsp;있음&lt;br /&gt; val&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;httpProvider.defaults.headers.asInstanceOf[Dictionary[Dictionary[String]]]&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers(&quot;common&quot;)&amp;nbsp;+=&amp;nbsp;(&quot;Csrf-Token&quot;&amp;nbsp;-&amp;gt;&amp;nbsp;csrfToken)&lt;/div&gt;&lt;p class=&quot;link&quot;&gt;&lt;strong&gt;참고 : Using AngularJs in order to retrieve a header from the response and set it on all requests - StackOverflow&lt;/strong&gt;&lt;br /&gt;&lt;a href=&quot;https://stackoverflow.com/questions/27332717/using-angularjs-in-order-to-retrieve-a-header-from-the-response-and-set-it-on-al&quot;&gt;https://stackoverflow.com/questions/27332717/using-angularjs-in-order-to-retrieve-a-header-from-the-response-and-set-it-on-al&lt;/a&gt;&lt;/p&gt;</description>
		<category>비공정</category>	<category>Scala</category>	<category>스칼라JS</category><category>앵귤러JS</category><category>CSRF</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1170872</guid>
	<comments>https://manalith.org/zbxe/blog/1170872#comment</comments>			<pubDate>Fri, 07 Jul 2017 06:30:23 +0000</pubDate>
		</item><item>
			<title>HDMI 선택기 고르기</title>
			<link>https://manalith.org/zbxe/blog/1170412</link>
				<description>&lt;p&gt;그냥 TV에 연결할 3포트 이상 4K 지원 제품 중 가장 저렴한 제품 고르려는데, 다나와로는 정보가 충분치 않아서 정리해 본다.&lt;br /&gt;특이사항에 적어놓은 것은 스펙 중에 눈에 띄어서 적은 것인데 다른 제품들에 해당 스펙이 없다는 뜻은 아니다.&lt;br /&gt;(확인하지 않았다는 뜻)&lt;br /&gt;가격 정보는 2017년 6월 20일 현재 기준이며, &lt;b&gt;오기에 대해 책임지지 않으니 반드시 구매 전 이중 확인 바랍니다.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;제품명 / 포트 수 / 최저가 / HDMI 버전 / HDCP 버전 / HDR 지원 여부 / 특이사항&lt;br type=&quot;_moz&quot; /&gt;&lt;a href=&quot;http://prod.danawa.com/info/?pcode=4397383&quot; target=&quot;_blank&quot;&gt;라인업시스템 LANStar LS-AS204&lt;/a&gt; / 4:1 / 31,000 / 2.0 / 1.2 / 미확인&lt;br /&gt;&lt;a href=&quot;http://prod.danawa.com/info/?pcode=3429599&quot; target=&quot;_blank&quot;&gt;라이트컴 PV735&lt;/a&gt; / 3:1 / 39,380 / 1.4 / 1.4 / 미확인 / PIP&lt;br /&gt;&lt;a href=&quot;http://prod.danawa.com/info/?pcode=4623291&quot; target=&quot;_blank&quot;&gt;강원전자 NETmate NM-HSU501&lt;/a&gt; / 5:1 / 49,870 / 2.0 / 2.2 / 미확인 / 연결된 Device 모두 Active 상태 유지&lt;br /&gt;&lt;a href=&quot;http://prod.danawa.com/info/?pcode=3944054&quot; target=&quot;_blank&quot;&gt;라이트컴 PV991&lt;/a&gt; / 3:1 / 48,000 / 2.0 / 2.2 / 미확인 / 자동 선택&lt;br /&gt;&lt;a href=&quot;http://prod.danawa.com/info/?pcode=3944025&quot; target=&quot;_blank&quot;&gt;라이트컴 PV992&lt;/a&gt; / 5:1 / 52,700 / 2.0 / 2.2 / 미확인 / 자동 선택&lt;br /&gt;&lt;a href=&quot;http://prod.danawa.com/info/?pcode=4201722&quot; target=&quot;_blank&quot;&gt;이지넷유비쿼터스 NEXT-404SP4K60&lt;/a&gt; / 4:1 / 66,700 / 2.0 / 2.2 / 미확인 / EDID 설정&lt;br /&gt;테라베이 UHD-SW41 (다나와 항목 없이 개별로 등록) / 4:1 / 110,000 / 2.0 / HDR10 / 자동 선택, 오디오 추출&lt;br /&gt;&lt;br /&gt;여기 정리하진 않았지만 그밖에 참고할 사항들은 다음과 같다.&lt;br /&gt;- HDR 지원 여부&lt;br /&gt;- 리모컨 동봉 여부 (저가형의 경우 별매하기도 함)&lt;br /&gt;- 어댑터 포함 여부 (어떤 제품은 별매이고 어떤 제품은 연결할 단자 자체가 없기도 함)&lt;/p&gt;</description>
		<category>비공정</category>	<category>내가 쓰는 물건</category>	<category>HDMI 선택기</category><category>HDMI 셀렉터</category><category>HDMI 스위치</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1170412</guid>
	<comments>https://manalith.org/zbxe/blog/1170412#comment</comments>			<pubDate>Wed, 28 Jun 2017 04:39:25 +0000</pubDate>
		</item><item>
			<title>왜 Vector는 ::로 요소를 추출할 수 없는 걸까?</title>
			<link>https://manalith.org/zbxe/blog/1170293</link>
				<description>&lt;p&gt;스칼라 문법의 주요 특징 중 하나는 패턴 매칭이다. 언어에 익숙한 사용자라면 리스트(List)의 각 요소를 다음과 같은 방식으로 추출할 수 있다는 것을 알 것이다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;val&amp;nbsp;a&amp;nbsp;::&amp;nbsp;b&amp;nbsp;::&amp;nbsp;Nil&amp;nbsp;=&amp;nbsp;Seq(1,&amp;nbsp;2)&lt;/div&gt;&lt;p&gt;위 코드에서 a와 b에는 각각 1, 2가 담긴다. 만일 할당할 시퀀스가 이보다 크다면 MatchError가 발생한다. 이것을 패턴 매칭에서도 그대로 사용할 수 있기에 다음과 같은 코드가 가능하다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;val&amp;nbsp;targets:&amp;nbsp;Seq[File]&amp;nbsp;=&amp;nbsp;Seq(...)&lt;br /&gt; val&amp;nbsp;extension:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;targets&amp;nbsp;match&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;case&amp;nbsp;head&amp;nbsp;::&amp;nbsp;Nil&amp;nbsp;=&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;head.getExtension&lt;br /&gt; &amp;nbsp;&amp;nbsp;case&amp;nbsp;_&amp;nbsp;=&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;zip&quot;&lt;br /&gt; }&lt;/div&gt;&lt;p&gt;코드의 의도는, targets를 사용자가 선택한 파일들이라 가정한 뒤, 선택된 파일이 1개이면 그 파일의 확장자를, 아니면 &quot;zip&quot;이라는 확장자 extension에 할당하는 것이다. (다른 좋은 방법들이 있겠지만, 예시니까 이해하자) 그런데 여기에는 함정이 있다. 모든 Seq 유형이 ::를 이용한 분해를 지원하는 건 아니라는 사실이다. 다음 코드는 이전의 코드와 targets를 할당하는 방식만 다르고 모든 것이 동일하다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;val&amp;nbsp;targets:&amp;nbsp;Seq[File]&amp;nbsp;=&amp;nbsp;for&amp;nbsp;(i&amp;nbsp;&amp;lt;-&amp;nbsp;1&amp;nbsp;to&amp;nbsp;files.length)&amp;nbsp;yield&amp;nbsp;{&amp;nbsp;...&amp;nbsp;}&lt;br /&gt; val&amp;nbsp;extension:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;targets&amp;nbsp;match&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;case&amp;nbsp;head&amp;nbsp;::&amp;nbsp;Nil&amp;nbsp;=&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;head.getExtension&lt;br /&gt; &amp;nbsp;&amp;nbsp;case&amp;nbsp;_&amp;nbsp;=&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&quot;zip&quot;&lt;br /&gt; }&lt;/div&gt;&lt;p&gt;Scala 2.11 기준으로, 이 코드의 결과는 targets의 길이에 상관없이 &quot;zip&quot;이다. 그 얘기인즉, &quot;case head :: Nil =&amp;gt;&quot; 로 시작되는 PartialFunction[Any, Unit] 유형의 부분함수에 isDefinedAt(targets) 메서드를 호출했을 때 결과가 false라는 뜻이다. 이유는, 이 코드에서 for-yield 문의 실제 반환형이 Vector이며 이것은 Seq를 구현하지만 ::를 지원하지 않기 때문이다.&amp;nbsp;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;val&amp;nbsp;a&amp;nbsp;::&amp;nbsp;b&amp;nbsp;::&amp;nbsp;Nil&amp;nbsp;=&amp;nbsp;Seq(1,2)&lt;br /&gt; a:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;1&lt;br /&gt; b:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;2&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;val&amp;nbsp;a&amp;nbsp;::&amp;nbsp;b&amp;nbsp;::&amp;nbsp;Nil&amp;nbsp;=&amp;nbsp;Vector(1,2)&lt;br /&gt; &amp;lt;console&amp;gt;:13:&amp;nbsp;error:&amp;nbsp;constructor&amp;nbsp;cannot&amp;nbsp;be&amp;nbsp;instantiated&amp;nbsp;to&amp;nbsp;expected&amp;nbsp;type;&lt;br /&gt; &amp;nbsp;found&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;scala.collection.immutable.::[B]&lt;br /&gt; &amp;nbsp;required:&amp;nbsp;scala.collection.immutable.Vector[Int]&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;a&amp;nbsp;::&amp;nbsp;b&amp;nbsp;::&amp;nbsp;Nil&amp;nbsp;=&amp;nbsp;Vector(1,2)&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;^&lt;/div&gt;&lt;p&gt;::를 지원한다는 건 무엇일까? 에러 메시지로부터 알 수 있는 흥미로운 사실은, ::가 키워드가 아니라 유형이라는 점이다. 이것은 scala.collection.Immutable 패키지의 List.scala에 있는데, 정의는 다음과 같다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;final&amp;nbsp;case&amp;nbsp;class&amp;nbsp;::[B](override&amp;nbsp;val&amp;nbsp;head:&amp;nbsp;B,&amp;nbsp;private[scala]&amp;nbsp;var&amp;nbsp;tl:&amp;nbsp;List[B])&amp;nbsp;extends&amp;nbsp;List[B]&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;override&amp;nbsp;def&amp;nbsp;tail&amp;nbsp;:&amp;nbsp;List[B]&amp;nbsp;=&amp;nbsp;tl&lt;br /&gt; &amp;nbsp;&amp;nbsp;override&amp;nbsp;def&amp;nbsp;isEmpty:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;false&lt;br /&gt; }&lt;/div&gt;&lt;p&gt;::는 케이스 클래스이기 때문에 다음처럼 사용이 가능하다. (REPL에서 콜론은 예약어이므로 `를 붙였다.)&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;`::`(1,&amp;nbsp;List(2))&lt;br /&gt; res14:&amp;nbsp;scala.collection.immutable.::[Int]&amp;nbsp;=&amp;nbsp;List(1,&amp;nbsp;2)&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;val&amp;nbsp;`::`(a,&amp;nbsp;_)&amp;nbsp;=&amp;nbsp;`::`(1,&amp;nbsp;Nil)&lt;br /&gt; a:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;1&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;val&amp;nbsp;`::`(a,&amp;nbsp;`::`(b,&amp;nbsp;_))&amp;nbsp;=&amp;nbsp;`::`(1,&amp;nbsp;`::`(2,&amp;nbsp;Nil))&amp;nbsp;//&amp;nbsp;deep&amp;nbsp;match&lt;br /&gt; a:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;1&lt;br /&gt; b:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;2&lt;/div&gt;&lt;p&gt;그런데 사실, 바로 위 코드의 ::(a, ::(b, _))와 처음에 썼던 &#039;a :: b :: Nil&#039;은 동일한 기능을 한다. 둘다 스칼라가 지원하는 생성자 패턴(constructor pattern)이기 때문이다. 즉 케이스 클래스로부터 변수들을 다음과 같이 추출할 수 있다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;case&amp;nbsp;class&amp;nbsp;A(a:Int,&amp;nbsp;b:Int)&lt;br /&gt; defined&amp;nbsp;class&amp;nbsp;A&lt;br /&gt; &lt;br /&gt; scala&amp;gt;&amp;nbsp;val&amp;nbsp;a&amp;nbsp;A&amp;nbsp;b&amp;nbsp;=&amp;nbsp;A(1,2)&lt;br /&gt; a:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;1&lt;br /&gt; b:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;2&lt;/div&gt;&lt;p&gt;다소 생소하긴 하지만, 덕분에 ::라는 케이스 클래스를 만들고 val a :: b = ... 와 같은 생성자 패턴을 통해 변수들을 추출하는 것이 가능하다. 정리하면, 스칼라 컬렉션 패키지에 기본 내장된 :: 케이스 클래스는 List 유형을 대상으로 만들어졌기 때문에 Vector에는 사용할 수 없다. 또 그렇기 때문에 Vector.toList를 호출해 벡터를 리스트로 변환하면 문제없이 사용이 가능하다. 보통 List를 직접 쓰기보다는 Seq를 사용하기 때문에(Seq.apply의 반환형은 Seq지만, 반환되는 값의 실제 유형은 List다) a :: b 와 같은 생성자 패턴의 매칭 또한 별 생각없이 시도하다가 실수하기 쉽다. 특히 부분함수 중에 case _ =&amp;gt; 와 같은 완전함수가 존재한다면, 오류가 존재하는 것을 모르고 지나칠 수 있기 때문에 주의가 필요하다.&lt;/p&gt;&lt;h5 id=&quot;h1497858077393&quot;&gt;참고&lt;/h5&gt;&lt;p class=&quot;link&quot;&gt;&lt;strong&gt;How is the pattern matching for :: implemented?&lt;/strong&gt;&lt;br /&gt;&lt;a href=&quot;https://stackoverflow.com/questions/39606871/how-is-the-pattern-matching-for-implemented&quot;&gt;https://stackoverflow.com/questions/39606871/how-is-the-pattern-matching-for-implemented&lt;/a&gt;&lt;/p&gt;&lt;p class=&quot;link&quot;&gt;&lt;strong&gt;Scala pattern matching on sequences other than Lists&lt;/strong&gt;&lt;br /&gt;&lt;a href=&quot;https://stackoverflow.com/questions/6807540/scala-pattern-matching-on-sequences-other-than-lists&quot;&gt;https://stackoverflow.com/questions/6807540/scala-pattern-matching-on-sequences-other-than-lists&lt;/a&gt;&lt;/p&gt;</description>
		<category>비공정</category>	<category>Scala</category>	<category>Scala</category><category>Constructor Pattern</category><category>스칼라</category><category>생성자 패턴</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1170293</guid>
	<comments>https://manalith.org/zbxe/blog/1170293#comment</comments>			<pubDate>Mon, 19 Jun 2017 07:41:36 +0000</pubDate>
		</item><item>
			<title>대형 이미지 변환시의 ImageMagick 설정</title>
			<link>https://manalith.org/zbxe/blog/1169859</link>
				<description>&lt;p&gt;ImageMagick은 이미지를 만들거나 고치는데 널리 사용되는 자유 소프트웨어이다. 그런데 일반적으로 사용되지 않는 아주 큰 이미지를 다루려고 하는 경우 제약이 있다. 일단 다음을 보자.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;bash&quot; class=&quot;code&quot;&gt;$&amp;nbsp;convert&amp;nbsp;-size&amp;nbsp;5000x5000&amp;nbsp;xc:white&amp;nbsp;canvas.png&lt;/div&gt;&lt;p&gt;위 명령어는 흰색으로 채워진 가로 세로 각 5000 픽셀의 canvas.png라는 이미지를 생성한다. 정확하게는 5000x5000의 흰 캔버스 이미지(xc:white)를 입력으로 하여 canvas.png란 파일로 출력하는데, convert 명령을 이용해 새 이미지를 만들 때 이렇게 한다. 실행해 보면 아마 별 문제가 없을 것이다. 그런데 필자의 경우, 여기서 크기를 더 키우면 문제가 발생했다. (문제가 발생하는 정확한 크기는 다를 수 있다)&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;bash&quot; class=&quot;code&quot;&gt;$&amp;nbsp;convert&amp;nbsp;-size&amp;nbsp;10000x10000&amp;nbsp;xc:white&amp;nbsp;canvas.png&lt;br /&gt; convert-im6.q16:&amp;nbsp;DistributedPixelCache&amp;nbsp;&#039;127.0.0.1&#039;&amp;nbsp;@&amp;nbsp;error/distribute-cache.c/ConnectPixelCacheServer/244.&lt;br /&gt; convert-im6.q16:&amp;nbsp;cache&amp;nbsp;resources&amp;nbsp;exhausted&amp;nbsp;`canvas.png&#039;&amp;nbsp;@&amp;nbsp;error/cache.c/OpenPixelCache/3945.&lt;br /&gt; convert-im6.q16:&amp;nbsp;memory&amp;nbsp;allocation&amp;nbsp;failed&amp;nbsp;`canvas.png&#039;&amp;nbsp;@&amp;nbsp;error/png.c/WriteOnePNGImage/8756.&lt;br /&gt; &lt;br /&gt; $&amp;nbsp;convert&amp;nbsp;-size&amp;nbsp;30000x30000&amp;nbsp;xc:white&amp;nbsp;canvas.png&lt;br /&gt; convert-im6.q16:&amp;nbsp;width&amp;nbsp;or&amp;nbsp;height&amp;nbsp;exceeds&amp;nbsp;limit&amp;nbsp;`white&#039;&amp;nbsp;@&amp;nbsp;error/cache.c/OpenPixelCache/3802.&lt;br /&gt; convert-im6.q16:&amp;nbsp;no&amp;nbsp;images&amp;nbsp;defined&amp;nbsp;`canvas.png&#039;&amp;nbsp;@&amp;nbsp;error/convert.c/ConvertImageCommand/3258.&lt;/div&gt;&lt;p&gt;원하는 크기로 10000x10000을 입력했더니, 뭔가 디스크에 쓰는 것 같더니 곧 실패해버리고 만다. 이어서 더 큰 크기인 30000x30000으로 설정해 보았더니, 이번엔 뭔가 하는 것 같지도 않고 바로 에러를 내보낸다. 메시지를 보면, 뭔가 기본적인 제약이 설정되어 있는 것 같다.&lt;br /&gt;&lt;br /&gt;제약은 어떻게 확인할 수 있을까? identify라는 명령을 쓰면 된다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;bash&quot; class=&quot;code&quot;&gt;$&amp;nbsp;identify&amp;nbsp;-list&amp;nbsp;resource&lt;br /&gt; Resource&amp;nbsp;limits:&lt;br /&gt; &amp;nbsp;&amp;nbsp;Width:&amp;nbsp;16KP&lt;br /&gt; &amp;nbsp;&amp;nbsp;Height:&amp;nbsp;16KP&lt;br /&gt; &amp;nbsp;&amp;nbsp;Area:&amp;nbsp;128MP&lt;br /&gt; &amp;nbsp;&amp;nbsp;Memory:&amp;nbsp;256MiB&lt;br /&gt; &amp;nbsp;&amp;nbsp;Map:&amp;nbsp;512MiB&lt;br /&gt; &amp;nbsp;&amp;nbsp;Disk:&amp;nbsp;1GiB&lt;br /&gt; &amp;nbsp;&amp;nbsp;File:&amp;nbsp;768&lt;br /&gt; &amp;nbsp;&amp;nbsp;Thread:&amp;nbsp;8&lt;br /&gt; &amp;nbsp;&amp;nbsp;Throttle:&amp;nbsp;0&lt;br /&gt; &amp;nbsp;&amp;nbsp;Time:&amp;nbsp;unlimited&lt;/div&gt;&lt;p&gt;너비(Width) 및 높이(Height)를 보면 최대 16KP 까지로 제한되어있는데, 이 때문에 변환 가능한 이미지는 15999x15999 까지이다. 따라서 이 값을 수정하면 30000x30000 이미지도 문제없이 시도할 수 있다. 이 제약들은 policy.xml 이라는 설정파일에 저장되어 있는데, 우분투 17.04 기준 /etc/ImageMagick-6/policy.xml 이며 잘 모르겠으면 locate 명령을 이용해 파일을 검색하면 된다. (같은 제약들을 실행시 -limit 옵션으로도 설정할 수 있지만 policy.xml의 제약 이상으로 설정하지 못한다.)&lt;br /&gt;&lt;br /&gt;그런데 처음에 시도한 10000x10000 이미지의 경우, 너비 및 높이 제한이 아니라 캐시가 남아있지 않아서 종료되었다. 이 경우 변환에 필요한 자원이 부족하다는 것인데, 이 자원이 어느정도인지 어떻게 알아낼 수 있을까?&lt;br /&gt;&lt;br /&gt;다시 메시지를 살펴보자. 일단 convert-im6은 ImageMagick 6의 convert 명령이란 뜻이다. 뒤에 붙은 q16은 채널 심도(Channel Depth)라고도 하는 채널당 비트 수가 16인 버전임을 의미한다. 이것으로 변환에 필요한 자원을 추정할 수 있다. IM v6 Q16에서 1개의 픽셀은 RGBA 4개 채널로 이루어져 있고, 각 채널은 16비트씩을 사용하므로 1 픽셀당 8 바이트를 쓰게 된다(16 * 4 / 8). 이때 만일 100x100 크기의 이미지를 만든다면, 여기 소요되는 자원은 100 * 100 * 8 / 1000 이므로 80KB로 추정할 수 있다. (&lt;a href=&quot;http://www.imagemagick.org/discourse-server/viewtopic.php?t=31988&quot; target=&quot;_blank&quot;&gt;참고&lt;/a&gt;) 이를 10000x10000 이미지에 적용하면, 10000 * 10000 * 8 / 1e9 &amp;nbsp;= 0.8 GB다. 그리고 convert 명령은 input과 output으로 이루어지는 변환이기 때문에 각각 자원이 필요하다. 그래서 0.8GB에 다시 2를 곱하면 1.6GB 가량이 된다.&lt;br /&gt;&lt;br /&gt;convert 명령의 -debug 옵션을 통해 이것을 실제로 확인할 수 있다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;bash&quot; class=&quot;code&quot;&gt;$&amp;nbsp;convert&amp;nbsp;-debug&amp;nbsp;&quot;cache,resource&quot;&amp;nbsp;-size&amp;nbsp;10000x10000&amp;nbsp;xc:white&amp;nbsp;canvas.png&lt;br /&gt; 2017-06-09T00:33:30+09:00&amp;nbsp;0:00.000&amp;nbsp;0.000u&amp;nbsp;6.9.7&amp;nbsp;Cache&amp;nbsp;convert-im6.q16[10504]:&amp;nbsp;cache.c/SetPixelCacheExtent/3737/Cache&lt;br /&gt; &amp;nbsp;&amp;nbsp;extend&amp;nbsp;white[0]&amp;nbsp;(/tmp/magick-105046WGN3DhKI7D6[3],&amp;nbsp;disk,&amp;nbsp;800MB)&lt;br /&gt; 2017-06-09T00:33:30+09:00&amp;nbsp;0:00.000&amp;nbsp;0.000u&amp;nbsp;6.9.7&amp;nbsp;Cache&amp;nbsp;convert-im6.q16[10504]:&amp;nbsp;cache.c/OpenPixelCache/4044/Cache&lt;br /&gt; &amp;nbsp;&amp;nbsp;open&amp;nbsp;white[0]&amp;nbsp;(/tmp/magick-105046WGN3DhKI7D6[3],&amp;nbsp;Disk,&amp;nbsp;10000x10000&amp;nbsp;800MB)&lt;br /&gt; ...&lt;br /&gt; 2017-06-09T00:37:48+09:00&amp;nbsp;0:05.200&amp;nbsp;1.990u&amp;nbsp;6.9.7&amp;nbsp;Cache&amp;nbsp;convert-im6.q16[11872]:&amp;nbsp;cache.c/OpenPixelCache/4044/Cache&lt;br /&gt; &amp;nbsp;&amp;nbsp;open&amp;nbsp;canvas.png[0]&amp;nbsp;(/tmp/magick-11872aGzR1M4h9kb3[3],&amp;nbsp;Disk,&amp;nbsp;10000x10000&amp;nbsp;1GB)&lt;br /&gt; ...&lt;/div&gt;&lt;p&gt;흥미로운 것은 내보낼 파일의 포맷으로 JPG를 선택할 경우 예상대로 800MB(총 1.6GB)가 사용되는 반면, PNG 파일에는 로그에서처럼 25%의 추가적인 용량이 요구된다는 것이다. 아마 내부적으로 채널이 하나 더 추가된 CMYKA를 사용하는 것이 아닐까? 정확한 내용은 확인이 필요하다. 그래서 이 경우에는 총 1.8GB 가량이 필요하고, 실제로 Disk 값을 변경해 가며 테스트해 보면 1.6GiB(=1.72GB) 에서는 실패하고, 1.7GiB(=1.82GB) 에서는 성공한다. 이런 기준으로 30000x30000을 계산하면 필요한 자원 크기는 JPG의 경우 30000 * 30000 * 8 * 2 / 1e9 = 14.4GB, PNG의 경우 여기에 &amp;nbsp;1.8GB(25%)가 추가된 &amp;nbsp;16.2GB 이다. 이제 여기에 맞게 policy.xml 내용을 변경해주면 된다.&lt;br /&gt;&lt;/p&gt;</description>
		<category>비공정</category>	<category>리눅스</category>	<category>ImageMagick</category><category>convert</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1169859</guid>
	<comments>https://manalith.org/zbxe/blog/1169859#comment</comments>			<pubDate>Thu, 08 Jun 2017 16:11:36 +0000</pubDate>
		</item><item>
			<title>스칼라.js와 Scalajs-angular로 구현한 IsolateForm</title>
			<link>https://manalith.org/zbxe/blog/1169210</link>
				<description>&lt;p&gt;앵귤러JS 1.x에서 &amp;lt;form /&amp;gt; 태그를 중첩해서 사용할 경우, 내부 폼의 검증(validation) 상태가 외부 폼에 전이되는 경우가 있다. 일반적으로는 이렇게 폼을 중첩해서 쓸 일이 없겠지만, 만일 어떤 디렉티브가 있고 이 디렉티브에 자체적인 폼과 검증 등이 있다면 &amp;lt;form /&amp;gt; 태그 안에 사용시 의도치 않은 방식으로 동작할 수 있다. 이것을 막기 위해 아래 링크에서 제안하는 것이 IsolateForm 이다.&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;link&quot;&gt;&lt;strong&gt;Skip nested forms validation with AngularJS - Stack Overflow&lt;/strong&gt;&lt;br /&gt;&lt;a href=&quot;http://stackoverflow.com/questions/19333544/skip-nested-forms-validation-with-angularjs&quot;&gt;http://stackoverflow.com/questions/19333544/skip-nested-forms-validation-with-angularjs&lt;/a&gt;&lt;/p&gt;&lt;p&gt;IsolateForm의 코드는 다음과 같다. 링크된 웹페이지의 답변 가운데 앵귤러 1.5 이상에서 정상 동작하는 코드(CC BY-SA 3.0 라이선스)다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;jscript&quot; class=&quot;code&quot;&gt;//&amp;nbsp;출처&amp;nbsp;:&amp;nbsp;http://stackoverflow.com/questions/19333544/skip-nested-forms-validation-with-angularjs/37481846#37481846,&amp;nbsp;CC&amp;nbsp;BY-SA&amp;nbsp;3.0&lt;br /&gt; module.directive(&#039;isolateForm&#039;,&amp;nbsp;function()&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;return&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;restrict:&amp;nbsp;&#039;A&#039;,&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;require:&amp;nbsp;&#039;?form&#039;,&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;link:&amp;nbsp;function(scope,&amp;nbsp;element,&amp;nbsp;attrs,&amp;nbsp;formController)&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!formController)&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt; &lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;parentForm&amp;nbsp;=&amp;nbsp;formController.$$parentForm;&amp;nbsp;//&amp;nbsp;Note&amp;nbsp;this&amp;nbsp;uses&amp;nbsp;private&amp;nbsp;API&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!parentForm)&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt; &lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Remove&amp;nbsp;this&amp;nbsp;form&amp;nbsp;from&amp;nbsp;parent&amp;nbsp;controller&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parentForm.$removeControl(formController);&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt; &amp;nbsp;&amp;nbsp;};&lt;br /&gt; });&lt;/div&gt;&lt;p&gt;필자는 Scala.js를 사용하기 때문에, 위 디렉티브를 &lt;a href=&quot;https://github.com/greencatsoft/scalajs-angular&quot; target=&quot;_blank&quot;&gt;scalajs-angular&lt;/a&gt;&amp;nbsp;0.8로 재구현했다. 다음과 같다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;import&amp;nbsp;scala.scalajs.js&lt;br /&gt; &lt;br /&gt; import&amp;nbsp;org.scalajs.dom.Element&lt;br /&gt; &lt;br /&gt; import&amp;nbsp;com.greencatsoft.angularjs._&lt;br /&gt; import&amp;nbsp;com.greencatsoft.angularjs.core.Scope&lt;br /&gt; &lt;br /&gt; @injectable(&quot;isolateForm&quot;)&lt;br /&gt; class&amp;nbsp;IsolateFormDirective&amp;nbsp;extends&amp;nbsp;AttributeDirective&amp;nbsp;with&amp;nbsp;Requires&amp;nbsp;{&lt;br /&gt; &lt;br /&gt; &amp;nbsp;&amp;nbsp;this.requirements&amp;nbsp;+=&amp;nbsp;&quot;?form&quot;&lt;br /&gt; &lt;br /&gt; &amp;nbsp;&amp;nbsp;override&amp;nbsp;type&amp;nbsp;ScopeType&amp;nbsp;=&amp;nbsp;NestedScope[Scope]&lt;br /&gt; &lt;br /&gt; &amp;nbsp;&amp;nbsp;override&amp;nbsp;def&amp;nbsp;link(&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;scope:&amp;nbsp;NestedScope[Scope],&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;elems:&amp;nbsp;Seq[Element],&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;attrs:&amp;nbsp;Attributes,&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;controllers:&amp;nbsp;Either[Controller[_],&amp;nbsp;js.Any]*)&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;controllers&amp;nbsp;match&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Seq(Right(ctrl))&amp;nbsp;=&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;parentForm&amp;nbsp;=&amp;nbsp;ctrl.asInstanceOf[js.Dynamic].$$parentForm&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!js.isUndefined(parentForm))&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parentForm.$removeControl(ctrl)&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;_&amp;nbsp;=&amp;gt;&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt; &amp;nbsp;&amp;nbsp;}&lt;br /&gt; }&lt;/div&gt;</description>
		<category>비공정</category>	<category>Scala</category>	<category>앵귤러JS</category><category>AngularJS</category><category>스칼라</category><category>Scala</category><category>스칼라JS</category><category>ScalaJS</category><category>스칼라.js</category><category>Scala.js</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1169210</guid>
	<comments>https://manalith.org/zbxe/blog/1169210#comment</comments>			<pubDate>Mon, 22 May 2017 06:17:27 +0000</pubDate>
		</item><item>
			<title>스칼라.js의 확장 메서드 구현</title>
			<link>https://manalith.org/zbxe/blog/1169123</link>
				<description>&lt;p&gt;스칼라JS 환경에서 내부 코드가 어떻게 구성되는지 간단히 들여다보는 글이다. 기능적으로, 스칼라JS에서 확장 메서드는 예상대로 잘 동작하기 때문에 특별한 이슈가 있는지 궁금한 분은 이 글을 읽지 않아도 된다.&lt;/p&gt;&lt;p&gt;우선 다음 코드를 보자. 메인 코드 두 줄 모두 &quot;Output : 1&quot;이라는 글자를 출력하는 간단한 코드다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot; style=&quot;display: block;&quot;&gt;package&amp;nbsp;test&lt;br /&gt; &lt;br /&gt; import&amp;nbsp;scala.scalajs.js.JSApp&lt;br /&gt; &lt;br /&gt; object&amp;nbsp;TutorialApp&amp;nbsp;extends&amp;nbsp;JSApp&amp;nbsp;{&lt;br /&gt; &lt;br /&gt; &amp;nbsp;&amp;nbsp;implicit&amp;nbsp;class&amp;nbsp;RichInt(value:&amp;nbsp;Int)&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;str&amp;nbsp;=&amp;nbsp;s&quot;Output&amp;nbsp;:&amp;nbsp;$value&quot;&lt;br /&gt; &amp;nbsp;&amp;nbsp;}&lt;br /&gt; &lt;br /&gt; &amp;nbsp;&amp;nbsp;implicit&amp;nbsp;class&amp;nbsp;RichDouble(val&amp;nbsp;value:&amp;nbsp;Double)&amp;nbsp;extends&amp;nbsp;AnyVal&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;str&amp;nbsp;=&amp;nbsp;s&quot;Output&amp;nbsp;:&amp;nbsp;$value&quot;&lt;br /&gt; &amp;nbsp;&amp;nbsp;}&lt;br /&gt; &lt;br /&gt; &amp;nbsp;&amp;nbsp;def&amp;nbsp;main():&amp;nbsp;Unit&amp;nbsp;=&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(1.str)&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;println(1d.str)&lt;br /&gt; &amp;nbsp;&amp;nbsp;}&lt;br /&gt; }&lt;/div&gt;&lt;p&gt;스칼라의 암묵 변환 기능은 기존 객체의 변동 없이 새 기능을 추가하는 강력한 기능이다. 하지만 알려진대로, Value Class로 만들지 않고 사용하는 경우 객체 생성(Instantiation)이라는 오버헤드가 발생한다. 이 차이는 스칼라JS에서 어떻게 확인될까? 위 코드의 컴파일된 코드를 보자. 환경은 Scala.js 0.6.16에 가독성을 위해 성능이 떨어지는 ECMAScript6 출력 모드를 사용하였다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;jscript&quot; class=&quot;code&quot; style=&quot;display: block;&quot;&gt;class&amp;nbsp;$c_Ltest_TutorialApp$&amp;nbsp;extends&amp;nbsp;$c_O&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;init___()&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;this&lt;br /&gt; &amp;nbsp;&amp;nbsp;};&lt;br /&gt; &amp;nbsp;&amp;nbsp;main__V()&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;x&amp;nbsp;=&amp;nbsp;new&amp;nbsp;$c_Ltest_TutorialApp$RichInt().init___I(1).str__T();&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;this$2&amp;nbsp;=&amp;nbsp;$m_s_Console$();&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;this$3&amp;nbsp;=&amp;nbsp;$as_Ljava_io_PrintStream(this$2.outVar$2.v$1);&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this$3.java$lang$JSConsoleBasedPrintStream$$printString__T__V((x&amp;nbsp;+&amp;nbsp;&quot;\n&quot;));&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;x$1&amp;nbsp;=&amp;nbsp;$m_Ltest_TutorialApp$RichDouble$().str$extension__D__T(1.0);&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;this$5&amp;nbsp;=&amp;nbsp;$m_s_Console$();&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;this$6&amp;nbsp;=&amp;nbsp;$as_Ljava_io_PrintStream(this$5.outVar$2.v$1);&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this$6.java$lang$JSConsoleBasedPrintStream$$printString__T__V((x$1&amp;nbsp;+&amp;nbsp;&quot;\n&quot;))&lt;br /&gt; &amp;nbsp;&amp;nbsp;};&lt;br /&gt; &amp;nbsp;&amp;nbsp;$$js$exported$meth$main__O()&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.main__V()&lt;br /&gt; &amp;nbsp;&amp;nbsp;};&lt;br /&gt; &amp;nbsp;&amp;nbsp;&quot;main&quot;()&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;this.$$js$exported$meth$main__O()&lt;br /&gt; &amp;nbsp;&amp;nbsp;};&lt;br /&gt; }&lt;/div&gt;&lt;p&gt;우선 TutorialApp 클래스의 main 메서드를 보면, RichInt.str의 경우 예상대로 new 키워드를 사용해 새로운 객체를 만든 후 값을 출력하는 것을 볼 수 있다. 반면에 Value Class인 RichDouble의 경우 new 키워드를 사용하지는 않으나&amp;nbsp;$m_Ltest_TutorialApp$RichDouble$() 라는 함수를 호출하여 반환된 값에서 다시 str 메서드를 호출하는 것을 알 수 있다. 혹시 이것이 객체 생성 과정은 아닐까?&lt;br /&gt;확인을 위해 RichDouble의 컴파일된 코드를 한번 보자.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;jscript&quot; class=&quot;code&quot;&gt;class&amp;nbsp;$c_Ltest_TutorialApp$RichDouble$&amp;nbsp;extends&amp;nbsp;$c_O&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;init___()&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;this&lt;br /&gt; &amp;nbsp;&amp;nbsp;};&lt;br /&gt; &amp;nbsp;&amp;nbsp;str$extension__D__T($$this)&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;new&amp;nbsp;$c_s_StringContext().init___sc_Seq(new&amp;nbsp;$c_sjs_js_WrappedArray().init___sjs_js_Array([&quot;Output&amp;nbsp;:&amp;nbsp;&quot;,&amp;nbsp;&quot;&quot;])).s__sc_Seq__T(new&amp;nbsp;$c_sjs_js_WrappedArray().init___sjs_js_Array([$$this]))&lt;br /&gt; &amp;nbsp;&amp;nbsp;};&lt;br /&gt; }&lt;br /&gt; const&amp;nbsp;$d_Ltest_TutorialApp$RichDouble$&amp;nbsp;=&amp;nbsp;new&amp;nbsp;$TypeData().initClass({&lt;br /&gt; &amp;nbsp;&amp;nbsp;Ltest_TutorialApp$RichDouble$:&amp;nbsp;0&lt;br /&gt; },&amp;nbsp;false,&amp;nbsp;&quot;test.TutorialApp$RichDouble$&quot;,&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;Ltest_TutorialApp$RichDouble$:&amp;nbsp;1,&lt;br /&gt; &amp;nbsp;&amp;nbsp;O:&amp;nbsp;1&lt;br /&gt; });&lt;br /&gt; $c_Ltest_TutorialApp$RichDouble$.prototype.$classData&amp;nbsp;=&amp;nbsp;$d_Ltest_TutorialApp$RichDouble$;&lt;br /&gt; let&amp;nbsp;$n_Ltest_TutorialApp$RichDouble$&amp;nbsp;=&amp;nbsp;(void&amp;nbsp;0);&lt;br /&gt; const&amp;nbsp;$m_Ltest_TutorialApp$RichDouble$&amp;nbsp;=&amp;nbsp;(function()&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;if&amp;nbsp;((!$n_Ltest_TutorialApp$RichDouble$))&amp;nbsp;{&lt;br /&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$n_Ltest_TutorialApp$RichDouble$&amp;nbsp;=&amp;nbsp;new&amp;nbsp;$c_Ltest_TutorialApp$RichDouble$().init___()&lt;br /&gt; &amp;nbsp;&amp;nbsp;};&lt;br /&gt; &amp;nbsp;&amp;nbsp;return&amp;nbsp;$n_Ltest_TutorialApp$RichDouble$&lt;br /&gt; });&lt;/div&gt;&lt;p&gt;17번째 줄이 문제의 함수다. 보면 16번째 줄에서 선언된 변수에 객체가 존재하는지를 확인한 후 있으면 그대로 반환하고, 없으면 생성해 할당한 뒤 반환한다. 이런 지연초기화 및 싱글턴 구현 덕분에 객체 할당의 부담은 최초 호출시까지 미루어지고, 한번 호출된 이후로는 반복 호출되도 객체가 늘어나지 않는다. 반면 RichInt의 확장 메서드는 사용될 때 마다 객체가 초기화되고 이후 가비지 컬렉터에 의해 제거되기를 반복할 것이다.&lt;/p&gt;</description>
		<category>비공정</category>	<category>Scala</category>	<category>ScalaJS</category><category>Scala.js</category><category>스칼라.js</category><category>스칼라JS</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1169123</guid>
	<comments>https://manalith.org/zbxe/blog/1169123#comment</comments>			<pubDate>Sun, 21 May 2017 08:05:46 +0000</pubDate>
		</item><item>
			<title>Seq ++ Option과 Option ++ Seq의 차이</title>
			<link>https://manalith.org/zbxe/blog/1164663</link>
				<description>&lt;h3 id=&quot;h1483972816866&quot;&gt;++ 연산자 혹은 메서드&lt;/h3&gt;&lt;p&gt;스칼라의 컬렉션 유형들이 제공하는 ++ 연산자를 사용하면 두 컬렉션을 손쉽게 결합할 수 있다. 이를테면 다음처럼 쓸 수 있다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;Seq(1,&amp;nbsp;2,&amp;nbsp;3)&amp;nbsp;++&amp;nbsp;Seq(4,&amp;nbsp;5,&amp;nbsp;6)&lt;br /&gt; res0:&amp;nbsp;Seq[Int]&amp;nbsp;=&amp;nbsp;List(1,&amp;nbsp;2,&amp;nbsp;3,&amp;nbsp;4,&amp;nbsp;5,&amp;nbsp;6)&lt;/div&gt;&lt;p&gt;++는 정확히는 Seq를 이루는 여러 트레이트 가운데 하나에 선언된 메서드이다. 이 메서드는 다른 컬렉션을 인자로 받아서, 자신의 내용과 결합한 새로운 컬렉션을 반환한다.&lt;br /&gt;&lt;br /&gt;그런데 흥미롭게도, 컬렉션이 아닌 Option에도 이 ++ 연산자를 사용할 수 있다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;Option(1)&amp;nbsp;++&amp;nbsp;Option(2)&lt;br /&gt; res0:&amp;nbsp;Iterable[Int]&amp;nbsp;=&amp;nbsp;List(1,&amp;nbsp;2)&lt;/div&gt;&lt;h3 id=&quot;h1483972707482&quot;&gt;Option.++()의 미스터리&lt;/h3&gt;&lt;p&gt;Option의 API를 살펴보면&amp;nbsp;++ 메서드가 없다. 어떻게 된 걸까? 사실 이 경우 두 번의 암묵 변환과 하나의 암묵 인자가 적용된다.&lt;br /&gt;&lt;br /&gt;1) Option에는 암묵 적용 함수인&amp;nbsp;&lt;a href=&quot;http://www.scala-lang.org/api/current/scala/Option$.html#option2Iterable[A](xo:Option[A]):Iterable[A]&quot; target=&quot;_blank&quot;&gt;option2Iterable&lt;/a&gt;이 선언되어 있어 &lt;a href=&quot;http://www.scala-lang.org/api/current/scala/collection/Iterable.html&quot; target=&quot;_blank&quot;&gt;Iterable&lt;/a&gt;로 암묵 변환될 수 있다.&amp;nbsp;Iterable은&amp;nbsp;&lt;a href=&quot;http://www.scala-lang.org/api/current/scala/collection/TraversableLike.html#++[B](that:scala.collection.GenTraversableOnce[B]):Traversable[B]&quot; target=&quot;_blank&quot;&gt;++&lt;/a&gt;&amp;nbsp;메서드가 있는&amp;nbsp;&lt;a href=&quot;http://www.scala-lang.org/api/current/scala/collection/TraversableLike.html&quot; target=&quot;_blank&quot;&gt;TraversableLike&lt;/a&gt;를 상속한다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;val&amp;nbsp;iter:Iterable[Int]&amp;nbsp;=&amp;nbsp;Option(0)&lt;br /&gt; iter:&amp;nbsp;Iterable[Int]&amp;nbsp;=&amp;nbsp;List(0)&lt;/div&gt;&lt;p&gt;TraversableLike.++ 메서드의 시그니처는 다음과 같다.&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;def&amp;nbsp;++[B&amp;nbsp;&amp;gt;:&amp;nbsp;A,&amp;nbsp;That](that:&amp;nbsp;GenTraversableOnce[B])(implicit&amp;nbsp;bf:&amp;nbsp;CanBuildFrom[Repr,&amp;nbsp;B,&amp;nbsp;That]):&amp;nbsp;That&lt;/div&gt;&lt;p&gt;2) TraversableLike.++는 인자로 &lt;a href=&quot;http://www.scala-lang.org/api/current/scala/collection/GenTraversableLike.html&quot; target=&quot;_blank&quot;&gt;GenTraversableOnce&lt;/a&gt;를 요구한다. Iterable은 GenTraversableOnce를 상속한다. 이에 사용된 인자인 Option이 Iterable로 변환되어 ++의 인자로 삽입된다.&lt;br /&gt;&lt;br /&gt;3) TraversableLike.++는 암묵 인자로 CanBuildFrom[Repr, B, That]을 요구한다. 이에&amp;nbsp;&lt;a href=&quot;http://www.scala-lang.org/api/current/scala/collection/Iterable$.html#canBuildFrom[A]:scala.collection.generic.CanBuildFrom[scala.collection.Iterable.Coll,A,Iterable[A]]&quot; target=&quot;_blank&quot;&gt;Iterable.canBuildFrom&lt;/a&gt;이 삽입된다.&lt;br /&gt;&lt;br /&gt;4) TraversableLike.++는 암묵 인자의 That 유형 인자와 반환형이 같다. Iterable.canBuildFrom[Coll, A, Iterable[A]] 에서 That은 Iterable[A]이므로, 여기서 반환형은 Iterable[Int]이다.&lt;br /&gt;&lt;/p&gt;&lt;h3 id=&quot;h1483972768643&quot;&gt;암묵 변환의 효과&lt;/h3&gt;&lt;p&gt;이렇게 Option 유형이 쉽게 Iterable로 변환되는 덕에, 다음처럼 다른 컬렉션과 쉽게 결합이 가능하다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;Seq(1,&amp;nbsp;2)&amp;nbsp;++&amp;nbsp;Option(3)&lt;br /&gt; res0:&amp;nbsp;Seq[Int]&amp;nbsp;=&amp;nbsp;List(1,&amp;nbsp;2,&amp;nbsp;3)&lt;/div&gt;&lt;p&gt;그런데 앞서 설명한 시그니처 때문에 컬렉션의 뒤가 아니라 앞에 결합을 하려 하면 의도치 않은 반환형이 나올 수 있다. 예컨대 다음 코드에서 의도한 것은 새로운 Seq이지만 반환형은 Iterable이다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;Option(1)&amp;nbsp;++&amp;nbsp;Seq(2,&amp;nbsp;3)&lt;br /&gt; res0:&amp;nbsp;Iterable[Int]&amp;nbsp;=&amp;nbsp;List(1,&amp;nbsp;2,&amp;nbsp;3)&lt;/div&gt;&lt;p&gt;이 경우 Seq로 변환해 Seq의 ++ 메서드를 호출하는 방법이 있다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;Option(1).toSeq&amp;nbsp;++&amp;nbsp;Seq(2,3)&lt;br /&gt; res0:&amp;nbsp;Seq[Int]&amp;nbsp;=&amp;nbsp;List(1,&amp;nbsp;2,&amp;nbsp;3)&lt;/div&gt;&lt;p&gt;하지만 다음 메서드를 쓰면 더 간단하다.&lt;br /&gt;&lt;/p&gt;&lt;div editor_component=&quot;code_highlighter&quot; code_type=&quot;scala&quot; class=&quot;code&quot;&gt;scala&amp;gt;&amp;nbsp;Option(1)&amp;nbsp;++:&amp;nbsp;Seq(2,3)&lt;br /&gt; res0:&amp;nbsp;Seq[Int]&amp;nbsp;=&amp;nbsp;List(1,&amp;nbsp;2,&amp;nbsp;3)&lt;/div&gt;&lt;p&gt;&lt;a href=&quot;http://www.scala-lang.org/api/current/scala/collection/TraversableLike.html#++:[B&gt;:A,That](that:Traversable[B])(implicitbf:scala.collection.generic.CanBuildFrom[Repr,B,That]):That&quot; target=&quot;_blank&quot;&gt;++:&lt;/a&gt; 메서드 역시 TraversableLike에 있다. 이 메서드의 시그니처는 크게 구분되지 않지만, 그 내용을 보면 ++와는 달리 인자에 자신의 내용을 삽입한다.&lt;br /&gt;&lt;/p&gt;</description>
		<category>비공정</category>	<category>Scala</category>	<category>스칼라</category><category>Scala</category><category>스칼라.js</category><category>Scala.js</category>			<dc:creator>mmx900</dc:creator>
			<guid isPermaLink="true">https://manalith.org/zbxe/blog/1164663</guid>
	<comments>https://manalith.org/zbxe/blog/1164663#comment</comments>			<pubDate>Mon, 09 Jan 2017 08:21:16 +0000</pubDate>
		</item>	</channel>
</rss>
