Closures vs Objects

2024/02/19(月) 20:39:15.91ID:VT95BnI9
It is well-known that closures and objects are equivalent. So are actors. Which one is the most fundamental?

よく知られたように、クロージャとオブジェクトは等価です。アクターも同様です。どの概念が最も基本的なのでしょうか?
2024/02/19(月) 20:49:06.93ID:VT95BnI9
A closure is a function with state in the local scope.
An object also has methods and state which belong to its instance.
These are compatible with each other.

クロージャはローカル・スコープに状態をもつ関数です。
オブジェクトもまたそのインスタンスに属するメソッドと状態を持ちます。
これらは互いに互換性があります。
2024/02/20(火) 09:36:12.34ID:fQy0X2aE
オブジェクトは複数のメソッドを持てる

クロージャは関数をひとつしか返せない

オブジェクトの勝ち
2024/02/20(火) 11:01:18.21ID:BFMbzAij
>>3
ハッシュテーブルに関数を追加して返すことで、複数のメソッドを返すことが可能
より柔軟に、オブジェクトとそれを操作する関数の組を返すこともできるし、無限個のメソッドをもったオブジェクトだって作れる
これらは書く対象によって使い分ければよいのであって、オブジェクトにメソッドが属しているのが常に最適とは限らない
たとえばイベントハンドラにコールバック関数として渡すような場合は、ひとつの関数として取得できたほうが便利
2024/02/20(火) 23:29:16.13ID:0cXn/136
クロージャのほうがprimitive
今時、高階関数やイテレータを書くのにわざわざデザインパターンとか使いたいかって話
クラスは、レキシカル環境つきオブジェクトだけが欲しくても必ず新しい名前空間が作られるし、単品で購入できない福袋みたいなもん
2024/02/21(水) 05:55:14.17ID:j79lZ4PO
二変数以上の関数のほとんどは、どの引数のオブジェクトに属しているともアプリオリには言えないし、オブジェクト指向は不自然
オブジェクトと引数は別物だし、もし関数を特定のオブジェクトに属するようにしたいなら、クロージャでラップすれば簡単にできる
2024/02/21(水) 05:56:37.93ID:j79lZ4PO
二変数以上の関数のほとんどは、どの引数のオブジェクトに属しているともアプリオリには言えないし、オブジェクト指向は不自然
オブジェクトと関数は別物だし、もし関数を特定のオブジェクトに属するようにしたいなら、クロージャでラップすれば簡単にできる
2024/02/21(水) 10:09:33.92ID:HBySQ9lj
2変数関数をクロージャにラップして1変数関数にしても、メソッドチェーンはできないよね
2024/02/21(水) 10:29:18.43ID:Cg6r5DK9
[1, 2, 3, 4, 5]にメソッドが属してれば

[1, 2, 3, 4, 5]
.filter(hoge) # ←戻り値もオブジェクト
.map(piyo) # ←これも
.sum()

みたいにできるけど、配列がオブジェクトじゃなかったら

map([1, 2, 3, 4, 5], hoge)

の戻り値はただの配列なので、繰り返し関数適用するには、Lispみたいな醜い入れ子にするしかない

sum(
 map(
  filter([1, 2, 3, 4, 5], hoge),
 piyo))
2024/02/21(水) 12:32:59.23ID:pWazjr0Z
>>8-9
これでどうよ

chain = (obj) => {
 return (f, ...args) => {
  if (!f) return obj
  return chain(f(obj, ...args))
 }
}

chain([1, 2, 3, 4, 5])(
 filter, (x) => x % 2)(
 map, (x) =>x * x)(
 sum)()
// => 35
2024/02/21(水) 15:43:45.17ID:im/wtuEl
一時期メタプログラミングとかいって、動的にクラスやメソッドを作れるのがすごいって言われてたけど、クロージャをデータ構造として使うならそれは自明にできることでは
2024/02/21(水) 16:28:56.20ID:4iB0jwLo
やり方が複数ある場合は、一番綺麗に書けるコードを採用するのが基本
2024/02/21(水) 22:54:29.50ID:DX/jvS2m
入れ子にしなくても関数合成でいいのでは?
メソッドチェーンっぽく書くには
Reverse-application operatorってのもあるけど
個人的には関数合成のほうがスッキリしててすこ

https://ideone.com/gCWxLP
let hoge n = n mod 2 = 1
let piyo = ( * ) 2
let () = print_int (List.fold_left (+) 0 (List.map piyo (List.filter hoge [1;2;3;4;5])))

