関数結合子のみを使用したリンクリストの作成



今日のようなデータ構造なしでリンクリストを作成する方法を示しますObject or Arrays ; 代わりに、関数の組み合わせを使用します.
私は、あなたがリンクスリストが何であるかについて、すでによく知られていると仮定しています.場合は、リンクリストには、チェックアウトを必要とする
.
私の目標は、あなたが前に見たことがないかもしれない何かを公開することです.Currying、部分的なアプリケーション、クロージャと関数の組み合わせで可能なものを表示します.そして、最も重要なのは、それをやっているときに少し楽しい時を過す.
⚠️ この記事にはrunkitが組み込まれています.このページの例では、実行、変更、微調整、および再生を意図します.

関数コンパレーターとは何か?


定義Thinking Functionally: Combinators

The word "combinator" is used to describe functions whose result depends only on their parameters. That means there is no dependency on the outside world, and in particular, no other functions or global value can be accessed at all.

In practice, this means that a combinator function is limited to combining its parameters in various ways.


それは取るために多くのので、多分いくつかの例が役立つでしょうか?
/* ☝️ These are combinators */
const I = a => a
const K = a => b => a
const V = a => b => c => c (a) (b)
const B = a => b => c => a (b (c))
//        -    -    -    ---------
//         \   |   /        |
//           arguments   ---
//                      /
//       only arguments are used

/* 👎 These are not */
const nope = a => a.map(double)
//                  --- ------
//                 /           \    
//                /    ⚠️ reaching outside of the func
//               /
//     ⚠️ can't use map either.
const add => a => b => a + b
//                       -
//                      /
// ⚠️ Uh oh, `+` is not part of 'arguments'
上記のコードを要約するには、コンテナーはその引数だけを使用できます.外部関数、メソッド、演算子を除外します.
心配しないで、それはまだ少し混乱して大丈夫です.(⊙_☉)

放棄構造


典型的なリンクリストは、これらのようなデータ構造を使用します.
class Node {
  constructor(data, next) {
    this.data = data
    this.next = next
  }
}

/* or */

const node = (data, next) => ({ data, next })

/* or */

const node = (data, next) => [ data, next ]
しかし、我々はこれらのデータ構造のどれも使用しないでしょう.関数の組み合わせを使用します.
コンビナータプールの右端に飛び込む前に、我々の基本機能から始めましょうnode :
function node (data, next) {
//             ----  ----
//           /            \
//       our data       the next node
}
ではどうやってアクセスするのdata and next 使わずにnode オブジェクトのような?あなたが言うならばcallbacks , あなたは正しかった!
/////////////////
////
//📌 注意:これらのコードブロックを変更して実行できます!/
////
/////////////////
関数ノード( data , next , callback )
callback ( data , next )を返す

//バインドを使用してデータと次の値を保存できます.
ノード.bind ( null , data data , null )
//先頭から値を読み取るためにコールバックを使用します.
ヘッド( data , next )=>
データを返します
))>
私はこの実装を本当に気にしないbind . だから私はカレーに行くnode 機能は、私は適用する部分的なアプリケーションを使用することができますdata and next . これは、bind しかし、より良い構文で.
const node = data => next => callback => callback ( data ) ( next )
//-- -- --
//////
//パラメータはcurses ()--
////
//クロージャがデータを作成します
//最後にコールされたときにコールバックする.
//バインドを使用してデータと次の値を保存できます.
const head = node (' data ') ( NULL )
//------------
////
//引数データとNULLを部分的に適用できます.
//先頭から値を読み取るためにコールバックを使用します.
ヘッド( data =)next next >
データを返します
))>
あなたが非常に近い注意を払っていたならば、あなたはそれに気がつきましたnodeV 上記の組み合わせ!
だから今node に設定できます.
const node = V
そして、以下のようなノードを作成できます:
const evenOdd = node ('Even') ('Odd')
const leftRight = node ('Left') ('Right')
const yesNo = node ('Yes') ('No')
もし我々が部分的なアプリケーションが何をしているかのブレークダウンを見ているなら、次のようになります.
// first copy the node function
const evenOdd = data => next => callback => callback (data) (next)

// apply 'Even' to data.
const evenOdd =         next => callback => callback ('Even') (next)

// apply 'Odd' to next.
const evenOdd =                 callback => callback ('Even') ('Odd')

// We end up with this:
const evenOdd = callback => callback ('Even') ('Odd')
evenOdd では、callback . The callback 以下のような関数を期待します:
const callback = a => b => { /* ??? */ }
我々は今、我々は再生を開始できるポイントです.ヒットplay このrunkitで修正し、callback 返す'Left' .
const v = a => b => c => c ( a )( b )
ノード= v
左端( node )
//todo :コールバックを変更し、コードが' left 'を返す
コールバック= A => B =>{ }
レフトライト( callback )///
再びコードを変更して'Right' .
すごい!さあ、電話しましょう'Left' 機能data'Right' 機能next .
const data = a => _ => a
const next = _ => b => b
我々の新しい機能でそれをもう一度走らせてください.
const v = a => b => c => c ( a )( b )
ノード= v
データを取得する
次のようになります
左端( node )
コンソール.ログ(左データ( data ))
コンソール.ログ(左)
あなたは気がつきましたかdata も私たちと同じですK Combinator ?
// 💥 BOOM!
const data = K
next ほとんどはK Combinator , しかし、それは少し異なります.next リターンb , 中data リターンa . ちょっとしたトリックがあります.
// 🧙‍♀️ MAGIC!
const next = K (I)
このきちんとしたトリックは、全体の記事のためのインスピレーションでした.私はあなたが今2秒未満でこの問題を解決することができます賭ける!

