본문 바로가기

카테고리 없음

함수형 자바스크립트 : Frisby 교수의 Composable Functional JavaScript

반응형

https://egghead.io/courses/professor-frisby-introduces-composable-functional-javascript

 

Professor Frisby Introduces Composable Functional JavaScript

This course teaches the ubiquitous abstractions for modeling pure functional programs. Functional languages have adopted these algebraic constructs across the board as a way to compose applications in a principled way. We can do the same in JavaScript. Whi

egghead.io

아래 책으로 유명한 저자의 라이브 코딩 무료 강의이다.

아래 책은 아직 보진 않았지만, 위 동영상의 내용이 좋아 나중에 한번 봐야겠다.

https://github.com/MostlyAdequate/mostly-adequate-guide

https://mostly-adequate.gitbook.io/mostly-adequate-guide/

 

Introduction - mostly-adequate-guide

That said, typed functional languages will, without a doubt, be the best place to code in the style presented by this book. JavaScript will be our means of learning a paradigm, where you apply it is up to you. Luckily, the interfaces are mathematical and,

mostly-adequate.gitbook.io

추가로 저자의 유료강의도 있다. 저자는 Salesforce의 개발자다.

그렇게 비싸지는 않아 영어실력이 있으면 한번 보는것도 추천한다.

https://www.pluralsight.com/courses/hardcore-functional-programming-javascript

 

선언적 코드를 박스타입으로 리팩터링

Either 타입으로 null 체크 로직을 composable하게

Either를 이용한 리팩터링

코드 주소가 없어서 화면 복붙

Semigroup으로  type 만들기

  • semigroup은 concat 메서드를 갖고 있는 타입
    • 배열
    • 스트링
  • 결합 법칙이 성립
  • 새로운 세미그룹 만들기
    • 불리언 ALL
    • Fist

https://embed.plnkr.co/DOEGK6vi1ChEC2wvmtH2?show=script.js,preview 

monoid로 실패에 안전한 함수 합성

const util = require("util");
import { List } from "immutable-ext";

const fromNullable = x => (x != null ? Right(x) : Left(null));

// Example 1
const Sum = x => ({
  x,
  concat: ({ x: y }) => Sum(x + y),
  [util.inspect.custom]: () => `Sum(${x})`
});

Sum.empty = () => Sum(0);

// Example 2
const Product = x => ({
  x,
  concat: ({ x: y }) => Product(x * y),
  [util.inspect.custom]: () => `Product(${x})`
});

Product.empty = () => Product(1);

// Example 3
const Any = x => ({
  x,
  concat: ({ x: y }) => Any(x || y),
  [util.inspect.custom]: () => `Any(${x})`
});

Any.empty = () => Any(false);

// Example 4
const All = x => ({
  x,
  concat: ({ x: y }) => All(x && y),
  [util.inspect.custom]: () => `All(${x})`
});

All.empty = () => All(true);

// Example 5
const Max = x => ({
  x,
  concat: ({ x: y }) => Max(x > y ? x : y),
  [util.inspect.custom]: () => `Max(${x})`
});

Max.empty = () => Max(-Infinity);

// Example 6
const Min = x => ({
  x,
  concat: ({ x: y }) => Min(x < y ? x : y),
  [util.inspect.custom]: () => `Min(${x})`
});

Min.empty = () => Min(Infinity);

// Example 7
const Right = x => ({
  chain: f => f(x),
  map: f => Right(f(x)),
  fold: (f, g) => g(x),
  concat: o => o.fold(e => Left(e), r => Right(x.concat(r))),
  [util.inspect.custom]: () => `Right(${x})`
});

const Left = x => ({
  chain: f => Left(x),
  map: f => Left(x),
  fold: (f, g) => f(x),
  concat: o => Left(x),
  [util.inspect.custom]: () => `Left(${x})`
});

const stats = List.of(
  { page: "Home", views: 40 },
  { page: "About", views: 10 },
  { page: "Blog", views: null }
);

stats.foldMap(x => fromNullable(x.views).map(Sum), Right(Sum(0)));
// Left(null)

// Example 8
const First = either => ({
  fold: f => f(either),
  concat: o => (either.isLeft ? o : First(either)),
  [util.inspect.custom]: () => `Min(${x})`
});

