stream()
설명
스트림은 스프레드시트 애플리케이션의 셀과 유사한 반응형 데이터 구조입니다.
예를 들어 스프레드시트에서 A1 = B1 + C1
일 때 B1
또는 C1
의 값을 변경하면 A1
의 값이 자동으로 변경됩니다.
마찬가지로 스트림이 다른 스트림에 종속되도록 설정하면 한 스트림의 값을 변경할 때 다른 스트림이 자동으로 업데이트되도록 할 수 있습니다. 이는 계산 비용이 많이 드는 경우 매번 다시 계산하지 않고 필요할 때만 실행할 수 있어 유용합니다.
Stream은 Mithril.js의 핵심 배포판에 포함되지 않습니다. Stream 모듈을 사용하려면 다음을 사용하십시오.
var Stream = require('mithril/stream');
개발 환경에서 번들링 도구를 지원하지 않는 경우, 모듈을 직접 다운로드하여 사용할 수도 있습니다.
<script src="https://unpkg.com/mithril/stream/stream.js"></script>
<script>
태그를 사용하여 직접 로드하는 경우 (CommonJS의 require
대신), 스트림 라이브러리는 window.m.stream
으로 전역 스코프에 노출됩니다. window.m
이 이미 정의된 경우 (예: 메인 Mithril.js 스크립트도 사용하는 경우) 기존 객체에 연결됩니다. 그렇지 않으면 새 window.m
을 만듭니다. Mithril.js와 스트림을 <script>
태그로 함께 사용하는 경우, mithril/stream
스크립트 태그 앞에 Mithril.js 스크립트 태그를 위치시켜야 합니다. mithril/stream
이 먼저 로드될 경우, mithril
이 나중에 mithril/stream
이 정의한 window.m
객체를 덮어쓰기 때문입니다. 이는 라이브러리가 CommonJS 모듈로 사용될 때 (require(...)
사용)는 문제가 되지 않습니다.
서명
스트림을 생성하는 방법입니다.
stream = Stream(value)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
value | any | 아니오 | 이 인수가 있으면 스트림의 값이 이 값으로 설정됩니다. |
반환 값 | Stream | 스트림을 반환합니다. |
정적 멤버
Stream.combine
업스트림이 업데이트될 때 반응적으로 업데이트되는 계산된 스트림을 생성합니다. 스트림 결합을 참조하십시오.
stream = Stream.combine(combiner, streams)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
combiner | (Stream..., Array) -> any | 예 | combiner 인수를 참고하십시오. |
streams | Array<Stream> | 예 | 결합할 스트림 배열 |
반환 값 | Stream | 스트림을 반환합니다. |
combiner
계산된 스트림의 값을 생성하는 방식을 지정합니다. 스트림 결합을 참조하십시오.
any = combiner(streams..., changed)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
streams... | Stream 타입의 가변 인자 | 아니오 | stream.combine 함수의 두 번째 인자로 전달되는 스트림들에 해당하는 0개 이상의 스트림 가변 인자 |
changed | Array<Stream> | 예 | 업데이트된 스트림 목록 |
반환 값 | any | 계산된 값을 반환합니다. |
Stream.merge
스트림 배열의 값을 값 배열로 가지는 스트림을 생성합니다.
stream = Stream.merge(streams)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
streams | Array<Stream> | 예 | 스트림 목록 |
반환 값 | Stream | 입력 스트림들의 값으로 이루어진 배열을 값으로 가지는 새로운 스트림을 반환합니다. |
Stream.scan
스트림의 각 값에 대해 누산기와 들어오는 값을 사용하여 함수를 호출하고, 그 결과로 새 스트림을 생성합니다.
누산기 함수 내에서 stream.SKIP
값을 반환하면, 해당 스트림에 의존하는 다른 스트림의 업데이트를 방지할 수 있습니다.
stream = Stream.scan(fn, accumulator, stream)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
fn | (accumulator, value) -> result |SKIP | 예 | 누산기 및 값 매개변수를 사용하고 동일한 유형의 새 누산기 값을 반환하는 함수 |
accumulator | any | 예 | 누산기의 시작 값 |
stream | Stream | 예 | 값을 포함하는 스트림 |
반환 값 | Stream | 결과를 포함하는 새 스트림을 반환합니다. |
Stream.scanMerge
스트림 및 스캔 함수 쌍의 배열을 가져와 주어진 함수를 사용하여 해당 스트림을 모두 단일 스트림으로 병합합니다.
stream = Stream.scanMerge(pairs, accumulator)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
pairs | Array<[Stream, (accumulator, value) -> value]> | 예 | 스트림과 스캔 함수로 이루어진 튜플 배열 |
accumulator | any | 예 | 누산기의 시작 값 |
반환 값 | Stream | 결과를 포함하는 새 스트림을 반환합니다. |
Stream.lift
업스트림이 업데이트될 때 반응적으로 업데이트되는 계산된 스트림을 생성합니다. 스트림 결합을 참조하십시오. combine
과 달리 입력 스트림은 가변적인 수의 인수(배열 대신)이고 콜백은 스트림 대신 스트림 값을 받습니다. changed
매개변수는 없습니다. 일반적으로 combine
보다 애플리케이션에 더 사용자 친화적인 함수입니다.
stream = Stream.lift(lifter, stream1, stream2, ...)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
lifter | (any...) -> any | 예 | lifter 인수를 참조하십시오. |
streams... | Stream 타입의 가변 인자 | 예 | 값을 추출할 스트림 |
반환 값 | Stream | 스트림을 반환합니다. |
lifter
계산된 스트림의 값을 생성하는 방식을 지정합니다. 스트림 결합을 참조하십시오.
any = lifter(streams...)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
streams... | Streams 의 스플랫 | 아니오 | stream.lift 함수에 전달된 스트림의 값에 해당하는 0개 이상의 값 가변 인자 |
반환 값 | any | 계산된 값을 반환합니다. |
Stream.SKIP
다운스트림 실행을 건너뛰기 위해 스트림 콜백에 반환할 수 있는 특수 값
Stream["fantasy-land/of"]
이 메서드는 기능적으로 stream
과 동일합니다. Fantasy Land의 Applicative 사양을 준수하기 위해 존재합니다. 자세한 내용은 Fantasy Land란 무엇입니까 섹션을 참조하십시오.
stream = Stream["fantasy-land/of"](value)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
value | any | 아니오 | 이 인수가 있으면 스트림의 값이 이 값으로 설정됩니다. |
반환 값 | Stream | 스트림을 반환합니다. |
인스턴스 멤버
stream.map
값이 콜백 함수의 결과로 설정된 종속 스트림을 만듭니다. 이 메서드는 stream["fantasy-land/map"]의 별칭입니다.
dependentStream = stream().map(callback)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
callback | any -> any | 예 | 콜백 함수의 반환 값이 새로운 스트림의 값이 됩니다. |
반환 값 | Stream | 스트림을 반환합니다. |
stream.end
true
로 설정하면 해당 스트림에 의존하는 다른 스트림과의 연결을 끊습니다. 종료된 상태를 참조하십시오.
endStream = stream().end
stream["fantasy-land/of"]
이 메서드는 기능적으로 stream
과 동일합니다. Fantasy Land의 Applicative 사양을 준수하기 위해 존재합니다. 자세한 내용은 Fantasy Land란 무엇입니까 섹션을 참조하십시오.
stream = stream()["fantasy-land/of"](value)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
value | any | 아니오 | 이 인수가 있으면 스트림의 값이 이 값으로 설정됩니다. |
반환 값 | Stream | 스트림을 반환합니다. |
stream["fantasy-land/map"]
값이 콜백 함수의 결과로 설정된 종속 스트림을 만듭니다. 스트림 체이닝을 참조하십시오.
이 메서드는 Fantasy Land의 Applicative 사양을 준수하기 위해 존재합니다. 자세한 내용은 Fantasy Land란 무엇입니까 섹션을 참조하십시오.
dependentStream = stream()["fantasy-land/map"](callback)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
callback | any -> any | 예 | 콜백 함수의 반환 값이 새로운 스트림의 값이 됩니다. |
반환 값 | Stream | 스트림을 반환합니다. |
stream["fantasy-land/ap"]
이 메서드의 이름은 apply
를 나타냅니다. 스트림 a
에 함수가 값으로 있는 경우 다른 스트림 b
는 b.ap(a)
에 대한 인수로 사용할 수 있습니다. ap
를 호출하면 스트림 b
의 값을 인수로 사용하여 함수가 호출되고 함수 호출의 결과인 다른 스트림이 반환됩니다. 이 메서드는 Fantasy Land의 Applicative 사양을 준수하기 위해 존재합니다. 자세한 내용은 Fantasy Land란 무엇입니까 섹션을 참조하십시오.
stream = stream()["fantasy-land/ap"](apply)
인수 | 타입 | 필수 | 설명 |
---|---|---|---|
apply | Stream | 예 | 값이 함수인 스트림 |
반환 값 | Stream | 스트림을 반환합니다. |
기본 사용법
스트림은 Mithril.js 코어 라이브러리에 포함되어 있지 않습니다. 프로젝트에 포함하려면 해당 모듈을 require하십시오.
var stream = require('mithril/stream');
변수로서의 스트림
stream()
은 스트림을 반환합니다. 가장 기본적인 수준에서 스트림은 변수 또는 getter-setter 속성과 유사하게 작동합니다. 상태를 저장하고 수정할 수 있습니다.
var username = stream('John');
console.log(username()); // "John"을 기록합니다.
username('John Doe');
console.log(username()); // "John Doe"를 기록합니다.
주요 차이점은 스트림이 함수이므로 고차 함수로 구성할 수 있다는 것입니다.
var users = stream();
// fetch API를 사용하여 서버로부터 사용자 정보를 요청
fetch('/api/users')
.then(function (response) {
return response.json();
})
.then(users);
위의 예에서 users
스트림은 요청이 완료될 때 응답 데이터로 채워집니다.
양방향 바인딩
스트림은 이벤트 콜백 등에서 값을 설정할 수도 있습니다.
// 스트림
var user = stream('');
// 스트림과 양방향으로 연결된 입력 필드
m('input', {
oninput: function (e) {
user(e.target.value);
},
value: user(),
});
위의 예에서 사용자가 입력을 입력하면 user
스트림이 입력 필드의 값으로 업데이트됩니다.
계산된 속성
스트림은 계산된 속성을 구현하는 데 유용합니다.
var title = stream('');
var slug = title.map(function (value) {
return value.toLowerCase().replace(/\W/g, '-');
});
title('Hello world');
console.log(slug()); // "hello-world"를 기록합니다.
위의 예에서 slug
의 값은 title
이 업데이트될 때 계산됩니다. slug
를 읽을 때 계산되는 것이 아닙니다.
물론 여러 스트림을 기반으로 속성을 계산할 수도 있습니다.
var firstName = stream('John');
var lastName = stream('Doe');
var fullName = stream.merge([firstName, lastName]).map(function (values) {
return values.join(' ');
});
console.log(fullName()); // "John Doe"를 기록합니다.
firstName('Mary');
console.log(fullName()); // "Mary Doe"를 기록합니다.
Mithril.js에서 계산된 속성은 원자적으로 업데이트됩니다. 여러 스트림에 의존하는 스트림은 계산된 속성의 종속성 그래프가 얼마나 복잡하든 값 업데이트당 두 번 이상 호출되지 않습니다.
스트림 체이닝
스트림은 map
메서드를 사용하여 연결할 수 있습니다. 이렇게 연결된 스트림을 _종속 스트림_이라고 합니다.
// 부모 스트림
var value = stream(1);
// 종속 스트림
var doubled = value.map(function (value) {
return value * 2;
});
console.log(doubled()); // 2를 기록합니다.
종속 스트림은 _반응형_입니다. 부모 스트림의 값이 업데이트될 때마다 해당 값이 업데이트됩니다. 이는 종속 스트림이 부모 스트림의 값이 설정되기 전이나 후에 생성되었는지 여부에 관계없이 발생합니다.
특수 값 stream.SKIP
을 반환하여 종속 스트림이 업데이트되지 않도록 할 수 있습니다.
var skipped = stream(1).map(function (value) {
return stream.SKIP;
});
skipped.map(function () {
// 실행되지 않음
});
스트림 결합
스트림은 둘 이상의 부모 스트림에 의존할 수 있습니다. 이러한 스트림은 stream.merge()
를 통해 만들 수 있습니다.
var a = stream('hello');
var b = stream('world');
var greeting = stream.merge([a, b]).map(function (values) {
return values.join(' ');
});
console.log(greeting()); // "hello world"를 기록합니다.
또는 도우미 함수 stream.lift()
를 사용할 수 있습니다.
var a = stream('hello');
var b = stream('world');
var greeting = stream.lift(
function (_a, _b) {
return _a + ' ' + _b;
},
a,
b
);
console.log(greeting()); // "hello world"를 기록합니다.
더욱 복잡한 사용 사례를 위해, 반응형 계산 과정에서 스트림 자체를 직접 다룰 수 있도록 stream.combine()
이라는 저수준 API도 제공됩니다.
var a = stream(5);
var b = stream(7);
var added = stream.combine(
function (a, b) {
return a() + b();
},
[a, b]
);
console.log(added()); // 12를 기록합니다.
스트림은 원하는 수의 스트림에 의존할 수 있으며 모든 업데이트가 동시에 처리되도록 보장됩니다(원자적 업데이트). 이렇게 하면 스트림 D에 대한 콜백이 B에 새 값이 있지만 C에 이전 값이 있는 경우와 같이 일관성 없는 값으로 호출되지 않습니다. 원자성은 불필요하게 다운스트림을 다시 계산하지 않는 성능 이점도 제공합니다.
특수 값 stream.SKIP
을 반환하여 종속 스트림이 업데이트되지 않도록 할 수 있습니다.
var skipped = stream.combine(
function (stream) {
return stream.SKIP;
},
[stream(1)]
);
skipped.map(function () {
// 실행되지 않음
});
스트림 상태
스트림은 특정 시점에 보류 상태, 활성 상태, 종료 상태 중 하나의 상태를 가집니다.
보류 중 상태
매개변수 없이 stream()
을 호출하여 보류 중인 스트림을 만들 수 있습니다.
var pending = stream();
스트림이 여러 스트림에 의존하고 있으며, 그 중 하나라도 보류 중인 상태라면, 해당 스트림 또한 보류 중인 상태가 되며 값을 업데이트하지 않습니다.
var a = stream(5);
var b = stream(); // 보류 중인 스트림
var added = stream.combine(
function (a, b) {
return a() + b();
},
[a, b]
);
console.log(added()); // undefined를 기록합니다.
위의 예에서 added
는 부모 b
도 보류 중이므로 보류 중인 스트림입니다.
이는 stream.map
을 통해 생성된 종속 스트림에도 적용됩니다.
var value = stream();
var doubled = value.map(function (value) {
return value * 2;
});
console.log(doubled()); // `doubled`가 보류 중이므로 undefined를 기록합니다.
활성 상태
스트림이 새로운 값을 받으면 활성 상태로 변경됩니다 (단, 스트림이 이미 종료된 상태가 아니라면).
var stream1 = stream('hello'); // stream1은 활성 상태입니다.
var stream2 = stream(); // stream2는 보류 중인 상태로 시작합니다.
stream2('world'); // 그런 다음 활성화됩니다.
여러 부모가 있는 종속 스트림은 모든 부모가 활성 상태인 경우 활성화됩니다.
var a = stream('hello');
var b = stream();
var greeting = stream.merge([a, b]).map(function (values) {
return values.join(' ');
});
위의 예에서 a
스트림은 활성 상태이지만 b
는 보류 중입니다. b("world")
를 설정하면 b
가 활성화되고 따라서 greeting
도 활성화되어 "hello world"
값을 갖도록 업데이트됩니다.
종료된 상태
stream.end(true)
를 호출하면 해당 스트림이 다른 스트림에 더 이상 영향을 주지 않도록 중단시킬 수 있습니다. 이렇게 하면 스트림과 종속 스트림 간의 연결이 끊어집니다.
var value = stream();
var doubled = value.map(function (value) {
return value * 2;
});
value.end(true); // 종료된 상태로 설정
value(5);
console.log(doubled());
// `doubled`가 더 이상 `value`에 의존하지 않으므로 undefined를 기록합니다.
종료된 스트림도 여전히 상태를 저장하는 컨테이너 역할을 합니다. 즉, 스트림이 종료된 후에도 값을 읽고 쓰는 getter-setter로 사용할 수 있습니다.
var value = stream(1);
value.end(true); // 종료된 상태로 설정
console.log(value(1)); // 1을 기록합니다.
value(2);
console.log(value()); // 2를 기록합니다.
스트림의 수명이 제한된 경우(예: DOM 요소가 드래그되는 동안에만 mousemove
이벤트에 반응하고 드롭된 후에는 반응하지 않는 경우) 스트림을 종료하는 것이 유용할 수 있습니다.
스트림 직렬화
스트림 객체를 JSON.stringify()
함수의 인자로 전달하면, 스트림이 가지고 있는 값이 JSON 문자열로 변환됩니다.
var value = stream(123);
var serialized = JSON.stringify(value);
console.log(serialized); // 123을 기록합니다.
스트림은 렌더링을 트리거하지 않습니다
Knockout과 같은 라이브러리와는 달리, Mithril.js의 스트림은 템플릿의 리렌더링을 자동으로 발생시키지 않습니다. 리렌더링은 Mithril.js 컴포넌트 뷰에 정의된 이벤트 핸들러, 라우트 변경, 또는 m.request
호출의 완료 시점에 발생합니다.
다른 비동기 이벤트(예: setTimeout
/setInterval
, 웹 소켓 구독, 타사 라이브러리 이벤트 핸들러 등)에 대한 응답으로 다시 그리기를 원하는 경우 m.redraw()
를 수동으로 호출해야 합니다.
Fantasy Land란 무엇입니까
Fantasy Land는 일반적인 대수 구조의 상호 운용성을 지정합니다. 쉽게 말해, Fantasy Land 명세를 준수하는 라이브러리들을 사용하면, 각 라이브러리가 내부적으로 어떻게 구현되었는지에 상관없이 동일하게 동작하는 재사용 가능한 함수형 코드를 작성할 수 있습니다.
예를 들어 plusOne
이라는 일반 함수를 만들고 싶다고 가정해 보겠습니다. 간단한 구현은 다음과 같습니다.
function plusOne(a) {
return a + 1;
}
이 구현의 단점은 숫자에만 사용할 수 있다는 것입니다. 하지만 변수 a
의 값을 생성하는 로직이 에러 상태 (예: Sanctuary 또는 Ramda-Fantasy 라이브러리의 Maybe 또는 Either 타입으로 감싸진 값)를 반환하거나, Mithril.js 스트림, Flyd 스트림 등의 다양한 타입이 될 수도 있습니다. 이상적으로는 a
가 가질 수 있는 모든 가능한 유형에 대해 동일한 함수의 유사한 버전을 작성하고 싶지 않고 래핑/래핑 해제/오류 처리 코드를 반복적으로 작성하고 싶지 않습니다.
이것이 Fantasy Land가 도움이 되는 이유입니다. Fantasy Land 대수의 관점에서 해당 함수를 다시 작성해 보겠습니다.
var fl = require('fantasy-land');
function plusOne(a) {
return a[fl.map](function (value) {
return value + 1;
});
}
이제 이 메서드는 R.Maybe
, S.Either
, stream
등과 같은 모든 Fantasy Land 호환 Functor와 함께 작동합니다.
이 예제는 복잡해 보일 수 있지만 복잡성의 절충점입니다. 간단한 plusOne
구현은 간단한 시스템이 있고 숫자만 증가시키는 경우에 적합하지만 Fantasy Land 구현은 많은 래퍼 추상화 및 재사용된 알고리즘이 있는 대규모 시스템이 있는 경우 더 강력해집니다.
Fantasy Land를 채택해야 하는지 여부를 결정할 때 기능적 프로그래밍에 대한 팀의 숙련도를 고려하고 팀이 코드 품질을 유지하기 위해 커밋할 수 있는 수준(새로운 기능을 작성하고 마감일을 맞추는 압력 대비)에 대해 현실적이어야 합니다. 함수형 프로그래밍 스타일은 작고 명확하게 정의된 함수들을 조합하고 활용하는 데 크게 의존하기 때문에, 탄탄한 문서화 시스템을 갖추고 있지 않거나 함수형 언어에 대한 경험이 부족한 팀에게는 적합하지 않을 수 있습니다.