Summary
My friend James Ward was explaining some of the struggles he had learning Scala, in particular partial functions.
Advertisement
Note: I'm leaving the original text in here because that's what people were commenting on. But on further study, I realized that neither James nor I had enough of a grasp of this topic to discuss it. In particular, Scala's PartialFunction is quite different than what I suspect we were trying to figure out: Partially Applied Functions -- unfortunately the names of the two concepts are quite similar. The PartialFunction only accepts a subset of inputs, and is used, according to Odersky, to "write control structures that are not easily expressible otherwise." Thus it may be more analogous to the Strategy design pattern, although I'm not certain about that. A reply inthis postseems to make a case that PartialFunction is a kind of Chain of Responsibility pattern (I find similarities between Strategy and Chain of Responsibility, myself).
scala> List(41, "cat") collect { case i: Int ⇒ i + 1 }
res1: List[Int] = List(42)
The original article:
He tried to understand that one concept for a couple of months before it made sense to him. Admittedly, partial functions are not intuitive for anyone who has been schooled in traditional programming, but still, looking at the problem he was trying to solve it seemed like James was required to expend too much effort relative to the simplicity of the problem (as he pointed out, now that he understands the concept it seems straightforward).
He showed me the code, and it was basically a situation where there was common code in the existing function, and the partial function completion allowed the programmer to add the custom code. A light bulb went on: "It's the Template Method design pattern!" (Here I was thinking of partially-applied functions).
And my initial reaction was that the template method shouldn't be this hard to understand; that Scala had made life more difficult.
However, after sleeping on it I remembered the observation that several people have made: "A design pattern represents a language failing." Objects aren't really designed to solve this particular problem, so you have to do the Template Method to compensate. What you're actually doing is saying "here's common code, you fill in the custom code," and if a functional programming idiom does that for you, then partial function completion is actually more obvious and a better fit. I initially interpreted the problem through the lens of the Template Method pattern because that's the way I already knew how to analyze it, but the more I think about it the more Template Method seems like an awkward patch and partial functions seem like the elegant approach.
With this new perspective, the next time I approach partial functions in Scala I will have to see if it feels better -- often once you really get the motivation behind a feature it becomes transparent.
Taking a step back, I have a worry about Scala, which is what I'll call "The Perl Effect." Perl prided itself on allowing "more than one way to do it." As a result, you could see many different implementations of the same code, often remarkably different. You'd have to parse through the code before realizing that all the programmer was doing was something you could translate into a simpler form. Often Perl programmers tended towards excessive cleverness. Even worse is when the clever code is your own, and you can't figure it out because it's been several days or weeks since you wrote it. The Zen of Python includes the maxim "There should be one -- and preferably only one -- obvious way to do it" precisely to counterpoint Perl's "There's more than one way to do it."
One might even suggest that Perl has reached end-of-life as a language (does anyone still believe that Perl 6 will ever arrive?) precisely because of the infinite varieties of expression, which sounds good in theory but is not so maintainable in practice.
Scala too has "more than one way to do it." Most of the Scala programmers today are early adopters, and early adopters tend to love cleverness, so they have been publishing lots of clever code. I suspect that the same brain quirk that allows them to be early adopters makes them think that once they've figured something out it should be obvious to everyone else. This kind of code does not do the Scala community any good, because it scares people off and gives Scala the reputation of being quite a difficult language.
It doesn't have to be that way.
Perhaps it's time to start a style guide, and/or a dictionary of Scala programming idioms to express the "one and preferably only one obvious way to do it." Without something like this and the culture behind it, Scala runs the risk of becoming a community of clever early adopters rather than a mainstream programming language.
I remember seeing plenty of what would now seem like strange Java code before the publication of Joshua Bloch's Effective Java. It seems to me like that book helped define idiomatic Java. Scala could surely benefit from something similar.
Gosh, if the "partial function" notion is going to throw you, then probably all of functional programming is going to throw you. Partial functions are just a really basic concept, not just in programming, but even in just math. I don't think Scala is the problem here -- probably any language that uses tried-and-true logic constructs is going to be a big problem for James.
Saying a partial function is "like the Template Pattern I'm so familiar with" is comparable to saying a hammer and a nail is "like the left-shoe-and-railroad-spike solution I've been using up until now". Partial functions are a natural and logical concept that's been around for well over 100 years now, while the Template Pattern is a kludge invented relatively recently to try to get the OO paradigm to do something it was not designed to do.
> Gosh, if the "partial function" notion is going to throw > you, then probably all of functional programming is going > to throw you. Partial functions are just a really basic > concept...
> One might even suggest that Perl has reached end-of-life as > a language (does anyone still believe that Perl 6 will ever > arrive?) precisely because of the infinite varieties of > expression
You make several good points in the post, however I feel a need to point out this non sequitur.
Perl does have several ways to do it, and this has affected the language and the community using it (although extensions to the language have been turned down for providing too many ways to do something).
But the parenthetical is a non sequitur. Perl 6 has been in development for a decade not "because of the infinite varieties of expression" but because the VM built for Perl 6 was designed to run other languages as well. A general VM like that, especially for dynamic languages, will take much longer to write than a VM that is tightly coupled to the language it runs. Delays in implementing a general VM don't make much of a statement on the virtues or vices of one of the languages that can be compiled to that VM.
You could point out that the current Perl grammar is undecidable ( http://perlmonks.org/?node_id=663393 ), or a recent study that beginners have as hard a time figuring out Perl code (without a teacher) as they do figuring out a language syntax largely based on chance ( http://www.cs.siue.edu/~astefik/papers/StefikPlateau2011.pdf ). But the example you chose seems to be largely irrelevant.
For the record, the Parrot VM is in good enough shape to use for production work, at least on UNIX like platforms ( http://parrot.org/ ), and Perl 6 is very close to 1.0 ( http://perl6.org/ ).
Indeed, your references to Perl indicate a misunderstanding of Perl and its community.
Perl 6 is not the successor to Perl 5; in fact, it's not a language at all, but rather a spec, in the same way that Common Lisp is a spec with many implementations.
The name Perl 6 is misleading, but that's a wholly separate issue.
Perl 5 code can be confusing, in that any code can be confusing if you don't understand the idiomatic usage and core principles of the language. Looking at Perl as it was commonly used in 1991, for example, would indeed be painful, but less because of the nature of Perl, and more the state of programming practices before it was hip to be pretty.
This is a very silly claim. Design patterns are a pretty universal concept, all languages have them, just different ones. And they are particularly useful in languages that allow several ways to do the same thing, by definition.
> the more I think about it the more Template Method seems like an awkward patch and partial functions seem like the elegant approach.
Partial functions and template methods have very little in common.
Applying a function partially only applies that one function and its code, nothing more. What you vary is the parameters you pass to it, not the code. It's a very rigid approach compared to the template method.
A better metaphor for the template method would be a method which lets you replace arbitrary subsets of its body with your own.
Something closer to the point you were trying to make would be a function that accepts functions as parameters, or in other words, higher order functions. Now this comes closer to letting you achieve what the template method does.
Instead of everyone throwing around a lot of ideas and concepts maybe someone could help the coders in the group by providing a couple of examples that demonstrate the points made here. There is not one line of code to clarify anyones point of view. A picture (or line of code) is worth a thousand words.
> Instead of everyone throwing around a lot of ideas and > concepts maybe someone could help the coders in the group > by providing a couple of examples that demonstrate the > points made here. There is not one line of code to clarify > anyones point of view. A picture (or line of code) is > worth a thousand words.
Exactly. I've been waiting for days now just for an answer to my question, so that a discussion can proceed from there.
val f1: PartialFunction[Any,String] = { case "1" => "One" case "2" => "Two" case "3" => "Three" } // f1: PartialFunction[Any,String] = <function1>
val f2: PartialFunction[Any,String] = { case 1 => "One" case 2 => "Two" case 3 => "Three" } // f2: PartialFunction[Any,String] = <function1>
val f3 = f1 orElse f2 // f3: PartialFunction[Any,String] = <function1>
f3("1") // res0: String = One
f3(1) // res1: String = One
f3("3") // res2: String = Three
f3("a") // scala.MatchError: a (of class java.lang.String) // at $anonfun$1.apply(<console>:7) // at $anonfun$1.apply(<console>:7) // at scala.PartialFunction$$anon$1.apply(PartialFunction.scala:46)
I am confused about what is confusing about this example. Yes, there is a pattern match failure here. That's no different than if one were manually writing Java and doing something like
if (s.equals("1")) { return "One"; } else if (s.equals("2")) { return "Two"; ... else { throw new MatchError(s); }
etc. It seems like a straightforward runtime error to me.
> > val f1: PartialFunction[Any,String] = { > case "1" => "One" > case "2" => "Two" > case "3" => "Three" > } > // f1: PartialFunction[Any,String] = <function1> > > val f2: PartialFunction[Any,String] = { > case 1 => "One" > case 2 => "Two" > case 3 => "Three" > } > // f2: PartialFunction[Any,String] = <function1> > > val f3 = f1 orElse f2 > // f3: PartialFunction[Any,String] = <function1> > > f3("1") > // res0: String = One > > f3(1) > // res1: String = One > > f3("3") > // res2: String = Three > > f3("a") > // scala.MatchError: a (of class java.lang.String) > // at $anonfun$1.apply(<console>:7) > // at $anonfun$1.apply(<console>:7) > // at > scala.PartialFunction$$anon$1.apply(PartialFunction.scala:4 > 6) >
> I am confused about what is confusing about this example. > Yes, there is a pattern match failure here. Partial functions are defined for specific value ranges. The example just showed how you can compose two partial functions, one on {"1","2","3"} and another on {1,2,3}, into one of the union of those. Partial functions gives a runtime error when you hit outside their defined value ranges.
The example wasn't meant to highlight the error but the composability of partial functions. You can use a if-else in Java but it's not equivalent to partial functions. You cannot compose those, nor can you pass them as arguments to functions as is. In addition, you can also use extractors to decompose the value you're pattern matching.
Flat View: This topic has 19 replies
on 2 pages
[
12
|
»
]