First.empty = () => First(Left());

const find = (xs, f) =>
  List(xs)
    .foldMap(x => First(f(x) ? Right(x) : Left()), First.empty())
    .fold(x => x);

find([3, 4, 5, 6, 7], x => x > 4);
// Right(5)

// Example 9
const Fn = f => ({ fold: f, concat: o => Fn(x => f(x).concat(o.fold(x))) });

const hasVowels = x => !!x.match(/[aeiou]/gi);
const longWord = x => x.length >= 5;
const both = Fn(
	// All(hasVowels(x))
  compose(
    All,
    hasVowels
  )
).concat(

  Fn(
  	 // All(longWord(x))
    compose(
      All,
      longWord
    )
  )
);

[("gym", "bird", "lilac")].filter(x => both.fold(x).x);
// [lilac]

// Example 10
const Pair = (x, y) => ({
  x,
  y,
  concat: ({ x: x1, y: y1 }) => Pair(x.concat(x1), y.concat(y1)),
  [util.inspect.custom]: () => `Pair(${x}, ${y})`
});

foldMap으로 타입 언박싱

매핑 후 폴딩

  • 레코드의 속성, 리스트의 값을 모노이드로 매핑 후 폴드할 수 있음.
  • 레코드 타입의 속성들을 모노이드로 생각하자

https://embed.plnkr.co/jpjMXRd2Hz8zIBLEM0Q4?show=script.js,preview 

LazyBox로 지연평가

thunk를 이용하여 코드의 평가를 늦춤

fold가 코드 실행을 트리거

프로미스, 옵저버블 스트림과 같은 구체적인 값 대신 내부에 함수가 있습니다.

View code for this lesson

Task로 부수효과 캡처

예제는 Folktale 라이브러리 사용

Either랑 다르게 부수효과가 존재함을 보여줌.

부수효과가 발생하는 부분을 격리!

View code for this lesson

Task로 비동기 액션 캡처

파일 읽기/쓰기, HTTP통신, 로깅 등 온갖 사이드이펙트를 격리

부수효과가 있는 코드

당신은 Functor를 사용하고 있었다.

합성 보존과 Identity

합성 순서를 주의한다.

