【问题标题】:Haskell and General TypesHaskell 和一般类型
【发布时间】:2017-08-13 04:52:16
【问题描述】:

我是 Haskell 的新手,我想了解一般类型的工作原理。

获取表达式类型的“系统”思维方式应该是什么?

举个例子,如果我们有:

(\x y z -> x (y z))

我的想法是使用直觉,但它并不总是有效。

在这种情况下,我会说:

   (y z) :: (t -> t1) --function y takes t and return t1
 x (y z) :: (t1 -> t2) --function x takes argument of type (return type of y)

(\x y z -> x (y z)) :: (t1 -> t2) -> (t -> t1) -> t -> t2 --return type is t2 (of x) with argument of type t for function y

我很确定这应该是正确的,但有时它更难,而且这种思维方式似乎行不通。

例如,如果我们有:

1.  (\x -> x (<))  or
2.  (.) . (.)

在这种情况下,我不知道如何找到类型,首先我猜 (Bool,但我在编写洞表达式时遇到了麻烦。

所以问题是,使用这种练习的最佳方法是什么?


添加:我知道如何使用 ghci 检查类型,问题是如何在没有它的情况下实际找到它们(了解 Haskell 的工作原理)。

【问题讨论】:

    标签: function haskell generics lambda types


    【解决方案1】:

    使用GHC编译器,可以通过typehole来询问type,_

    例如:

    thing :: _
    thing = (\x -> (<))
    

    产量:

    Found hole ‘_’ with type: t -> a0 -> a0 -> Bool
    Where: ‘t’ is a rigid type variable bound by
               the inferred type of thing :: t -> a0 -> a0 -> Bool
               at src/Expat/Data/E.hs:555:1
           ‘a0’ is an ambiguous type variable
    To use the inferred type, enable PartialTypeSignatures
    In the type signature for ‘thing’: _
    

    我有时将它与 where 子句一起使用来隔离较大函数中的表达式,例如

    myFun :: a -> b
    myFun a = otherFun thing
      where 
        thing :: _
        thing = (\x -> (<))
    

    您也可以使用孔来代替术语。例如,如果我不确定 otherFun 到底应该是什么,我可以直接替换为 _

    【讨论】:

      【解决方案2】:

      您可以在ghci 中使用:t 轻松检查表达式的类型:

      Prelude> :t (\x y z -> x (y z))
      (\x y z -> x (y z)) :: (r1 -> r) -> (r2 -> r1) -> r2 -> r
      Prelude> :t (\x -> x (<))
      (\x -> x (<)) :: Ord a => ((a -> a -> Bool) -> r) -> r
      Prelude> :t (.) . (.)
      (.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
      

      但我假设您想自己派生类型。

      1. 首先,我们可以从查看变量开始。这些是xyz。我们首先为它们分配“通用类型”,所以:

        x :: a
        y :: b
        z :: c
        

        现在我们看一下表达式的右手边,看到(y z)。这意味着我们以z 作为参数“调用”y。因此,我们将y 的类型“特化”为y :: c -&gt; d。那么(y z) 的类型就是(y z) :: d。现在我们看到x (y z)。因此,我们再次将x 的类型“特化”为x :: d -&gt; e。结果我们得到:

        x :: d -> e
        y :: c -> d
        z :: c
        

        最后,lambda 表达式映射 \x y z -&gt; x (y z)。所以这意味着我们在“伪代码”中寻找结果类型:type(x) -&gt; type(y) -&gt; type(z) -&gt; type('x (y z)')。或者:

        \x y z -> x (y z) :: (d -> e) -> (c -> d) -> c -> e
        
      2. 对于第二个表达式,我们首先要导出(&lt;)的类型:

        Prelude> :t (<)
        (<) :: Ord a => a -> a -> Bool
        

        这是因为(&lt;)Prelude 中以该类型定义。

        现在我们知道了,我们可以看看类型签名。我们将首先假设x :: b 的类型。现在我们看右边,看到我们调用x (&lt;)。这意味着我们知道x 具有Ord a =&gt; (a -&gt; a -&gt; Bool) -&gt; c 类型,并且我们知道x (&lt;) :: c。然后我们又得到了一个 lambda 表达式,正如我们在上面看到的,我们可以像这样解决它:

        (\x -> x (<)) :: Ord a => ((a -> a -> Bool) -> c) -> c
        
      3. 最后对于第三个表达式,我们先重写为:

        (.) (.) (.)
        

        这里的first(.)函数,原来是.(不带括号),但我们会先去掉操作符的语法糖。

        接下来我们会给操作符起一个名字(只是为了方便我们给它们命名)。这在 Haskell 中是不允许的,但我们现在将忽略它。我们这样做的原因是为了稍后引用特定(.)函数的类型:

        (.<i>1</i>) (.<i>2</i>) (.<i>3</i>)

        接下来我们查找(.)的类型:

        Prelude> :t (.)
        (.) :: (b -> c) -> (a -> b) -> a -> c
        

        所以我们首先分配一些类型:

        (.1) :: (b -> c) -> (a -> b) -> a -> c
        (.2) :: (e -> f) -> (d -> e) -> d -> f
        (.3) :: (h -> i) -> (g -> h) -> g -> i
        

        现在我们需要进一步分析这些类型。我们用(.2) 作为参数调用(.1),所以我们知道(b -&gt; c)(.1) 的第一个参数等价于(e -&gt; f) -&gt; (d -&gt; e) -&gt; d -&gt; f。所以这意味着:

        (b -> c) ~ (e -> f) -> (d -> e) -> d -> f
        

        由于类型箭头是右关联(e -&gt; f) -&gt; (d -&gt; e) -&gt; d -&gt; f 实际上意味着(e -&gt; f) -&gt; ((d -&gt; e) -&gt; (d -&gt; f))。所以现在我们可以做一个分析:

          b        -> c
        ~ (e -> f) -> ((d -> e) -> (d -> f))
        

        这意味着b ~ (e -&gt; f)c ~ ((d -&gt; e) -&gt; (d -&gt; f))。所以我们将我们的第一个(.1) :: (b -&gt; c) -&gt; (a -&gt; b) -&gt; a -&gt; c“专门化”到

        (.1) :: ((e -> f) -> ((d -> e) -> (d -> f))) -> (a -> (e -> f)) -> a -> ((d -> e) -> (d -> f))
        

        现在(.1) (.2)的类型是

        (.1) (.2) :: (a -> (e -> f)) -> a -> ((d -> e) -> (d -> f))
        

        但是现在我们用(.3) 调用该函数,所以我们必须派生((.1) (.2)) (.3) 的类型。因此,我们必须再次进行类型解析:

          a        -> (e        -> f)
        ~ (h -> i) -> ((g -> h) -> (g -> i))
        

        这意味着a ~ (h -&gt; i)e ~ (g -&gt; h)f ~ (g -&gt; i)。所以现在我们已经将类型解析为:

        ((.1) (.2)) (.3) :: a -> c
        =                   (h -> i) -> ((d -> (g -> h)) -> (d -> (g -> i)))
        =                   (h -> i) -> (d -> g -> h) -> d -> g -> i
        

        最后一行只是语法上的简化。如您所见,这映射到我们从ghci 派生的类型。

      正如您所见,对于所有三个查询,我们都获得了相同的类型(当然类型变量的名称不同)。

      【讨论】:

      • 非常有趣的方法,你会如何使用(\x -&gt; x (&lt;))
      • 谢谢,我开始明白了。你能告诉我你将如何使用(.).(.),因为在这种情况下我们没有任何 lambda 表达式..
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-30
      • 1970-01-01
      • 1970-01-01
      • 2022-08-12
      • 1970-01-01
      • 2016-01-01
      • 1970-01-01
      相关资源
      最近更新 更多