そのリスト


我々がリンクされたリストに学んだものを翻訳しましょう.
A = A = A
コンストK = A => B => A
const v = a => b => c => c ( a )( b )
ノード= v
データ標準
次のようにします.
const nil = symbol (' nil ')//末尾を検出するオブジェクトだけです.
const first = node ('1st') (Nil)
//
////
//nil終了
const second = node (' 2 ') ( 1 )
//---
////
//次のノードとして最初のノードを渡します
const番目のノード( 3番目)
//---
////
//2番目のノードを次のノードとして渡す
コンソール.ログ( 3番目(データ))//=> ' 3 '
コンソール.log ( 3番目の(データ))//=> ' 2 nd '
コンソール.log ( 3番目( next ) ( data ))///=> ' 1 '

カウントリスト


リストを列挙してカウントを返す簡単な関数を作成できます.
A = A = A
コンストK = A => B => A
const v = a => b => c => c ( a )( b )
ノード= v
データ標準
次のようにします.
const nil = symbol (' nil ')
const length =( list , value = 0 )=>
リストの一覧
?値
: length ( list ( next ), value + 1 )
const first = node ('1st') (Nil)
const second = node (' 2 ') ( 1 )
const番目のノード( 3番目)
コンソール.log ( length ( 1 ))///= 1
コンソール.log ( length ( 2 ))///= 2
コンソール.ログ( length ( 3 ))///= 3

マップリスト


マッピングはArray .
A = A = A
コンストK = A => B => A
const v = a => b => c => c ( a )( b )
ノード= v
データ標準
次のようにします.
const nil = symbol (' nil ')
//この実装を心配しないでください.
//以下のコードをデモするだけです.
のリストを表示します
リストの一覧
?リスト
: node ( func ( list ( data )))( map ( func ( list ( next )) ))
const first = node ('1st') (Nil)
const second = node (' 2 ') ( 1 )
const番目のノード( 3番目)
const upper = x = > x . touppercase ()
const second = map ( 3番目)
コンソール.ログ( 3番目(データ))//=> ' 3 '
コンソール.log ( 3番目の(データ))//=> ' 2 nd '
コンソール.log ( 3番目( next ) ( data ))///=> ' 1 '

フィルタ


フィルタリングもArray .
A = A = A
コンストK = A => B => A
const v = a => b => c => c ( a )( b )
ノード= v
データ標準
次のようにします.
const nil = symbol (' nil ')
//この実装を心配しないでください.
//以下のコードをデモするだけです.
const filter = predicate => list =>
リスト== Nil?リスト
:述語( list )node ( list ( data ))( filter ( predicate ) ( list ( next ))
: filter ( predicate ) ( list ( next )
const first = node (1) (Nil)
第2ノード( 2 )(第1回)
const番目のノード( 3 )( 2 )
const 4番目のノード( 4 )( 3番目)
x = 0 = 0 =
コンセントevens =フィルタ( iSeven ) ( 4 )
コンソール.ログ( evens ( data ))///> 4
コンソール.log ( evens ( next ))

しかし、関数の組み合わせは本当に役に立ちますか?


確かに、この方法でリンクリストを作成する必要はありません.実際には、まずリンクリストを作成する必要はありません.それで、これはとにかくとにかくアカデミックです.
驚くべきことに、関数結合器の実用的な用途がいくつかあります!
あなたは、認識しないかもしれませんB Combinator
const B = a => b => c => a (b (c))
書かれていない限り
const compose = f => g => x => f (g (x))
そうですね.compose はまさにB Combinator ! あなたが好奇心旺盛ならpipeQ Combinator .
もう一つの有用なユーティリティ機能ですalways . ラムダはalways 図書館で.また、単純な機能の組み合わせでそれを再現することができます.
const always = K

const awesome = always ('Awesome!')

awesome () //=> 'Awesome!'
awesome (123) //=> 'Awesome!'
awesome ('hello') //=> 'Awesome!'
tap 私はよく使用する共通の関数です.以下のように書くことができます.それは副作用を管理するための素晴らしいです.
const tap = func => val => {
  func (val) // execute my side effect
  return val // return the original value
}
私たちも書くことができましたtap このように:
const tap = S (K)
これは、関数の組み合わせで作成することができます本当に便利なものの多くです!

概要

  • データ構造を使用せずにリンクリストを作成する方法を学びました.
  • どのような関数の組み合わせがどのように役に立つのかを学びました.
  • 我々は、データを格納するための、部分的なアプリケーションとクロージャを使用する方法を学んだ.
  • あなたが何を学んだかもしれない他の知ってみよう!
    あなたがRunKitの例について考えたことを知らせてください.私は私のポストにそれらを組み込むことを検討しています.
    関数の組み合わせについてもっと知りたいですか?コメントで知らせてください!
    あなたが機能JavaScriptが大好きな場合は、ここまたはTwitterで私に従ってください!