如何从纯函数调用不纯函数?

Ste*_*orn 6 haskell functional-programming

我刚读完“学到Haskell来造福伟大!”。书,所以我的问题可能很幼稚。我不明白的是如何从纯代码中调用“不纯”的IO函数。

这是一个用C#编写的工作示例。在我们的业务逻辑中,我们根据天气计划一些行动。我们以通常的C#方式进行操作。

interface IWeatherForecast
{
    WeatherData GetWeather(Location location, DateTime timestamp);
}

// concrete implementation reading weather from DB
class DbWeather : IWeatherForecast
{
    public override WeatherData GetWeather(Location location, DateTime timestamp)
    {...}
}

class WeatherFactory
{
    public IWeatherForecast GetWeatherProvider()
    {...}
}

// Business logic independent from any DB
class MaritimeRoutePlanner
{
    private IWeatherForecast weatherProvider = weatherFactory.GetWeatherProvider();

    public bool ShouldAvoidLocation(Location location, DateTime timestamp)
    {
        WeatherData weather = weatherProvider.GetWeather(location, timestamp);
        if(weather.Beaufort > 8)
            return true;
        else...
            ...
    }
}
Run Code Online (Sandbox Code Playgroud)

如何在Haskell中实现此逻辑?

实际上,“纯逻辑” MaritimeRoutePlanner称之为weatherProvider.GetWeather()“纯IO”。

Haskell有可能吗?您如何在Haskell中建模?

Mar*_*ann 12

常见问题(如何从纯函数调用不纯函数)是一个常见问题解答。参见例如此问题及其答案:如何从不纯方法中返回纯值

与其他任何与软件体系结构相关的主题一样,如何以更具功能性的方式构造代码也取决于环境。您正在编写哪种程序?REST API?智能手机应用程序?控制台程序?批处理工作?外接程序?

在许多情况下,您可以摆脱所谓的“ 不纯净”三明治

  1. 收集来自不纯来源的所有必需数据
  2. 用该数据调用纯函数
  3. 对纯函数的返回值做一些不纯净的事情

在Haskell中,您可以执行此操作,因为入口点始终是不纯的。这是天气决策问题的简单示意图。首先定义要使用的数据。在这里,我仅包含该beaufort值,但我假设WeatherData将包含比该值更多的数据(这就是为什么我将其定义为data而不是a的原因newtype)。

data WeatherData = WeatherData { beaufort :: Int } deriving (Eq, Show)
Run Code Online (Sandbox Code Playgroud)

您现在可以将决策逻辑编写为纯函数:

shouldAvoidLocation :: WeatherData -> Bool
shouldAvoidLocation weather = beaufort weather > 8
Run Code Online (Sandbox Code Playgroud)

加载数据是一个完全具体的操作:

readWeatherFromDb :: Location -> LocalTime -> IO WeatherData
readWeatherFromDb location timestamp = -- implementation goes here...
Run Code Online (Sandbox Code Playgroud)

这里没有明确的抽象。该函数读取数据并返回不纯数据。那可能是不纯净三明治中的第一步(不纯净)。

现在可以根据该体系结构来构造应用程序的入口点:

main :: IO ()
main = do
  w <- readWeatherFromDb Berlin $ LocalTime (fromGregorian 2019 8 29) (TimeOfDay 8 55 8)
  if shouldAvoidLocation w
    then putStrLn "Avoid"
    else putStrLn "Go"
Run Code Online (Sandbox Code Playgroud)

呼叫shouldAvoidLocation是在三明治中间的好东西,然后是不纯净的putStrLn呼叫。


che*_*ner 6

简而言之,您不会从不纯的“函数”(也称为action)中提取数据;您纯功能推向新的动作。

data WeatherData = WeatherData { beaufort :: Int, ... }

-- getWeather is a pure function
-- getWeather someLocation someDate is an action
getWeather :: Location -> DateTime -> IO WeatherData
getWeather l d = ...


-- badWeather is a pure function
badWeather :: WeatherData -> Bool
badWeather wd = beaufort wd > 8

-- avoidLocation is a pure function
-- avoidLocation someLocation someDate is an action
-- We can simply use fmap to lift (or wrap) the pure function badWeather
-- into a new action.
avoidLocation :: Location -> DateTime -> IO Bool
avoidLocation l d = fmap badWeather (getWeather l d)
Run Code Online (Sandbox Code Playgroud)

avoidLocation实际上不会产生布尔值;它创建,最后执行时的动作的用途 badWeather,以产生一个布尔值。