Haskell 为stdio打印基础实验而挣扎
Haskell是个新手,无法在简单的程序中打印日期。我想做的是:Haskell 为stdio打印基础实验而挣扎,haskell,Haskell,Haskell是个新手,无法在简单的程序中打印日期。我想做的是: 从getCurrentTime获取当前时间 在日期调用纯函数,返回字符串 将字符串打印到stdio 我了解到getCurrentTime返回一个IO单子。我必须用额外的酱汁,比如fmap,将我的纯函数提升到monad中。还是不走运 我做错了什么 ---编辑--- 忘了提到它编译并运行,但不生成输出 module Main where import System.IO import Data.Time.Clock import Da
module Main where
import System.IO
import Data.Time.Clock
import Data.Time.Calendar
date :: IO (Integer,Int,Int)
date = fmap (toGregorian . utctDay) getCurrentTime
getDateStr :: (Integer,Int,Int) -> String
getDateStr (year,month,day) = "Date is " ++ show year ++ "/" ++ show month ++ "/" ++ show day ++ "\n"
main = do
let printabledate = fmap getDateStr date
fmap print printabledate
它的工作原理如下:
class Functor f where
fmap :: (a -> b) -> f a -> f b
...
,注意到fmap::(a->b)->fa->fb
的功能是正常功能fmap
fmap getDateStr date::IO String
,print::a->IO()
将是a
String
fmap打印(fmap getDateStr date)
将具有IO(IO())
类型。关键是print
不是一个“正常”函数,但它是一个一元函数。如果将fmap
一个一元函数转换为一元值,则会将一个一元值包装在另一个一元值中
然后,当您计算main
时,返回类型为IO()
的内部一元值。那不是你想要的。要获得所需的结果,只需将绑定到可打印日期即可,正如@Ryan在评论中建议的那样:
(>>=) :: m a -> (a -> m b) -> m b
printabledate >>= print :: IO ()
仅此而已。让我们暂时忘记单子的概念,让我们稍微澄清一下:
在OOP中,我们有类。大多数情况下,它们会将某些行为绑定到某些数据。在Haskell中,我们不这样做,而是创建数据类型即数据
另外,在OOP中,我们有接口的概念,它允许我们为一些类可以共享的一些常用函数定义API。在某种程度上,我们可以根据类共享的属性对这些类进行分组,例如创建一个接口Mappable
,该接口具有一个map
方法,该方法将函数应用于实现该接口的类的内容
现在,例如,我们可以创建一个类List
,该类实现Mappable
,其中map
将函数应用于列表的每个元素
在Haskell中,我们有类型类,它们类似于接口,但更好,因为它们允许您为任何现有类型实现API。例如,我们以前的Mappable
,在Haskell中被称为Functor
。不要害怕,因为它只是一个类似于AbstractEnterpriseJavaBeanFactory
的名称。定义如下:
class Functor f where
fmap :: (a -> b) -> f a -> f b
...
现在我们可以用它扩展任何有一些内容的东西,比如我们以前的列表,在Haskell中是:
data List a = ... -- List implementation
instance Functor List where
fmap f lst = ... -- Implementation of fmap
这很好,因为Functor
概念保证了函数f
将应用于实现此功能的数据类型的内容,始终返回数据类型的另一个副本
你现在可能会想,这和我的问题有什么关系
Haskell附带了许多预定义的类型类:Functor
,Foldable
,Applicative
类型类仅确保在应用API中定义的函数时满足某些约束:
- 函子的
fmap
将函数a->b
和fa
作为参数
- Applicative接受一个“容器”,其中包含
f(a->b)
和f a
- 等等
在所有这些类型类中,有一个具有以下API
class X f where
bind :: (a -> f b) -> f a -> f b
这与以前的函子类似,但函数不是返回元素,而是作为参数传递,返回另一个“容器”
此类型类称为Monad,在Haskell中定义如下:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a -- This puts the value a inside of the "container" m
...
(请注意,参数是翻转的)
因此,基本上IO不是monad,只是一种容器类型,它恰好实现了type类monad
中定义的API,还实现了Functor
和许多其他类型(您可以在的instances部分检查它们)
现在让我们来解释一下:
首先,我们使用date
获取IO(Integer,Int,Int)
,它基本上是一个包含三元组的容器
然后,我们使用fmap
将getDateStr
函数应用于它的内容,因此我们得到一个IO
,其中包含String
现在我们将该值绑定到printableDate
。因此,printableDate
现在是一个IO字符串
现在我们使用fmap
将print
应用于printableDate
的内容,但是等待,print
返回一个“空”的IO
容器,所以现在我们得到的是一个IO
包含IO()
(IO(IO())
),但是main
函数的返回类型必须始终是IO()
我们现在该怎么办?我们有两个选择:
1)使用=
操作符打开printableDate
中的值,期望传递给它的函数返回另一个“容器”,这就是print
所做的
使用您觉得更自然的选项,因为它们都被接受,并且您应该在问题中包含错误。无论如何,fmap print printable date
将为您提供一个IO(IO())
。您可以使用printabledate>>=print
,但更常见的是使用do
和printabledatemain=print=@Ryan-Oops是的,我更新了问题来描述非结果。你的建议奏效了,尽管我还不明白为什么。想留下答案吗?fmap打印::
main = do
printabledate <- fmap getDateStr date
print printabledate
main = do
let printabledate = fmap getDateStr date
printabledate >>= print