论坛首页 编程语言技术论坛

用 haskell 扩展 ruby

浏览 6361 次
精华帖 (6) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-04-10   最后修改:2009-09-09
Haskell 是“纯洁的”函数式语言,可以解释运行,也可以编译成本地代码。
其编译过程大致是先生成 C 代码(确切的说是 C--),然后使用自带的 gcc 编译。
Haskell 编译后体积小,性能好,不需要笨重的运行时,在
http://shootout.alioth.debian.org/
的 benchmark 排名经常超过 java 和 D。

与它相似的函数式语言的简单比较如下:


注意:Erlang, Scheme 都是不纯洁的哦。

pure 和 lazy(惰性求值)密切相关。
由于纯函数(就是我们中学课本上的函数,而不是一般编程语言说的“函数”)表现稳定,所以求值后可以存起结果。如果下次调用参数相同,可以直接返回这个结果(似乎编译器的优化要更复杂一些?)。于是纯函数式的语言用惰性求值不会产生任何性能问题。

惰性求值有个好处是可以随心所欲定义无限长的列表,你用到其中某一项时它才会算出那项的值。
eager 的就不行——定义时就会对这个 list 求值,无限循环了。

---------------------------------------------------------------------------------

Haskell vs Ruby:
彻底的函数式 vs 彻底的面向对象,静态类型 vs 动态类型,极度纯洁 vs 极度不纯洁,面向数学公式 vs 面向自然语言,缩进 vs end ……

一方的弱项正好是另一方的长处,所以用 Haskell 扩展 Ruby 还是很有意义的。
当然两者也有一些共同的缺点,譬如:代码量都比较短。

首先请看一个简单的用 Haskell 产生dll的示例:
http://www.haskell.org/ghc/docs/latest/html/users_guide/win32-dlls.html

不过 dllMain.c 和各种编译参数太古板了,可以写一个脚本省掉这个过程:

makedll.rb 将当前目录下所有.hs文件都检查一遍,生成dllMain.c,然后自动编译链接产生dll。并且连带产生 interface.rb。
### Begin Config

### Set output dll name
#outputdll = 'some name.dll'

### Set ghc options
#options = '-O2 -threaded'

### Set heap and stack options
#heap_and_stack = '-H128m -K1m'

### End   Config

if ARGV[0] == 'clean'
  system 'del *.o *.hi *.c *.a *.h'
  exit
end


# tidy config options
outputdll ||= "#{File.basename File.expand_path('.')}.dll"
options   ||= ''
heap_and_stack &&= "char *ghc_rts_opts = \"#{heap_and_stack}\";"


# hash for haskell-to-C type cast. needs to be complete in the future
TypeCast = { 'Int' => 'long',
  'Char' => 'char',
  'String' => 'char*'
}

# cast signature from haskell to ruby/dl style
def signature_cast line
  if line =~ /^\s*foreign\s+export\s+stdcall\s+(\w+)\s+\:\:\s+(.+?)\s+\-\>\s+IO\s+(\w+)\s*$/
	func = $1.dup
	ret_type = TypeCast[$3.strip]
	plist = $2.split('->').map{|e| TypeCast[e.strip] }.join(',')
	"extern \"#{ret_type} #{func}(#{plist})\", :stdcall"
  end
end


# scan files
fnames = Dir.entries('.').select do |f|
  !(File.directory? f) && f =~ /\.hs$/
end
modules = []
fnames_with_ext = [] # files containing extern functions
rb_interface = []	 # ruby interface modules
fnames.each do |fn|
  File.open fn do |f|
	# search for module xxx
	module_name = nil
	while line = f.gets
	  if line =~ /^\s*module (\w+)/
		module_name = $1
		break
	  end
	end
	# search for extern function signatures
	externs = []
	while line = f.gets
        sig = signature_cast line
	  externs << sig if sig
	end
	if externs != []
	  modules << module_name
	  fnames_with_ext << fn
	  rb_interface << externs.join("\n")
	end
  end
end