fx.map(f).map(g) === fx.map(x=>g(f(x))

const id = x =>x
fx.map(id) === id(fx)

of를 이용해 Pointed Functor로 Lift

Either의 초기값이 Right인 이유는? 

초기값이 Left면 함수 합성이 무시됨.

직관은 성공했다 가정하고 연산을 예약하는 것임.

당신은 Monad를 사용하고 있었다.

  • F.of
  • F.chain (flatMap, bind, >>=)

체인의 특징은 두개의 중첩 타입을 flatten 한다는 것임.

항등원과 결합법칙 성립

Box(3) === Box(3)

모든 모나드에서 map 메서드를 파생할 수 있습니다.

이것은 모나드가 functor라는 것을 의미합니다.

또한 모나드는 applicative functor면서 pointed functor입니다.

즉, 모나드를 이용해 모든 다른 메서드를 정의할 수 있습니다.

하지만 가장 중요한 기능은 두 가지 타입을 결합하는 것입니다. (chain)

m.chain(x=>M.of(f(x)))
많은 사람들이 M 단어를 두려워합니다.
나는 모나드가 chainable이라고 부르를 수 있을 만큼 쉬운 것이라고 믿습니다.
본질을 꿰뚫는 말입니다.

사실, 범주 이론에 대한 수년간의 수학적 연구는
모나드가 어떻게 작동하는지,
모나드가 보유하는 속성,
어떻게 상호 작용하는지에 대해 설명했습니다.

Build Curried functions

딱히 설명할 게 없어보임. 다인자 함수을 일인자 함수의 연속으로

Applicative Functors for multiple arguments

드디어 다시 코드가 제공됨

View code for this lesson

ap는 값으로 함수를 받았을 때, 해당 함수에 박스 안의 값을 적용하는 방법임.

펑터 법칙을 만족함

F(x).map(f) === F(f).ap(F(x))

lift 형태를 사용하면 add(1,2)랑 모양이 비슷해서 이해가 쉬움

Apply multiple functors as arguments to a function (Applicatives)

중첨된 코드를 리팩토링 해보자

 

const $ = selector => Either.of({selector,height:10})

const getScreenSize = screen => head => foot => screen - (head.height + foot.height)
const res3 = $('header').chain(head=>$('footer').map(foot=>getScreenSize(800)(head)(foot)))

console.log("result 3 ",res3);
const res4 = Either.of(getScreenSize(800)).ap($('header')).ap($('footer'));
console.log("result 4 ",res4);

List comprehensions with Applicative Functors

외부 라이브러리를 사용해야 함.

파이썬 등 다른 언어에서 많이 볼 수 있는 친구

['teeshirt-large-black',...] 3 * 3 * 2

View code for this lesson

Write applicatives for concurrent actions

이것도 구현은 외부 라이브러리를 사용한다.

아래는 프로미스 헬과 동일한 형태이다.

프로미스 헬 해결과 동시 실행 효과를 얻을 수 있다.

Leapfrogging types with Traversable

[Task] => Task([]) 형태로 바꾸는 것 (Promise. all)

Task.of를 통해 바깥 타입을 지정해준다.

모든 타입에 traverse 인스턴스가 있는 것은 아닙니다.
즉, traverse를 정의할 수 없습니다. 예를 들어 스트림과 같은 것입니다.
그러나 더 중요한 것은 traverse가 있다면 traverse 할 수 있다는 것입니다.
Traverse는 뒤의 함수가 applicative functor를 반환할 것으로 예상합니다. Task는 ap이므로 동작합니다.
대부분의 타입은 ap입니다.

Maintaining structure whilst asyncing

큰 중첩 트리 또는 파일 시스템이 있고 분기, 분해 및 재결합 또는 변형 없이 구조를 탐색하려는 경우 매우 강력합니다.

Principled type conversions with Natural Transformations

자연 변환은 단순하게 말하면 타입 변환임

nt(x).map(f) == nt(x.map(f) 규칙을 만족해야 함.

한 펑터에서 다른 펑터로....

nt(x).map(f) == nt(x.map(f) 규칙을 만족해야 함.
F a -> G a 

박스에서 Left하면 자연 변환 규칙을 어기는 것임

 

둘은 box(200)으로 같음
Left는 다름

Apply Natural Transformations in everyday work

Array => List

array=>Right

또다른 예제 : 타입 중첩 > 트랜스포머로 해결

두번 사용 가능

Isomorphisms and round trip data transformations

isomorphism ??

이것은 흥미로운 관계입니다.

이것이 의미하는 바는 이러한 함수가 우리의 데이터 타입이 다른 데이터 타입과 동일한 정보를 보유하고 있음을 증명한다는 것입니다.

나는 문자열이 문자 배열과 동형이라고 주장합니다.

이 두 데이터 타입은 동일한 정보를 보유해야 하며 손실 없이 변환할 수 있어야 합니다.

hel...

스트링은 모든 배열과 isomorpic하지 않음, 캐릭터 배열과만 동형임

 

또다른 예제 

Left(unbdefined)

Build a data flow for a real world app

다음 두 강의랑 이어지는 내용임

스포티파이 api에서 두 아티스트의 공통점을 찾기 위한 계획을 세웁니다.

필요한 데이터를을 확보할 수 있도록 데이터 흐름을 스케치합니다.

 

Retrieve and use data from an api with pure functional constructs

Task로 되어있으나  Promise를 사용할 수 있음.

  • http 요청을 통해 Promise (resolved, rejected)를 가져옴
  • then 체인을 통해 map함
  • then / catch 양쪽에서 모나드 트랜스포머 사용
    • catch면 그대로 task
    • then이면 이전에 then()을 통해 포함한 Either를 확인하여 reject할 것인지, resolve할 것인지 결정.

2. https://github.com/DrBoolean/spotify-fp-example/tree/master

Find the intersection of sets with Semigroups

intersection 타입을 만들고, concat 메서드를 정의하여 교집합을 정의한다.
같은 타입의 파라미터 리스트, 튜플을 만나면 tranverse를 생각한다.

3. View code on GitHub

 

반응형