(* function composition *)
let (<<) f g x = f (g x)
let f = print_int << List.fold_left (+) 0 << List.map piyo << List.filter hoge
let () = f [1;2;3;4;5]

let (>>) f g x = g (f x)
let f = List.filter hoge >> List.map piyo >> List.fold_left (+) 0 >> print_int
let () = f [1;2;3;4;5]

(* @@ Application operator *)
let () = print_int @@ List.fold_left (+) 0 @@ List.map piyo @@ List.filter hoge [1;2;3;4;5]

(* |> Reverse-application operator *)
let () = [1;2;3;4;5] |> List.filter hoge |> List.map piyo |> List.fold_left (+) 0 |> print_int
14デフォルトの名無しさん
垢版 |
2024/02/22(木) 01:30:07.21ID:seCGgTJE
Forth で書いてよ
2024/02/22(木) 08:01:15.94ID:5dknMZXg
>>13
カリー化のなせる技やね
2024/02/22(木) 08:40:34.15ID:GWomKHpC
chainl = (obj) => {
 return (f, arg) => {
  if (!f) return obj
  return chainl(f(arg, obj))
 }
}

cons = (x, xs) => [x, xs]

foreach = (lst, f) => {
 if(!lst) return
 [x, xs] = lst
 f(x)
 foreach(f, xs)
}

linkedList =
 chainl(null)(
  cons, 1)(
  cons, 2)(
  cons, 3)()

chainl(linkedList)(foreach, console.log)()
// 3
// 2
// 1

いろいろ書けるんだな
2024/02/22(木) 10:55:42.37ID:2PGen9gt
カリー化もクロージャと可変長引数があればできるね

currying = (f, ...args) => (...rest) => f(...args, ...rest)
2024/02/22(木) 11:04:45.60ID:2PGen9gt
これで、mapなどが

map(f, col)

と、関数を先にとる形でも、>>16のchainlを用いて

chainl([1, 2, 3, 4, 5])(
 currying(filter, (x) => x % 2))(
 currying(map, (x) => x * x))(
 sum)()

と書けるわけか
2024/02/22(木) 11:11:29.91ID:2PGen9gt
いや、単にchainlを

chainl = (obj) => {
 return (f, ...arg) => {
  if (!f) return obj
  return chainl(f(...arg, obj))
 }
}

とすればいいのか
2024/02/22(木) 12:04:47.68ID:0KLGqPZR
クロージャの真価はコンテキストを導入できること

(let ((a 1) (b 2)) (+ a b))

((lambda (a b) (+ a b)) 1 2)

は等価
なので、上のa + bの変わりに関数を返却すれば独立したスコープの状態をもつ何かを作れる

クラスベースのオブジェクト指向でも同様だが、そのようなコンテキストのテンプレート(= class)は静的にしか作れない
21デフォルトの名無しさん
垢版 |
2024/02/22(木) 12:15:29.98ID:Q3haYGVO
静的なほうがバグの発見は早そう
2024/02/22(木) 13:03:51.54ID:BdBf3uc1
なんでも自由を許すと無秩序になる
>>10だって、シグネチャを無視することが前提の設計になってる
(最後に結果を呼び出す時は第一引数のfを省略する)

TypeScriptが使われるのもそれが理由
今やPythonやRubyにも型がある
23デフォルトの名無しさん
垢版 |
2024/02/22(木) 13:23:57.40ID:v9QmWW2c
くだらん
https://stackoverflow.com/questions/2497801/closures-are-poor-mans-objects-and-vice-versa-what-does-this-mean
2024/02/22(木) 13:24:08.05ID:WrtsfcL/
型なんて、プログラマがちまちま書かずとも、コンパイラや型チェッカが実行前に検査してくれればいいと思う
2024/02/22(木) 15:02:53.21ID:bbu+R1aP
currying = (f) => {
 args = []
 apply = (a) => {
  if (a === undefined) return f(...args)
  args.push(a)
  return apply
 }
 return apply
}
2024/02/22(木) 18:39:34.82ID:SfbO+Gz+
クロージャはポリモーフィズムができない
オブジェクト指向言語ではたとえば比較演算子の動作を型によって変えられる
a.compare(b)とソースコードのどこに書いても、aの型に応じて適切にcompareの動きが選択される
変数の型によって実行する関数を変えるにはどうする?
まさか関数定義に型による条件分岐をいちいち書くわけじゃあるまいし
27デフォルトの名無しさん
垢版 |
2024/02/22(木) 19:07:57.79ID:v9QmWW2c
class構文が入るまでどうやってJSでクラスを定義してたのか調べれば分かるよ
2024/02/22(木) 19:20:20.77ID:bu5i+qrs
>>27
a.hoge()のようにしてダックタイピングするなら当然できる
hogeがa(or a.prototype)に属さないただの関数の場合でも
hogeの中で型による条件分岐を書く以外の方法でできるか
2024/02/22(木) 19:30:27.02ID:coyprm7i
https://www.yunabe.jp/docs/javascript_class_in_google.html
30デフォルトの名無しさん
垢版 |
2024/02/22(木) 19:41:55.54ID:v9QmWW2c
>>28
なんて?
2024/02/22(木) 20:46:51.05ID:5LGf2QVl
>>26,28
a, bではなくcompareのほうを、状態をもった関数オブジェクトとして生成する