# build ruby interface
File.open 'interface.rb', 'w' do |f|
  ruby_template = <<-ES
require 'dl/import'
require 'dl/types'
module %s
  extend DL::Importer
  dlload '%s'
  %s
end
  ES
  outputdll =~ /^([a-zA-Z]+)/
  module_name = $1
  module_name[0] = module_name[0].chr.upcase
  f.puts ruby_template % [module_name, outputdll, rb_interface.join("\n  ")]
end


# build dllMain.c
require 'erb'
File.open 'dllMain.c', 'w' do |f|
  f.puts ERB.new(DATA.read).result(binding)
end


# compile
fnames_with_ext.each do |fn|
  system "ghc -c \"#{fn}\" -fglasgow-exts"
end
system "ghc -c dllMain.c"


# link
objs = fnames_with_ext.map do |fn|
  "\"#{fn.sub /hs$/,'o'}\" \"#{fn.sub /\.hs$/,'_stub.o'}\""
end.join ' '
system "ghc -shared dllMain.o #{objs} -o \"#{outputdll}\" #{options}"


__END__
#include <windows.h>
#include <Rts.h>

<%= heap_and_stack %>

<% modules.each do |m| %>
extern void __stginit_<%= m %>(void);
<% end %>

static char* args[] = { "ghcDll", NULL };
BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, void* reserved)
{
  if (reason == DLL_PROCESS_ATTACH) {
    <% modules.each do |m| %>
    startupHaskell(1, args, __stginit_<%= m %>);
    <% end %>
    return TRUE;
  }
  return TRUE;
}



------------------------------------------------------------------------------

简单过程示例:

保证 ghc 6.10.2 (最流行的haskell编译器和解释器) 和 ruby 1.9 (内置DL库)。

建一个文件夹adder,在里面新建 adder.hs,内容如下
module Adder where

adder :: Int -> Int -> IO Int  –– gratuitous use of IO
adder x y = return (x+y)

foreign export stdcall adder :: Int -> Int -> IO Int


把上面的 makedll.rb 扔进去,产生 adder.dll 和 interface.rb:
ruby makedll.rb
ruby makedll.rb clean


用 ruby/DL 调用这个 dll 非常简单:
require 'interface.rb'
puts "5+8=#{Adder.adder(5,8)}"


ruby/DL 调用扩展的好处是可以避免 "dll hell",我的ruby是VC2008编译的,调用gcc产生的动态链接库也不会出现 segfault。

补充1:推荐使用 ghc 6.10.2 , ghc 6.10.1 可能有问题。
补充2:在linux下编译 haskell 共享库要简单一些,不需要dllMain.c。具体可以参看
http://blog.haskell.cz/pivnik/building-a-shared-library-in-haskell/
补充3:修改 makedll.rb,可以产生 ruby interface 了。
补充4:2009.9 看到 hubris: (只能在 linux 和 mac 下面用,类似于 rubyinline)
http://www.infoq.com/news/2009/08/haskell-ruby-hubris
http://github.com/mwotton/Hubris/tree/master
  • 大小: 12.6 KB
   发表时间:2009-04-10   最后修改:2009-04-10
并非给ruby吧,只是通过dll import,任何一种语言皆可调用之。dll麻烦是你要了解函数签名,否则就没戏了。

作为面向计算的语言,我倒是有兴趣了解一下haskell和fortran之间的效能关系,尤其是大规模稀疏矩阵和傅里叶变换时候。。。

0 请登录后投票
   发表时间:2009-04-10   最后修改:2009-04-11
似乎 haskell 不太适合矩阵计算,Immutable Array 的 Update 自然比 Mutable Array 慢,不过有些研究说可以将速度提升到很牛叉的水平。

支持者们总会争辩说性能主要问题在于你的算法复杂度而不是语言……
然后给一个长长的论文链接说:reading "An approach to fast arrays in Haskell"
might be a good idea for everyone wondering about ghc performance

