在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):billpmurphy/hask开源软件地址(OpenSource Url):https://github.com/billpmurphy/hask开源编程语言(OpenSource Language):Python 100.0%开源软件介绍(OpenSource Introduction):HaskHask is a pure-Python, zero-dependencies library that mimics most of the core language tools from Haskell, including:
Features not yet implemented, but coming soon:
Note that all of this is still very much pre-alpha, and some things may be buggy! Installation
To run the tests: Why did you make this?I wanted to cram as much of Haskell into Python as possible while still being 100% compatible with the rest of Python, just to see if any useful ideas came out of the result. Also, it was fun! Contributions, forks, and extensions to this experiment are always welcome! Feel free to submit a pull request, open an issue, or email me. In the spirit of this project, abusing the Python language as much as possible is encouraged. FeaturesHask is a grab-bag of features that add up to one big pseudo-Haskell functional programming library. The rest of this README lays out the basics. I recommend playing around in the REPL while going through the examples. You To import all the language features: The List type and list comprehensionsHask provides the To create a new >>> L[1, 2, 3]
L[1, 2, 3]
>>> my_list = ["a", "b", "c"]
>>> L[my_list]
L['a', 'b', 'c']
>>> L[(x**2 for x in range(1, 11))]
L[1 ... ] To add elements to the front of a List, use >>> 1 ^ L[2, 3]
L[1, 2, 3]
>>> "goodnight" ^ ("sweet" ^ ("prince" ^ L[[]]))
L["goodnight", "sweet", "prince"]
>>> "a" ^ L[1.0, 10.3] # type error
>>> L[1, 2] + L[3, 4]
L[1, 2, 3, 4] Lists are always evaluated lazily, and will only evaluate list elements as
needed, so you can use infinite Lists or put never-ending generators inside of
a One way to create infinite lists is via list comprehensions. As in Haskell, there are four basic type of list comprehensions: # list from 1 to infinity, counting by ones
L[1, ...]
# list from 1 to infinity, counting by twos
L[1, 3, ...]
# list from 1 to 20 (inclusive), counting by ones
L[1, ..., 20]
# list from 1 to 20 (inclusive), counting by fours
L[1, 5, ..., 20] List comprehensions can be used on ints, longs, floats, one-character strings,
or any other instance of the Hask provides all of the Haskell functions for List manipulation ( >>> L[1, ...]
L[1 ...]
>>> from hask.Data.List import take
>>> take(5, L["a", "b", ...])
L['a', 'b', 'c', 'd', 'e']
>>> L[1,...][5:10]
L[6, 7, 8, 9, 10]
>>> from hask.Data.List import map
>>> from hask.Data.Char import chr
>>> letters = map(chr, L[97, ...])
>>> letters[:9]
L['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
>>> len(L[1, 3, ...]) # uh oh Otherwise, you can use for i in L[0, ...]:
print i
>>> 55 in L[1, 3, ...]
True Algebraic Data TypesHask allows you to define algebraic datatypes, which are immutable objects with a fixed number of typed, unnamed fields. Here is the definition for the infamous from hask import data, d, deriving
from hask import Read, Show, Eq, Ord
Maybe, Nothing, Just =\
data.Maybe("a") == d.Nothing | d.Just("a") & deriving(Read, Show, Eq, Ord) Let's break this down a bit. The syntax for defining a new type constructor is: data.TypeName("type param", "type param 2" ... "type param n") This defines a new algebraic datatype with type parameters. To define data
constructors for this
type, use FooBar, Foo, Bar =\
data.FooBar("a", "b") == d.Foo("a", "b", str) | d.Bar To automagically derive typeclass instances for the type, add Putting it all together, here are the definitions of Either, Left, Right =\
data.Either("a", "b") == d.Left("a") | d.Right("b") & deriving(Read, Show, Eq)
Ordering, LT, EQ, GT =\
data.Ordering == d.LT | d.EQ | d.GT & deriving(Read, Show, Eq, Ord, Bounded) You can now use the data constructors defined in a >>> Just(10)
Just(10)
>>> Nothing
Nothing
>>> Just(Just(10))
Just(Just(10))
>>> Left(1)
Left(1)
>>> Foo(1, 2, "hello")
Foo(1, 2, 'hello') You can view the type of an object with >>> from hask import _t
>>> _t(1)
int
>>> _t(Just("soylent green"))
(Maybe str)
>>> _t(Right(("a", 1)))
(Either a (str, int))
>>> _t(Just)
(a -> Maybe a)
>>> _t(L[1, 2, 3, 4])
[int] The type system and typed functionsSo what's up with those types? Hask operates its own shadow Hindley-Milner
type system
on top of Python's type system; In Hask, typed functions take the form of
@sig(H/ "a" >> "b" >> "a")
def const(x, y):
return x
def const(x, y):
return x
const = const ** (H/ "a" >> "b" >> "a")
>>> f = (lambda x, y: x + y) ** (H/ int >> int >> int)
>>> f(2, 3)
5
>>> f(9, 1.0) # type error Second, >>> g = (lambda a, b, c: a / (b + c)) ** (H/ int >> int >> int >> int)
>>> g(10, 2, 3)
2
>>> part_g = g(12)
>>> part_g(2, 2)
3
>>> g(20, 1)(4)
4
>>> from hask.Prelude import flip
>>> h = (lambda x, y: x / y) ** (H/ float >> float >> float)
>>> h(3.0) * h(6.0) * flip(h, 2.0) % 36.0
9.0 The compose operation is also typed-checked, which makes it appealing to write programs in pointfree style, i.e, chaining together lots of functions with composition and relying on the type system to catch programming errors. As you would expect, data constructors are also just >>> Just * Just * Just * Just % 77
Just(Just(Just(Just(77)))) The type signature syntax is very simple, and consists of a few basic primitives that can be combined to build any type signature:
Some examples: # add two ints together
@sig(H/ int >> int >> int)
def add(x, y):
return x + y
# reverse order of arguments to a function
@sig(H/ (H/ "a" >> "b" >> "c") >> "b" >> "a" >> "c")
def flip(f, b, a):
return f(a, b)
# map a Python (untyped) function over a Python (untyped) set
@sig(H/ func >> set >> set)
def set_map(fn, lst):
return set((fn(x) for x in lst))
# map a typed function over a List
@sig(H/ (H/ "a" >> "b") >> ["a"] >> ["b"])
def map(f, xs):
return L[(f(x) for x in xs)]
# type signature with an Eq constraint
@sig(H[(Eq, "a")]/ "a" >> ["a"] >> bool)
def not_in(y, xs):
return not any((x == y for x in xs))
# type signature with a type constructor (Maybe) that has type arguments
@sig(H/ int >> int >> t(Maybe, int))
def safe_div(x, y):
return Nothing if y == 0 else Just(x/y)
# type signature for a function that returns nothing
@sig(H/ int >> None)
def launch_missiles(num_missiles):
print "Launching {0} missiles! Bombs away!" % num_missiles It is also possible to create type synonyms using Ratio, R =\
data.Ratio("a") == d.R("a", "a") & deriving(Eq)
Rational = t(Ratio, int)
@sig(H/ Rational >> Rational >> Rational)
def addRational(rat1, rat2):
... Pattern matchingPattern matching is a more powerful control flow tool than the Pattern matching expressions follow this syntax: ~(caseof(value_to_match)
| m(pattern_1) >> return_value_1
| m(pattern_2) >> return_value_2
| m(pattern_3) >> return_value_3) Here is a function that uses pattern matching to compute the fibonacci
sequence. Note that within a pattern match expression, def fib(x):
return ~(caseof(x)
| m(0) >> 1
| m(1) >> 1
| m(m.n) >> fib(p.n - 1) + fib(p.n - 2))
>>> fib(1)
1
>>> fib(6)
13 As the above example shows, you can combine pattern matching and recursive functions without a hitch. You can also deconstruct an iterable using @sig(H[(Num, "a")]/ ["a"] >> t(Maybe, "a"))
def add_first_two(xs):
return ~(caseof(xs)
| m(m.x ^ (m.y ^ m.z)) >> Just(p.x + p.y)
| m(m.x) >> Nothing)
>>> add_first_two(L[1, 2, 3, 4, 5])
Just(3)
>>> add_first_two(L[9.0])
Nothing Pattern matching is also very useful for deconstructing ADTs and assigning their fields to temporary variables. def default_to_zero(x):
return ~(caseof(x)
| m(Just(m.x)) >> p.x
| m(Nothing) >> 0)
>>> default_to_zero(Just(27))
27
>>> default_to_zero(Nothing)
0 If you find pattern matching on ADTs too cumbersome, you can also use numeric
indexing on ADT fields. An >>> Just(20.0)[0]
20.0
>>> Left("words words words words")[0]
'words words words words'
>>> Nothing[0] # IndexError Typeclasses and typeclass instancesTypeclasses allow you to add additional functionality to your ADTs. Hask implements all of the major typeclasses from Haskell (see the Appendix for a full list) and provides syntax for creating new typeclass instances. As an example, let's add a def maybe_fmap(fn, x):
"""Apply a function to the value inside of a (Maybe a) value"""
return ~(caseof(x)
| m(Nothing) >> Nothing
| m(Just(m.x)) >> Just(fn(p.x)))
instance(Functor, Maybe).where(
fmap = maybe_fmap
)
>>> times2 = (lambda x: x * 2) ** (H/ int >> int)
>>> toFloat = float ** (H/ int >> float)
>>> fmap(toFloat, Just(10))
Just(10.0)
>>> fmap(toFloat, fmap(times2, Just(25)))
Just(50.0) Lots of nested calls to >>> (toFloat * times2) * Just(25)
Just(50.0)
>>> (toFloat * times2) * Nothing
Nothing Note that this example uses Now that from hask import Applicative, Monad
instance(Applicative, Maybe).where(
pure = Just
)
instance(Monad, Maybe).where(
bind = lambda x, f: ~(caseof(x)
| m(Just(m.a)) >> f(p.a)
| m(Nothing) >> Nothing)
) The |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论