Project

General

Profile

Actions

Feature #17576

open

Partial Functions (procs, lambdas)

Added by temabolshakov (Tema Bolshakov) almost 2 years ago. Updated about 2 months ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:102216]

Description

We already have pattern matching and functions. Let's combine them and introduce a "partial procs" as first-class citizens.

What are partial procs? This is a function that works on a subset of arguments. The partial proc's main advantage is that a caller may decide in advance if this proc can accept an argument or not and do something different rather than calling it.

That's how it may look like:

partial_proc = proc do |arg|
in x if x.odd?
  "#{x} is odd"
end

One can check if a proc is defined on the argument

partial_proc.defined?(42) #=> false
partial_proc.defined?(41) #=> true

You can call such a partial proc and it raises an error when it's not defined on this argument:

partial_proc.call(42) #=> raises NoMatchingPatternError (42)
partial_proc.call(41) #=> 41 is odd

And finally, we can call or fallback to a default value:

partial_proc.call_or_else(42) { "fallback value" } #=> 'fallback value'
partial_proc.call_or_else(41) { "fallback value" } #=> 41 is odd

Updated by matz (Yukihiro Matsumoto) almost 2 years ago

Is there any real-world use-case? I don't see any of them.
Besides that, proposed syntax does not work well with normal blocks.

Matz.

Updated by pyromaniac (Arkadiy Zabazhanov) about 2 months ago

Isn't the topic starter talking about pattern arguments for function? This is something I'm personally looking froward to. It is similar to Elixir's functions pattern matching and increases the expressiveness of the language by order of magnitude.

For example, I have some complex entity and have a handler that does something with its attributes:

Discount = Data.define(:name, :code, :percentage)
Product = Data.define(:title, :price)

def calculate_discount({ price: Money & 0.. }, { percentage: BigDecimal & 0..100 })
  price * percentage
end

Now I have a loosely coupled handler that only specifies the requirements for the passed object with a pattern. You can even think of it as of a simple type system.

calculate_discount(product, discount)

Btw, while imagining this example I also thought maybe it is possible to use boolean-like pattern combinations, we have | user there already to alternate patterns, so why don't combine them with &?

This implementation is an equivalent of:

def calculate_discount(product, discount)
  product => { price: Money & 0.. }
  discount => { percentage: BigDecimal & 0..100 }

  price * percentage
end 

When and if there will be a way of putting a pattern into a variable somehow, I would do something like:

def self.defpm(method_name, pattern, &block)
  @method_patterns ||= {}
  @method_patterns[method_name] ||= {}
  @method_patterns[method_name][pattern] = block

  unless method_defined?(method_name)
    define_method(method_name, *args) do
      pattern, block = self.class.method_pattern[method_name].detect do |pattern, block|
        args in pattern
      end
      block.call(*args)
    end
  end
end

This code would not do the trick by I hope you got the idea: we define multiple methods with the same name but different argument patterns using the DSL and then the first matching pattern and its corresponding block is used when the method is called. We might also need some pattern match result object like we have for regexp match. Later, this can be turned from a DSL to a first-class syntax like:

defpm foo({ bar: 0.. })

end

The bottom line is that in Elixir this pattern matching for function signature is very expressive and makes code writing and support a way more joyful process.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0