一例として、

compareのクロージャには、「型名=>その型の引数を受け取った時に呼び出す関数」のテーブルを保持する
そのテーブルに関数を動的に登録するための関数を作成する

例:
IComparable = () => {
 dispatch = {}
 compare = (self, other) => { /*dispatchテーブルから関数を検索して実行*/ }
 implement = (type, fn) => { /* dispatchテーブルにfnを登録 */ }
 return [compare, implement]
}

もちろんdispatchの条件は型名じゃなくても、別の判定用関数を用意してもいい
2024/02/22(木) 20:49:14.36ID:5LGf2QVl
インタフェースの実装は、型定義に書くよりも使用時に動的にできた方が自然だ
たとえば木構造にイテレータを実装するとして、深さ優先探索にするのか幅優先探索にするのかは、その時々によって変わる。木構造の定義だけからは決まらない
2024/02/22(木) 22:14:42.54ID:BzyFji/L
最近のフレームワークはどんどん脱オブジェクト指向・関数型化してないか?
Pythonのフレームワークは機能のMixinは継承ではなくデコレータ(ようはクロージャ)によるものが多い
Reactなんか思想は元々関数型的だったけど、ついにクラスコンポーネントやめちゃった
2024/02/23(金) 00:14:17.41ID:vwgp24xa
cps = (f) => (...args) => args.pop()(f(...args))

eq = cps((a, b) => a === b)
sub = cps((a, b) => a - b)
mul = cps((a, b) => a * b)

fact = (n, k) => {
 eq(n, 0, (isZero) => {
  if(isZero) {
   return k(1)
  }
  else {
   return sub(n, 1, (prevn) =>{
    fact(prevn, (prevf) => {
     mul(n, prevf, k)
    })
   })
  }
 })
}

fact(5, console.log)
2024/02/23(金) 13:52:03.15ID:P8ZW/vvm
Closures are functions with contexts.
Objects are structures with contexts.
A structure can have functions as its member, and vice versa.
So both are equal.
2024/02/23(金) 19:20:29.47ID:mZKLNS+O
オブジェクトよりも、クロージャよりも、第一級継続のほうが強い
37デフォルトの名無しさん
垢版 |
2024/02/24(土) 00:22:58.31ID:PF21lJOY
>>36
パフォーマンスは?
2024/02/24(土) 06:28:09.62ID:dndVDS6q
>>37
1. ハードウェア/コンパイラの技術向上に期待する
2. ネックになる部分だけ最適化する
3. そもそもパフォーマンスが必要ならC++やRustなどで書く
39デフォルトの名無しさん
垢版 |
2024/02/24(土) 11:30:44.99ID:PF21lJOY
>>38
その回答、第一級継続関係無いやん。
2024/02/24(土) 21:14:52.39ID:4BGFiybC
オブジェクトとクロージャが使えることは、コードブロック(+ ローカル変数)を第一級オブジェクトに持っているのだと思える
継続が第一級オブジェクトなのは、プログラム実行時の任意の状態が第一級オブジェクトということ
41デフォルトの名無しさん
垢版 |
2024/02/24(土) 21:42:30.15ID:fbMBQzh4
>>40
継続使う場合のパフォーマンスは?
2024/02/25(日) 00:23:07.77ID:bNSutqDu
>>41
昨日からどうした
なにか嫌なことでもあったか?
レスを投稿する

5ちゃんねるの広告が気に入らない場合は、こちらをクリックしてください。