苦苦挣扎于stdio的基本实验打印

时间:2017-08-13 22:10:34

标签: haskell

Haskell全新,无法在简单程序中打印日期。我想做什么:

  1. 从getCurrentTime获取当前时间
  2. 在日期调用纯函数,返回一个字符串
  3. 将字符串打印到stdio。
  4. 我了解到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
    

2 个答案:

答案 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}} bindprint @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附带了许多预定义类型类:FunctorFoldableApplicative,...

类型类仅确保在应用其API中定义的函数时将满足某些约束:

  • Functor fmapa -> b函数和f a作为参数
  • 申请人需要一个&#34;容器&#34;使用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),它基本上是一个包含三元组的容器。

然后,我们使用getDateStrfmap函数应用于其内容,因此我们会返回IO内有String的内容。< / p>

现在我们将此值绑定到printableDate。因此printableDate现在是IO String

现在我们使用printprintableDate应用于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