Functional Programming for rubists



Functional Programming for rubists

0 0


railsclub-2015-slides


On Github niquola / railsclub-2015-slides

Functional Programming for rubists

Created by niquola / @niquola

Nikolay Ryzhikov

Функции

применяються

Данные

значения

Нет объектов и классов

Чистые Функции

    list = [1, 2, 3]

    new_list = push(list, 4)

    puts list # => [1, 2, 3]

    puts new_list # => [1, 2, 3, 4]
    

Чистые Функции

    mapa = {a: 1}

    new_mapa = assoc(mapa, :b, 2)

    puts mapa #=> {a: 1}

    puts new_mapa #=> {a: 1, b: 2}
    

Всегда одно и тоже

cos(π) => 1
cos(π) => 1
cos(π) => 1

Чего бы вокруг не происходило

cos(π) => 1

Вообще ничего

не поменять

И нет переменных

Вообще ничего не прячут

передавать все явно

Kомпозиция

      f(g(h(x)))

      f.g.h x

      (-> (h x) g f)
      

У нас Программы - графы

A у них - деревья

Не выполняются

а вычисляються

Функции высших порядков

Принимают функции

параметризация вычисления

Возвращают функции

конструкторы вычислений

fold (reduce)

      # fold(col, init-value, &proc)
      sum = fold([1,2,3], 0,+)
      prod = fold([1,2,3], 0,*)

      map = fn (f, col)
        fold col, [] do |acc,x|
          push(acc,f(x))
        end
      end

      filter = fn (pred, col)
        fold col, [] do |acc,x|
          pred(x) ?  push(acc,f(x)) : acc
        end
      end
      

wrap (middle-ware)

      fn wrap(f)
        fn (x)
          if ???
            f(x)
          else
            return ???
          end
        end
      end
      

Like our decorators or chain of responsibility

compose wrappers

      f' =  wrap(f)
      f'' = other_wrap'(f')
      ....
      ....
      ....
      f''''(x)
      

Transducers

      # chain(acc,elem)
      chain = compose(
        filter(odd?),
        map(double),
        take(5)
      )

      reduce(chain, [], coll)
      

собираем вычисление, потом выполняем

Изменяемое состояние

Software Transactional Memory

      # isolation
      transaction do
        change(state1)
        change(state2)
      end
      

Ни тебе race condition, dead lock, incosistent read

Функциональное ядро

А как они пишут WEB?

Web Server у них функция

      fn server(req)
        return {
          status: 200,
          body: "Hello",
          headers: {
            ContentType: 'text'
          }
        }
      end

      server({uri: "/home", params: {q: '???'}})
      

Обертки для Middle-Ware

      server = compose(
        wrap_params,
        wrap_cookies,
        wrap_json_request,
        wrap_form_params,
        dispatch
      )

      server(request)
      

Наш rack

Routing

      # dispatch(request, routes_spec) -> handler

      fn dispatch(request, routes_spec)
         handler = find_in_routes(request, routes_spec)
         handler(request)
      end
      

Они ничего не знают про паттерны

Route Spec просто данные

      route_spec = {
        admin: {
          users: {
            POST: users/create
            GET:  users/index
           }
        }
      }

      route_spec = [
        ["admin", "users", "POST", users/create],
        ["admin", "users", "GET",  users/create],
      ]
      

1-3 files; ~100 SLOC total

gem "journey"

1.6K SLOC + 1.5K tests

*10 times bigger

View & Templating у них тоже функция

      fn layout(content)
        [:html,
          [:header,
            [:title "layout"]],
          [:body, menu() content]]
      end


      def render(data)
        layout(
          [:ul, map data {|x|
             [:li, link_to(x.url, x.text)]}]
      end
      

~600 SLOC + form builder

Я же говорю одни функции

      layout1(
        layout2(content1()),
        layout3(content2())
      )

      def decorate_user(user)
        user[:full_name] = user[:first_name] + user[:last_name]
        user
      end

      def link_to(url, label)
        [:a, {href: url}, label]
      end
      

нет проблем с лайаутами и хэлперами

haml

2.6K SLOC + 3K tests

*10 times bigger

А ORM?

Ups, а объектов то нет

А работа с базой?

      query = {
        select: [:id, :name], from: "users",
        where: [:name "=" x]
      }

      fn by_name(name, query)
        merge(query, :where,["name", "ilike", name])
      end

      fn active_users(query)
        merge(query, :where,["status", "=", "active"])
      end

      conn/exec( sql( active_users( by_name("nicola", query)))
      

опять функции ~ 800 SLOC

Arel

3.4K SLOC + 5K tests

Функциональный Web?

  String:HTTP -> fns ->  String:SQL -> DB -> fns -> String:HTTP
      

Функциональный UI?

    State -> ui_function -> VirtualDom -> React.diff/patch -> DOM

    Event -> change State -> State'

    State' -> ui_function -> VirtualDom -> React.diff/patch -> DOM
      

А фреймворки есть?

Почти нет, много маленьких библиотек, которые легко склеиваются

А почему они маленькие?

Сильно сфокусированны на задаче

SOLIDные

Но мы тоже

Почему мы тогда тратим в 10 раз больше сил?

Почему композиция - проблема?

Почему декомпозиция тоже проблема?

Почему в рабстве у фреймворка?

Почему нам нужно больше думать?

  • SOLID?
  • Service Object?
  • DCI?
  • Mixins/Traits?

а они просто для этого пишут функцию

Thx

Q?

Functional Programming for rubists Created by niquola / @niquola