Haskell全新,无法在简单程序中打印日期。我想做什么:
我了解到getCurrentTime返回一个IO monad。我必须使用像fmap这样的额外酱汁将我的纯函数提升到monad中。仍然没有运气。
我做错了什么?
---编辑---
忘记提到这个编译并运行但不产生输出。
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
答案 0 :(得分:2)
它的工作原理如下:
fmap :: (a -> b) -> f a -> f b
,注意到fmap
的功能是正常功能。fmap getDateStr date :: IO String
。print :: a -> IO ()
,a
将为String
。所以:fmap print (fmap getDateStr date)
的类型为IO (IO ())
。要点是print
不 a"正常"函数,但它是 monadic 函数。你fmap
monadic函数的monadic值,你将得到一个monadic值包含在另一个monadic值。
然后,当您评估main
时,您将获得类型IO ()
的内部monadic值。这不是你想要的。要获得所需的结果,只需{@ 1}} bind
至print
@Ryan在评论中建议:
printabledate
这就是全部。
答案 1 :(得分:1)
让我们忘记monad的概念,暂时清除一下:
在OOP中,我们有课程。大多数情况下,这些将某些行为绑定到某些数据。在Haskell中,我们不这样做,而是创建数据类型,就是数据。
此外,在OOP中,我们有接口的概念,它允许我们为某些类可以共享的一些常用函数定义API。在某种程度上,我们可以按照它们共享的属性对这些类进行分组,例如创建一个接口Mappable
,它具有一个map
方法,该方法将函数应用于实现该接口的类的内容。 / p>
现在,我们可以创建一个实现List<T>
的类Mappable
,其中map
将函数应用于列表的每个元素。
在Haskell中,我们有类型类,它们类似于接口,但更好,因为它们允许您为任何现有类型实现API。例如,之前的Mappable
在Haskell中称为Functor
。不要害怕,因为它只是AbstractEnterpriseJavaBeanFactory
之类的名字。它的定义如下:
class Functor f where
fmap :: (a -> b) -> f a -> f b
...
现在我们可以扩展任何具有某些内容的内容,比如之前的List<T>
,以及它在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
函数和f a
作为参数f (a -> b)
内的函数和f a
在所有这些类型中,有一个具有以下API
class X f where
bind :: (a -> f b) -> f a -> f b
它就像之前的仿函数一样,但是作为参数传递的函数不是返回一个元素,而是返回另一个&#34;容器&#34;。
这个类型类称为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,只是一个容器类型,恰好实现了类型类Monad
中定义的API,还有Functor
和其他许多(您可以在the IO API documentation)的实例部分查看它们。
现在让我们说理一下:
首先,我们使用date
来获取IO (Integer, Int, Int)
,它基本上是一个包含三元组的容器。
然后,我们使用getDateStr
将fmap
函数应用于其内容,因此我们会返回IO
内有String
的内容。< / p>
现在我们将此值绑定到printableDate
。因此printableDate
现在是IO String
。
现在我们使用print
将printableDate
应用于fmap
的内容,但等待,print
返回&#34;空&#34; IO
容器,现在我们得到的是IO
包含IO ()
(IO (IO ())
),但main
函数的返回类型必须永远是IO ()
。
我们现在做什么?我们有两种选择:
1)使用printableDate
运算符展开<-
内的值,允许我们获取String
本身:
main = do
let printabledate = fmap getDateStr date
unwrappedPrintableDate <- printabledate
print unwrappedPrintabledate
或者,直接:
main = do
printabledate <- fmap getDateStr date
print printabledate
2)使用>>=
类型类中定义的Monad
运算符:
main = do
let printabledate = fmap getDateStr date
printabledate >>= print
记住? >>=
运算符期望传递给它的函数返回另一个&#34;容器&#34;,这是print
所做的。
使用哪一种感觉更自然,因为它们都被接受并且the same thing