`
RednaxelaFX
  • 浏览: 3048105 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

没专门语法支持的语言里用monad好难看

阅读更多
上周在JavaEye问答看到求一个逻辑运算结果,其中De Morgan定律的应用如night_stalker老兄所说,并不困难。不过就这么应用定律来推算或许不直观,所以当时我就想换个角度,写段C#代码来穷举验证问题中的表达式在应用De Morgan定律前后的真假值是否总是相同。

通过LINQ,代码很简洁:
using System;
using System.Linq;

static class Program {
  static void Main(string[] args) {
    var booleanValues = new [] { true, false };
    var equal = !(from a1 in booleanValues
                  from a2 in booleanValues
                  from a3 in booleanValues
                  from b1 in booleanValues
                  from b2 in booleanValues
                  from b3 in booleanValues
                  select (!((a1 && b1) || (a2 && b2) || (a3 && b3))) ==
                         (!(a1 && b1) && !(a2 && b2) && !(a3 && b3)))
                  .Contains(false);
    Console.WriteLine(equal); // true
  }
}

这段代码看起来应该算是比较直观的。不过……似乎又不太直观?

对许多人来说,或许显式用循环会直观得多:
using System;

static class Program {
  static void Main(string[] args) {
    var booleanValues = new [] { true, false };
    var equal = true;
    foreach (var a1 in booleanValues) {
      foreach (var a2 in booleanValues) {
        foreach (var a3 in booleanValues) {
          foreach (var b1 in booleanValues) {
            foreach (var b2 in booleanValues) {
              foreach (var b3 in booleanValues) {
                if ((!((a1 && b1) || (a2 && b2) || (a3 && b3))) !=
                    (!(a1 && b1) && !(a2 && b2) && !(a3 && b3))) {
                  equal = false;
                  break;
                }
              }
              if (!equal) break;
            }
            if (!equal) break;
          }
          if (!equal) break;
        }
        if (!equal) break;
      }
      if (!equal) break;
    }
    Console.WriteLine(equal); // true
  }
}

但这代码好丑啊 =v=
好吧我作 弊了。就以上两个版本的代码来比较,它们实际的执行过程是不同的;LINQ的版本是会把所有可能性都算出来之后再看是否Contains(),因为Contains是一个eager operator,不像Select、Where那些是lazy的;循环版是遇到不相等的状况就直接退出循环了。不过反正两个版本算出来的结果都一样,用来对比也不算过分。

其实C#的foreach循环已经掩盖了许多细节,不需要显式用下标或者IEnumerator来遍历容器。不过我还是不喜欢在这种场景用显式的循环,主要是因为循环嵌套起来非常难看,搞不好就要超过屏幕的右边了。

之前说LINQ的版本看起来简洁,但未必很直观,是因为:多个from子句连在一起对应的函数是SelectMany,对IEnumerable<T>/IQueryable<T>而言这个LINQ运算符背后的概念则是list monad中的bind函数;其中的机理还是需要读些资料才能理解的。一看到monad这词很多人就要开始晕了吧? =w=

本来是想list monad在C#通过LINQ来使用可以这么简洁,那要是能在Ruby里也这样用就好了。但如标题所说,语言没有提供monad语法的时候,这玩儿写起来也不优雅。其实更关键的问题或许是没有typeclass的条件下monad无法提供统一的接口来提高抽象层次?不过我只想关心语法,也就是“看起来如何”的问题。

例如说,用Ruby来实现一个maybe monad:
class Maybe
  def initialize(val, has_val = true)
    @value, @has_value = val, has_val
  end
  
  def value
    raise 'Maybe::None has no value' unless @has_value
    @value
  end
  
  class << self
    def unit(val)
      new(val).freeze
    end
  end
  
  def bind
    @has_value ? (yield @value) : Maybe::None
  end
  
  def none?
    self == Maybe::None
  end
  
  def clone
    raise TypeError, "can't clone instance of Maybe"
  end
  
  def dup
    raise TypeError, "can't dup instance of Maybe"
  end
  
  None = Maybe.new(nil, false).freeze
  
  private_class_method :new, :allocate
end

定义了Maybe类之后,可以这样用:
res1 = Maybe.unit(1).
             bind { |x| Maybe.unit(x + x) }.
             bind { |x| Maybe.unit(x * x) }
#=> #<Maybe:0x34bc3e8 @has_value=true, @value=4>

看起来还不错,方法调用没有嵌套,都是串在一起的。
但这只不过是因为这一连串计算是从单一的来源得到数据的:那个“1”。假如要把一个“1”和一个“2”都用Maybe包装起来,然后要得到它们的和,那就变成:
one, two = [1, 2].map { |i| Maybe.unit i }
res2 = one.bind { |x|
         two.bind { |y|
           Maybe.unit(x + y)
         }
       }
#=> #<Maybe:0x35ece5c @has_value=true, @value=3>

于是bind的调用就嵌套起来了,郁闷 T T

我一时觉得纳闷,明明记得在Haskell里写的时候即使不用do也没这么麻烦的啊,像这样:
Just 1 >>= \x -> Just 2 >>= \y -> return (x + y)

然后才想起来其实这个表达式就是嵌套的 OTL
(Just 1) >>= (\x -> ((Just 2) >>= (\y -> (return (x + y)))))

还是没有括号看起来顺眼些……但是由于优先级不同,Haskell里能省略的这些括号在Ruby里就省略不了。只好乖乖的写吧。

嘛,无论如何,有了Maybe类的定义后,我们就得到了传播Maybe::None的能力。像是把前面的1+2换成1+None,得到的就会是None:
res3 = one.bind { |x|
         Maybe::None.bind { |y|
           Maybe.unit(x + y)
         }
       }
#=> #<Maybe:0x316384 @has_value=false, @value=nil>
res3.none?
#=> true

或者写在一行上?
res3 = one.bind { |x| Maybe::None.bind { |y| Maybe.unit(x + y) } }

用Haskell写的话,不用do记法是
Just 1 >>= \x -> Nothing >>= \y -> return (x + y)

用do记法的话
do x <- Just 1
   y <- Nothing
   return (x + y)


嗯,看过maybe monad,那么list monad呢?
Ruby里的Enumerable模块其实就可以看成一个抽象的表,里面的select、inject、map/collect等方法都有函数式编程的对应物:filter、fold_left、map等。那干脆就把list monad的bind函数写在Enumerable里好了,像这样:
module Enumerable
  def bind
    self.inject([]) { |acc, e| acc + (yield e) }
  end
end

然后,例如说要求[1, 2]的每个元素分别与[3, 4, 5]的每个元素的乘积的列表:
[1, 2].bind { |x|
  [3, 4, 5].bind { |y|
    [x * y]
  }
}
#=> [3, 4, 5, 6, 8, 10]

呜,没想像中的好看 T T
即使不用list monad,直接用each来写看起来也差不多:
res = []
[1, 2].each { |x|
  [3, 4, 5].each { |y|
    res << (x * y)
  }
}
#=> res == [3, 4, 5, 6, 8, 10]


而在有专门的语法支持的语言里,这逻辑写起来就很优雅:
do x <- [1, 2]
   y <- [3, 4, 5]
   return (x * y)
-- [3,4,5,6,8,10]

from x in new [] { 1, 2 }
from y in new [] { 3, 4, 5 }
select x * y
//=> { 3, 4, 5, 6, 8, 10 }

在Haskell里即使不用do记法也不难看:
[1, 2] >>= \x -> [3, 4, 5] >>= \y -> return (x * y)


回到开头C#的那个例子,用上面Ruby里的list monad来写的话,
boolVals.bind { |a1|
  boolVals.bind { |a2|
    boolVals.bind { |a3|
      boolVals.bind { |b1|
        boolVals.bind { |b2|
          boolVals.bind { |b3|
            [(!((a1 && b1) || (a2 && b2) || (a3 && b3))) ==
            (!(a1 && b1) && !(a2 && b2) && !(a3 && b3))]
          }
        }
      }
    }
  }
}.all? { |b| b }
#=> true

感觉微妙……

有没有什么办法能让这monad在Ruby里更好看些?还是说我要求的过多了?
即使像LINQ的SelectMany那样把Bind写成多接受一个参数的版本,调用也还是会嵌套啊(虽然lambda表达式不必嵌套了)。我就是想少写点括号少写些缩进而已……
周末再读读《The Ruby Programming Language》来找找灵感好了。

对了,记个链接:MenTaLguY: Monads in Ruby
还没来得及读……
2
1
分享到:
评论
7 楼 RednaxelaFX 2009-03-31  
ychael 写道
传入一个闭包嫌太少

很好奇你所提到的yield1、yield2是什么语境下的?因为没见过,所以想多了解一些~

至于在Ruby里传入多于一个闭包,其实不是大问题吧?原本用yield的地方也可以用显式的block参数+.call来调用,要多个闭包的话就多收几个参数好了。像是Ruby里的Y组合子,可以简单实现为:
irb(main):001:0> Y = lambda { |y| lambda { |f| lambda { |x| f[y[y][f]][x] } } }
=> #<Proc:0x02e9a154@(irb):1>
irb(main):002:0> Fix = Y[Y]
=> #<Proc:0x02e9a190@(irb):1>
irb(main):003:0> F = lambda { |fac| lambda { |x| 0 == x ? 1 : x * fac[x-1] } }
=> #<Proc:0x02e915a4@(irb):3>
irb(main):004:0> factorial = Fix[F]
=> #<Proc:0x02e9a1cc@(irb):1>
irb(main):005:0> factorial[4]
=> 24

这段代码里一个&都没用到(当然我们知道lambda这个函数里是有&的……不过不管了),也一个yield都没用到,直接收了参数拿来调用,仅此而已。好吧这段代码里的lambda都是收一个参数的,不过多收几个也没啥差别?

至于说我这帖原本想写的东西,应该跟传入多个闭包没关系,而是跟嵌套定义的闭包有关系……但我傻了,用monad也不会解决闭包的嵌套的,除非弄个monadic syntax来掩盖这实现细节,像Haskell的do记法。
6 楼 ychael 2009-03-30  
传入一个闭包嫌太少
5 楼 RednaxelaFX 2009-03-30  
ychael 写道
不太理解,是不是yield1,yield2这种?

Umm...我没理解,请问yield1、yield2指的是什么?
4 楼 ychael 2009-03-30  
不太理解,是不是yield1,yield2这种?
3 楼 night_stalker 2009-03-13  
幂集一般都可以用二进制数来对应,其他语言里也一样
写在Fixnum里只是为了方便一点而已
2 楼 RednaxelaFX 2009-03-13  
太强了!拜一下~~~
Fixnum可以这样用……太强了 XDD

night_stalker 写道
Ruby是不能省掉块结束符号地!真想弄掉只能自己加工下再eval……

不过我的原意是想尽量不用块结构,或者即便用也尽量不嵌套而是串连在一起。实际把代码写出来才发觉自己错了,原本想的办法的lambda表达式就是嵌套的。如果像LINQ的SelectMany那样多给一个参数或许能不嵌套,回头得试试。
1 楼 night_stalker 2009-03-13  
我(逃避抽象的实用主义者)觉得:怎样写比较短,在于如何穷举a1 a2 a3 b1 b2 b3

class Fixnum
  %w[a1 a2 a3 b1 b2 b3].each_with_index do |name, idx|
    define_method name, do
      self & (1<<idx) == 0 ? false : true
    end
  end
end
0b1000000.times do |n|
  n.instance_eval %q[
    if !((a1&&b1) || (a2&&b2) || (a3&&b3)) != !(a1&&b1) && !(a2&&b2) && !(a3&&b3)
      puts 'blah'
    end
  ]
end


Ruby是不能省掉块结束符号地!真想弄掉只能自己加工下再eval……

相关推荐

    函数式变成Monad简介

    Monad入门,简介。英文版。介绍什么是Monad,以及如何使用Monad,还有如果自己定义Monad。

    ppx_monad:OCaml 的 Monad 语法扩展

    要使用此语法,您需要使用[%monad ...]扩展名包装一个序列表达式(即e1; e2 )。 [ % monad x &lt; - [ 1 ; 2 ; 3 ]; y &lt; - [ 3 ; 4 ; 5 ]; return (x + y) ] v &lt;- e结合的一个monadic值e给一个变量v 。 ...

    consequence:语法清晰一致的简单 monad 实现

    具有清晰一致语法的简单 monad 实现。 用法 一个 monad 有一个值,它被包裹起来。 它的值可以是任何东西:String、Module、Proc 等等……它把它的值作为唯一的参数,并且可以使用元素引用语法进行初始化: ...

    Android-Monad,像scala那样的android上的monad.zip

    Monad是一种抽象的编程构造,源自纯函数式编程语言,如Haskell和Scala,它允许开发者在处理计算流程时保持代码的简洁性和可读性。在Android-Monad项目中,开发者可以体验到Scala风格的编程语法,使得Android应用的...

    关于 Swift Monad .zip

    Swift Monad 是一种编程概念,源于函数式编程语言,如 Haskell 和 Scala,被引入到苹果的 Swift 语言中,用于处理可变性和副作用。Monad 是一个抽象的概念,它提供了一种结构化的方式来组合操作,特别是在处理可能...

    monadless:Scala中用于monad组成的语法糖

    Monadless库与Cats库可以很好地协同工作,为开发者提供更丰富的Monad支持。 **ScalaJS和Monix的结合** ScalaJS是一个让Scala代码编译成JavaScript的工具,允许开发者在浏览器或Node.js环境中运行Scala程序。Monix...

    From Simple IO to Monad Transformers (2014)

    通过理解单子,我们可以更好地理解和使用Haskell的IO系统,从而与现实世界进行交互。 文档首先对比了纯函数编程和命令式编程的特点,阐述了纯函数如何处理现实世界中的问题。接着,文章介绍了单子和其关键操作符(&gt;&gt;...

    haskell_语法详细参考 你用你知道

    在这个详细参考中,我们将深入探讨 Haskell 的核心语法和特性,帮助你更好地理解和运用这一强大的编程工具。 1. **基本语法** - **变量与常量**:在 Haskell 中,变量名是区分大小写的,且默认是常量(不可变),...

    Monad 资源

    Monad 是微软在早期提出的一种编程模型,主要用于解决 .NET Framework 中异步操作和资源管理的问题。Monad 是一种函数式编程的概念,它提供了一种结构化的方式来处理计算过程,特别是那些涉及副作用的操作,如输入/...

    salmon:普通Lisp的Monad

    理解力Salmon使​​用fmap和flatmap的实现来提供do语法,该语法类似于Haskell中的do理解和Scala中的理解。 在Haskell中,monad理解如下: do a &lt;- [ 1 , 2 , 3 ] let c = 5 b &lt;- [ 4 , 5 ] return (a + b + c)...

    racket-monad:球拍的单子(!)

    Racket 中的 Monad,一种动态类型语言这个库展示了一种以动态类型语言处理 monad 的方法,包括一种实现返回类型多态的方法。 它开始是一个实验,记录在,查看动机和与其他方法的比较等。 代码使用的功能代替 Haskell...

    monhacko:用于 Hack 编程语言的 Monad

    如果你对Monad感兴趣,或者正在使用Hack编程,深入研究这个项目将帮助你提升对函数式编程和Monad的理解,从而更好地应对复杂的编程挑战。 总的来说,"monhacko"项目是探索Hack编程语言中Monad概念的一个实验性尝试...

    理解MONAD.pdf

    在给定的文件内容中,我们主要探讨了编程中的MONAD概念,并涉及了编程语言Swift中的相关用法。MONAD是一种用于处理组合异步操作、集合、函数等计算的通用设计模式,它能够让我们把计算表达为一系列操作步骤,而不...

    From Simple IO to Monad Transfo - J Adrian Zimmer.pdf

    本章节探讨了如何利用Monad来模拟传统函数组合的方式,这有助于更好地理解和编写Haskell中的复杂逻辑。 ##### 示例: - **问题提出**(Va Question):提出了一些关于如何使用Monad进行函数组合的问题。 - **答案**...

    movie-monad:由Haskell制作的免费且易于使用的视频播放器

    首先,movie-monad 是使用Haskell编程语言编写的。Haskell是一种纯函数式编程语言,以其类型系统和静态类型著称,这使得代码更加可靠和可维护。使用Haskell开发视频播放器意味着movie-monad具有高度的模块化和可组合...

    Freasy-Monad:使用具有一流Intellij支持的Scala宏轻松创建Free Monad的简便方法

    这使得开发者在使用Free Monad时可以获得更好的开发体验和工具支持。 **描述**提到的“一流Intellij支持”意味着这个库不仅提供了Free Monad的实现,还优化了与IntelliJ IDEA集成,使得IDE能够理解Free Monad的结构...

    monad.js:nodejs 的简单 monad 类型

    monad.js 为 NodeJS(或任何 CommonJS 实现)提供了简单的 monadic 数据类型。 也许 也许代表一个可能存在也可能不存在的值。 当一个值或函数的结果可能会或可能不会产生有意义的东西时,这是很自然的。 传统上, ...

    monad-ts:Monad-ts是一个小型库,实现了一些关键的monad以及将其链接到JavaScript和TypeScript中的流(管道)中的方法

    Monad-ts是一个小型库,实现了一些关键的monad以及将它们链接到JavaScript和Typescript的流(管道)中的方法。 兼容Angular 2+ 。 。 内容 介绍 所有单子 也许 列表 状态 附加工具(类和功能) 异步流 流 投 克隆...

    nimonad:Nim的Monad库

    Nimonad 是一个专门为 Nim 语言设计的 Monad 库,旨在为这个高效、简洁的系统编程语言引入函数式编程的概念。Monad 是一种抽象计算模型,它在函数式编程中扮演着重要的角色,尤其是在处理可变状态和控制流时。Nim ...

Global site tag (gtag.js) - Google Analytics