不过我想比较简单的解决方法是调用 fortran 库…… 据我的经验,矩阵运算没有很好的解决方法,譬如 C++ STL 的vararray 号称很快,其实比自己仔细用 C 实现的矩阵慢多了。

哪个语言比较适合 HPC 的讨论:
http://lambda-the-ultimate.org/node/2720

另外,其实对公式的表达能力强不代表面向计算……

考据党(ghc 的老大)对 haskell 的历史的研究(内含全家福一张):
http://research.microsoft.com/en-us/um/people/simonpj/papers/history-of-haskell/history.pdf
其中还有比较搞笑的一段:
引用
the program was far more robust than the C program, which often crashed and killed the patient


补充:
当然“慢”和“不适合”是相对的,数学家们就很喜欢 haskell。比 scala 快是肯定的,比起 ruby 更是百倍速度。
我觉得 haskell 扩展比 C 扩展容易写(不用考虑内存分配和很多边界条件),性能也很不错。如果用基于 VM 的语言,得通过两层接口,还不如 socket 调用。
并发方面, haskell 大概比 erlang 弱(http://thinkerlang.com/2006/01/01/haskell-vs-erlang-reloaded.html):
引用
Concurrency in Haskell deserves a praise, specially when used together with STM. Threads are lightweight (1024 bytes on the heap) and easy to launch and STM is a beautiful thing. Nothing beats being able to just send yourself a message, though. This is something that you can easily do with Erlang.

Erlang processes (327 bytes starting up, including heap) come with a message queue and you retrieve messages with “selective receive” that uses the same pattern-matching facilities as everything else.
0 请登录后投票
   发表时间:2009-06-11  
night_stalker,haskell是否可以写成so文件给ruby调用。
0 请登录后投票
   发表时间:2009-06-11   最后修改:2009-06-11
CharlesCui 写道
night_stalker,haskell是否可以写成so文件给ruby调用。


理论上可行 …… 但还没试过。

前提是:你的 ruby 是 gcc 编译的。

需要先把 ruby.h 等头文件转一遍,可能这个工具有用:
http://freshmeat.net/projects/c2hs

但是宏转不了。得自己对着理一遍 ……

有了接口,然后就和写 ruby 的 C-extension 差不多:
定义入口函数、入口函数里定义一些模块或者类、将 haskell 函数映射到 ruby 函数。

最后编译链接参数,要加上 ruby 的 lib。


总之…… 很麻烦。比用 D 语言写 ruby extension 麻烦。所以我觉得最适当的方法是做成通用 dll。



另外,可以用 Haskell 做 fcgi server,配合 rails ,参见上面某捷克文博客……
0 请登录后投票
   发表时间:2009-06-11  
我还是觉得 ocaml 和 f#的 syntax更对些胃口。

只是 ocaml利用multi-core不如 haskell来得方便
0 请登录后投票
   发表时间:2009-06-11  
其实C#调用hashell的dll更方便更简单~~ 连ruby的dll load都不需要。直接pinvoke...
0 请登录后投票
   发表时间:2009-06-12   最后修改:2009-06-12
ray_linn 写道
其实C#调用hashell的dll更方便更简单~~ 连ruby的dll load都不需要。直接pinvoke...


(+﹏+)~,不够方便是个问题 …… 再写个小工具生成接口就好了。


Hia hia:

  • 大小: 14.8 KB
0 请登录后投票
   发表时间:2009-06-12  
night_stalker 写道
ray_linn 写道
其实C#调用hashell的dll更方便更简单~~ 连ruby的dll load都不需要。直接pinvoke...


(+﹏+)~,不够方便是个问题 …… 再写个小工具生成接口就好了。



恩,我想到的是,用C#同时可以调用ghc和fortran两个的dll,同时用C#的GDI+应该可以做很多事情。


ghc负责函数,
fortran 负责 矩阵与傅里叶
C# 负责绘图
0 请登录后投票
   发表时间:2009-06-12  
哇咔咔,修改后的版本自动产生 interface.rb, 还是 Ruby 方便吧?
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics