Bind and Destructure Block Arguments

Bind and Destructure block arguments

I'd want a solution that assigned [[1, 2], [3, 4]] to x, [1, 2] to y and [3, 4] to z

How about this:

irb(main):001:0> y, z = x = [[1, 2], [3, 4]]
=> [[1, 2], [3, 4]]
irb(main):002:0> y
=> [1, 2]
irb(main):003:0> z
=> [3, 4]
irb(main):004:0> x
=> [[1, 2], [3, 4]]

Update (Let's put this into a block)

Is this okey for you:

[[1, 2], [3, 4]].tap do |arr| 
y, z = x = arr
p x # => [[1, 2], [3, 4]]
p y # => [1, 2]
p z # => [3, 4]
end

Destructuring Ruby block arguments dynamically

In your specific case, you'd use a splat to collect up everything but the first value:

triples.each { |first, *rest| puts first }
#-----------------------^splat

The *rest notation just collects everything that's left into an Array called rest.

In general, there's not much point to creating an arbitrary number of local variables (second, third, ..., nth) because you wouldn't be able to do anything with them; you could probably whip up a sick mess of evals but we already have a fine and functional Array class so why bother?

Destructuring a variadic function with an as-binding: not possible?

If you want a variable number of args, you are missing an &:

(defn foo [& [x & more :as full-list]]
(println "x:" x)
(println "more:" more)
(println "full list:" full-list))

Clojure param definition has only one special case that is the & char to indicate a variadic number of arguments. The rest are plain simple named arguments.

Now each simple argument can be destructured using the map or list syntax. For example:

(defn foo [ x y ] ...)

Can be destructured like:

(defn foo [[x1 x2 & x-more :as x] {:keys [y1 y2 y3]}] ...)

So we are saying that we expect the first param to be a list of at least 2 elements and the second param to be a map with some keys. Note that this will still be a fn of two params and that Clojure is not going to enforce that x actually has at least two elements. If x is an empty list, x1 and x2 will be nil.

Coming back to your question, if you look at my answer you will see that my fn has 0 mandatory params, with a variable number of arguments, while the one that you have has 1 mandatory param with a a variable number of arguments. What I am doing is just destructuring the var arg.

Number of arguments in a ruby block

This works because Ruby supports destructuring.

Destructuring allows you to bind a set of variables to a corresponding set of values anywhere that you can normally bind a value to a single variable.

This allows the following to hold true:

arr = [1, 2]
x = arr
x == [1, 2] # true

y, z = arr
y == 1 # true
z == 2 # true

You can see from the following code that destructuring in arguments to blocks isn't unique to the built-in methods that take a block:

def my_method(arr)
yield arr
end

my_method([1, 2, 3]) {|x| puts x.inspect }
# => [1, 2, 3]
my_method([1, 2, 3]) {|x, y, z| puts x.inspect }
# => 1

Check out Destructuring with Ruby for more information.

How does Ruby Array #count handle multiple block arguments

Just like assignments, there's a (not-so-) secret shortcut. If the right-hand-side is an array and the left-hand-side has multiple variables, the array is splatted, so the following two lines are identical:

a, b, c = [1, 2, 3]
a, b, c = *[1, 2, 3]

While not the same thing, blocks have something in the same vein, when the yielded value is an array, and there are multiple parameters. Thus, these two blocks will act the same when you yield [1, 2, 3]:

do |a, b, c|
...
end

do |(a, b, c)|
...
end

So, in your case, the value gets deconstructed, as if you wrote this:

[[1,1], [2,2], [3,4]].count {|(a,b)| a != b} # => 1

If you had another value that you are passing along with the array, you would have to specify the structure explicitly, as the deconstruction of the array would not be automatic in the way we want:

[[1,1], [2,2], [3,4]].each.with_index.count {|e,i| i + 1 == e[1] }
# automatic deconstruction of [[1,1],0]:
# e=[1,1]; i=0

[[1,1], [2,2], [3,4]].each.with_index.count {|(a,b),i| i + 1 == b }
# automatic deconstruction of [[1,1],0], explicit deconstruction of [1,1]:
# a=1; b=1; i=0

[[1,1], [2,2], [3,4]].each.with_index.count {|a,b,i| i + 1 == b }
# automatic deconstruction of [[1,1],0]
# a=[1,1]; b=0; i=nil
# NOT what we want

Arguments to Class.new block

It turns out that the block argument is the class you are creating. Run this snippet in irb

Class.new do |what|
p what
end

and you will see something like

#<Class:0x000000022b2698>
=> #<Class:0x000000022b2698>

The first line of output is given by p what, and the second line shows the return value of Class.new, which we know is the class. You can see that the what is the same object as the return value of Class.new.

Conclusion: the block argument is not very useful because you can get the class instance just using self in that class. The only usage I can imagine of is using the trick called flat scope to create methods.

Foo = Class.new do |klass|
define_method :class_name do
klass.name
end
end

Foo.new.class_name #=> "Foo"

Yet this is not very useful either because an object can easily access its class with self.class.

ruby: name of this syntax, which splits positional argument of block

Yes, it is called destructuring.

So what is destructuring? The most concise definition I found is from Common Lisp the Language. Destructuring allows you to bind a set of variables to a corresponding set of values anywhere that you can normally bind a value to a single variable. It is a powerful feature of Clojure that lets you write some very elegant code. For more information about Clojure's features, I recommend you check out Jay Field's blog post on the subject. While destructuring in Ruby is not quite as powerful as Clojure, you can still do some cool stuff.

Why does destructuring an argument object affect it's 'this' value?

In the first case, by destructuring you've created a separate reference to the sayHi function. When you then call it from the body of foo, this becomes the context for this in the body of sayHi.

In the second case, you are calling it as person.sayHi, so the context in this case is the containing object - i.e. person. Within sayHi, this is person, and name is defined.

There's nothing magical about destructuring, if you manually create a reference to the function you'll get the same effect:

const bar = person => {
const sayHi = person.sayHi;
console.log(sayHi());
};

Why no destructing in def form?

def is a special form at the compiler level: it makes a Var. def has to be available and usable before destructuring is available. You see something similar with let*, a compiler primitive that supports no destructuring: then after several thousand lines in clojure/core.clj the language is finally powerful enough to provide a version of let with destructuring, as a macro on top of let*.

If you want, you can write a macro (say, def+) that does this for you. Personally I think it's kinda gross and wouldn't use it, but using a Lisp means getting to use a language that suits you personally.



Related Topics



Leave a reply



Submit