I found myself writing code of the form...
val foo : Foo = ???
val step1 : Foo = {
if ( someCharacteristic( foo ) )
someTransformation( foo )
else
foo
}
val step2 : Foo = {
if ( otherCharacteristic( foo ) )
otherTransformation( foo )
else
foo
}
val result = step2;
That seemed awkward, and it seemed like this sort of ugly stepwise processing could be monadified. I've used but not written Scala monads; I thought I'd give it a shot. What I have seems to work, but I found proving the monad laws surprisingly challenging. I'm not sure I've got a valid monad, and wonder whether there is some subtle problem that would bite me someday if I haven't. FWIW, here's the code, any comments would be very welcome.
package dumbmonad;
import scala.language.implicitConversions;
object If {
implicit def unwrap[T]( ift : If[T] ) = ift.value;
def evaluate[T]( ift : If[T] ) : T = if ( ift.condition( ift.value ) ) ift.transformation( ift.value ) else ift.value;
def flatten[T]( outer : If[If[T]] ) : If[T] = evaluate( outer );
def apply[T]( value : T ) : If[T] = apply( value, _ => false, identity );
}
case class If[T]( val value : T, val condition : (T)=>Boolean, val transformation : (T)=>T ) {
lazy val evaluate : T = If.evaluate( this );
def map[S]( f : (T) => S ) : If[S] = If( f( this.evaluate ) )
def flatMap[S]( f : (T) => If[S] ) : If[S] = If.flatten( this.map( f ) )
}
I use it like this:
import dumbmonad._;
import dumbmonad.If._;
val foo : Foo = ???
for {
a <- If( foo, someCharacteristic, someTransformation )
b <- If( a, otherCharacteristic, otherTransformation )
} yield b;
val result : Foo = b; // implicit call to If.unwrap( ... )
Is this terrible?
1 Answer 1
What you are describing are simply functions.
To take a related example from mathematics,
/ x, if x >= 0
abs(x) = |
\ -x, otherwise
And you are just composing functions: f(g(x))
.
You can somewhat make the correspondence to monads, but it is a bit of a stretch. I think it is more that monads are extensions of functions rather than the other way around.
flatMap
would be the composition operator: o
, ie (f o g)(x) = f(g(x))
. And unit
would just be the identity operation: I(x) = x
.
The three monad laws:
- Associativity:
f o (g o h) = (f o g) o h
- Left unit:
I o f = f
- Right unit:
f o I = f
So instead of defining your If
class, you can just stick to def
, the usual function definition. Functional programming!!! In your case you can compose all your functions in any order since they always input/output Foo
type. You can compose functions in Scala with val h = f _ compose h _
, or using andThen
.
Map
(or even just a sequence of tuples) and two basic operations:filter
andfoldLeft
. :) Although it doesn't really answer the question. \$\endgroup\$